ravendb - 转换结果

标签 ravendb indexing

我的同事问我是否给了 orderline例如,可以像这样初始化一个 View 模型:

OrderViewModel
   string OrderId
   string CustomerName
   List<OrderLineViewModel> OrderLines

OrderLineViewModel
   string ProductName
   string ROI
   int Quantity

从索引?

我尝试进行成功加载客户名称的转换,但永远无法从订单中获取相关的产品信息。这可以通过转换完成还是我需要从索引字段进行投影?

干杯,

詹姆士

编辑:

我们试图直接从查询中填充 View 模型。
我们尝试了以下索引:
public class OrdersViewIndex : AbstractIndexCreationTask<Order>
{
   Map = orders => from order in orders
                   select new {
                                OrderId = order.id
                              };

   Transform = (database, orders) => from order in orders
                                     let customer = database.Load<Customer>(order.customerId)
                                     select new {
                                                  OrderId = order.id,
                                                  CustomerName = customer.Name,
                                                  OrderLines = // This is where I struggled to answer my colleagues questions as i'd need to load product name.
                                                }
}

最佳答案

首先实现所有索引自动映射Id进入名为 __document_id 的索引条目.所以再贴图也没多大值(value)。您在此索引映射中所做的只是将其再次复制到另一个名为 OrderId 的索引条目中。 .

其次,要了解转换实际上并不是索引的一部分,而只是附加到索引定义并在运行时执行。他们真正提供的只是一种在服务器上变形查询结果的方法。在大多数情况下,这些是您可以在客户端执行的操作。

三、索引用于查询非id字段,提供possibly stale但是 eventually consistent结果。当您通过他们的 Id 检索文档时(也称为文档键),那么使用索引根本没有意义。您想使用 .Load()方法代替,它提供 ACID保证,并且只是从数据库中检索文档。

现在 - 当您的文档只有客户 ID 时,您遇到了如何获取客户名称的问题,以及如何获取产品名称而不仅仅是产品 ID。假设您的文档如下所示:

public class Order
{
    public string Id { get; set; }
    public string CustomerId { get; set; }
    public List<OrderLine> OrderLines { get; set; }
}

public class OrderLine
{
    public string ProductId { get; set; }
    public int Quantity { get; set; }
}

public class Customer
{
    public string Id { get; set; }
    public string Name { get; set; }
}

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
}

如果您使用其 id 检索单个订单,您将执行以下操作:
var order = session.Load<Order>(theOrderId);

但是现在你想要填充一些像这样的 View 模型:
public class OrderVM
{
    public string OrderId { get; set; }
    public string CustomerId { get; set; }
    public string CustomerName { get; set; }
    public List<OrderLineVM> OrderLines { get; set; }
}

public class OrderLineVM
{
    public string ProductId { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
}

您可以使用 Includes 来执行此操作.
var order = session.Include<Order>(x => x.CustomerId)
                   .Include<Order>(x => x.OrderLines.Select(y => y.ProductId))
                   .Load<Order>(theOrderId);

var orderViewModel = new OrderVM
{
    OrderId = order.Id,
    CustomerId = order.CustomerId,
    CustomerName = session.Load<Customer>(order.CustomerId).Name,
    OrderLines = order.OrderLines.Select(x => new OrderLineVM
                 {
                     ProductId = x.ProductId,
                     ProductName = session.Load<Product>(x.ProductId).Name,
                     Quantity = x.Quantity
                 })
};

尽管看到多次调用 session.Load() ,实际上只有一次对数据库的调用。 .Include语句确保所有相关文档都在第一次调用时加载到 session 中。随后的调用只是将它从本地 session 中拉出来。

以上所有内容都是为了通过其 id 检索单个订单。相反,如果您想获取所有订单,或获取特定客户的所有订单 - 那么您需要查询。

对特定客户订单的动态查询如下所示:
var results = session.Query<Order>().Where(x => x.CustomerId == theCustomerId);

如果您想将这些投影到您的 View 模型,就像在您可以使用包含之前一样:
var results = session.Query<Order>()
    .Customize(x => x.Include<Order>(y => y.CustomerId)
                     .Include<Order>(y => y.OrderLines.Select(z => z.ProductId)))
    .Where(x => x.CustomerId == theCustomerId)
    .Select(x => new OrderVM
    {
        OrderId = x.Id,
        CustomerId = x.CustomerId,
        CustomerName = session.Load<Customer>(x.CustomerId).Name,
        OrderLines = order.OrderLines.Select(y => new OrderLineVM
        {
            ProductId = y.ProductId,
            ProductName = session.Load<Product>(y.ProductId).Name,
            Quantity = y.Quantity
        })
    });

这确实有效,但您可能不想每次都写这个。此外,当您只需要一个字段时,整个产品和客户记录必须在 session 中加载。这就是转换有用的地方。您可以按如下方式定义静态索引:
public class Orders_Transformed : AbstractIndexCreationTask<Order>
{
    public Orders_Transformed()
    {
        Map = orders => from order in orders select new { };

        TransformResults = (database, orders) =>
            from order in orders
            select new
            {
                OrderID = order.Id,
                CustomerID = order.CustomerId,
                CustomerName = database.Load<Customer>(order.CustomerId).Name,
                OrderLines = order.OrderLines.Select(y => new
                    {
                        ProductId = y.ProductId,
                        ProductName = database.Load<Product>(y.ProductId).Name,
                        Quantity = y.Quantity
                    })
            };
    }
}

现在,当您查询时,转换已经为您设置了数据。您只需要指定要投影到的结果 VM。
var results = session.Query<Order, Orders_Transformed>().As<OrderVM>();

您可能已经注意到,我在索引映射中根本没有包含任何字段。那是因为我没有尝试查询任何特定字段。所有数据都来自文档本身——索引中唯一的条目是自动添加的 __document_id条目,Raven 使用这些条目来呈现文档存储中的数据 - 用于返回或转换。

现在假设我想通过这些相关字段之一进行查询。例如,我想获取名为 Joe 的客户的所有订单。为此,我需要在索引中包含客户名称。 RavenDB 2.0 添加了一项功能,使这变得非常容易 - Indexing Related Documents .

您将需要修改索引映射以使用 LoadDocument方法,如下:
Map = orders => from order in orders
                select new
                {
                    CustomerName = LoadDocument<Customer>(order.CustomerId)
                };

如果您愿意,可以将其与 Includes 或 Transform 技术结合使用,以恢复完整的 View 模型。

另一种技术是存储这些字段和 project from the index .这对于像 CustomerName 这样的单个字段非常有效。 ,但对于像 OrderLines 这样的复杂值来说可能有点矫枉过正.

最后,另一个需要考虑的技术是 denormalization .考虑一下 Product可能会更名,或被删除。您可能不想使以前的订单无效。最好将与订单相关的任何产品数据复制到 OrderLine 中。目的。
public class OrderLine
{
    public string ProductId { get; set; }
    public string ProductName { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

一旦您这样做了 - 您在检索订单时不再需要加载产品数据。 Transform 部分变得不必要了,您只剩下一个简单的索引投影,如下所示:
public class Orders_ByCustomerName : AbstractIndexCreationTask<Order>
{
    public Orders_ByCustomerName()
    {
        Map = orders => from order in orders
                        select new
                        {
                          CustomerName = LoadDocument<Customer>(order.CustomerId).Name
                        };

        Store("CustomerName", FieldStorage.Yes);
    }
}

您可以通过以下方式轻松查询:
var results = session.Query<OrderVM, Orders_ByCustomerName>()
                     .Where(x => x.CustomerName == "Joe")
                     .As<OrderVM>();

注意在查询中,我第一次指定 OrderVM ,我正在定义索引条目的形状。它只是设置了 lambda,所以我可以指定 x.CustomerName == "Joe" .通常,您会看到用于此目的的特殊“结果”类。真的没关系 - 我可以使用任何具有 CustomerName 的类字符串字段。

当我指定 .As<OrderVM>() - 那是我从 Order 实际搬到的地方输入 OrderVM类型 - 和 CustomerName自从我们为它打开了现场存储以来,现场就出现了。

TL;博士

RavenDB 有很多选择。尝试找出适合您需求的方法。正确的文档设计,小心使用 Indexing Related DocumentsLoadDocument()将最总是消除对索引转换的需要。

关于ravendb - 转换结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14941739/

相关文章:

Javascript 数组分组类别

c++ - 将数组中的所有数字更改为其索引值

postgresql - RavenDB Sql 复制和 Postgres uuid

linq - 查找子集合不包含项目的所有项目

ravendb - 从 RavenDB Studio 中的集合中删除字段

sql - postgresql 查询缓慢避免使用函数

compression - 具有 ntfs 压缩功能的 ravendb 数据库

c# - 将搜索与 RavenDB 结合使用

python - 处理ValueError : cannot reindex from a duplicate axis的便捷方式

c++ - 在成员函数之间传递 const 变量作为数组的索引