改变结果

Jam*_*mez 7 indexing ravendb

我的同事问我是否给出了订单线示例,可以初始化一个看起来像这样的viewmodel:

OrderViewModel
   string OrderId
   string CustomerName
   List<OrderLineViewModel> OrderLines

OrderLineViewModel
   string ProductName
   string ROI
   int Quantity
Run Code Online (Sandbox Code Playgroud)

从索引?

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

干杯,

詹姆士

编辑:

我们正在尝试直接从查询中填充视图模型.我们尝试了以下索引:

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.
                                                }
}
Run Code Online (Sandbox Code Playgroud)

Mat*_*int 19

首先,要意识到所有索引都会自动映射Id到一个名为的索引条目__document_id.因此再次映射它没有多大价值.您在此索引映射中所做的只是将其再次复制到另一个名为的索引条目OrderId.

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

第三,索引用于查询非id字段,并提供可能过时最终一致的结果.当您通过它们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; }
}
Run Code Online (Sandbox Code Playgroud)

如果您使用其ID检索单个订单,则可以执行以下操作:

var order = session.Load<Order>(theOrderId);
Run Code Online (Sandbox Code Playgroud)

但是现在你想填充一些像这样的视图模型:

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; }
}
Run Code Online (Sandbox Code Playgroud)

你可以使用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
                 })
};
Run Code Online (Sandbox Code Playgroud)

尽管看到多次调用session.Load(),但实际上只有一次调用数据库.这些.Include陈述确保所有相关文件在第一次通话时加载到会话中.后续调用只是将其拉出本地会话.

以上所有内容均用于通过其ID检索单个订单.如果您想获得所有订单,或获得特定客户的所有订单 - 那么您需要查询.

对特定客户的订单的动态查询如下所示:

var results = session.Query<Order>().Where(x => x.CustomerId == theCustomerId);
Run Code Online (Sandbox Code Playgroud)

如果您想将这些投影到您的视图模型,就像之前可以使用包含:

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
        })
    });
Run Code Online (Sandbox Code Playgroud)

这确实有效,但您可能不想每次都这样写.此外,当您只需要每个产品的单个字段时,必须在会话中加载整个产品和客户记录.这是转换可能有用的地方.您可以按如下方式定义静态索引:

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
                    })
            };
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,当您查询时,转换已经为您设置了数据.您只需指定要投影到的结果VM即可.

var results = session.Query<Order, Orders_Transformed>().As<OrderVM>();
Run Code Online (Sandbox Code Playgroud)

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

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

您将需要修改索引映射以使用该LoadDocument方法,如下所示:

Map = orders => from order in orders
                select new
                {
                    CustomerName = LoadDocument<Customer>(order.CustomerId)
                };
Run Code Online (Sandbox Code Playgroud)

如果您愿意,可以将其与"包含"或"变换"技术相结合,以获取完整视图模型.

另一种技术是从索引中存储这些字段和项目.这对于单个字段非常有效CustomerName,但对于像复杂值这样的复杂值可能有点过分OrderLines.

最后,要考虑的另一种技术是非规范化.考虑一下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; }
}
Run Code Online (Sandbox Code Playgroud)

一旦这样做 - 您在检索订单时不再需要加载产品数据.变换部分变得不必要了,您将得到一个简单的索引投影,如下所示:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用以下内容轻松查询:

var results = session.Query<OrderVM, Orders_ByCustomerName>()
                     .Where(x => x.CustomerName == "Joe")
                     .As<OrderVM>();
Run Code Online (Sandbox Code Playgroud)

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

当我指定.As<OrderVM>()- 这是我实际从一个Order类型移动到一个OrderVM类型 - 然后该CustomerName字段出现在骑行中,因为我们打开了它的字段存储.

TL; DR

RavenDB有很多选择.尝试找到适合您需求的方法.正确的文件设计,并谨慎使用的索引相关文档LoadDocument()将最经常清除一个指标变换的需求.