7

My colleagues have asked me if given the orderline example, it'd be possible to initialize a viewmodel looking like this:

OrderViewModel
   string OrderId
   string CustomerName
   List<OrderLineViewModel> OrderLines

OrderLineViewModel
   string ProductName
   string ROI
   int Quantity

From an index?

I've tried doing a transform that successfully loads the customer name but could never manage to get the associated product information from the orderline. Can this be done with a transform or would I need to project from index fields?

Cheers,

James

EDIT:

We're trying to populate view models directly from a query. We tried the following index:

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.
                                                }
}
4

1 に答える 1

19

まず、すべてのインデックスがIdを というインデックス エントリに自動的にマップすることを理解して__document_idください。したがって、再度マッピングする価値はあまりありません。このインデックス マップで行っていることは、 という別のインデックス エントリに再度コピーすることだけOrderIdです。

次に、変換は実際にはインデックスの一部ではなく、インデックス定義に関連付けられて実行時に実行されるだけであることを理解してください。それらが実際に提供するのは、サーバー上でクエリ結果を変形する方法だけです。ほとんどの場合、これらはクライアント側で実行できることです。

第 3 に、インデックスは ID 以外のフィールドに対してクエリを実行するためのものであり、古い可能性がありますが最終的には一貫した結果を提供します。(ドキュメント キーIdとも呼ばれる)ドキュメントを取得する場合、インデックスを使用しても意味がありません。代わりに、ACID保証を提供し、データベースからドキュメントを取得するだけのメソッドを使用したいと考えています。.Load()

ここで、文書に顧客 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 を使用して 1 つの注文を取得する場合は、次のようにします。

var order = session.Load<Order>(theOrderId);

しかし今、次のようないくつかのビューモデルを作成したいと考えています:

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()、実際にはデータベースへの呼び出しは 1 つだけです。この.Includeステートメントにより、最初の呼び出しですべての関連ドキュメントがセッションに読み込まれたことが確認されました。後続の呼び出しは、ローカル セッションからそれを引き出すだけです。

上記はすべて、ID で単一の注文を取得するためのものです。代わりに、すべての注文を取得したい場合、または特定の顧客のすべての注文を取得したい場合は、クエリ実行する必要があります。

特定の顧客の注文に対する動的クエリは次のようになります。

var results = session.Query<Order>().Where(x => x.CustomerId == theCustomerId);

これらをビューモデルに投影したい場合は、インクルードを使用する前のように:

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
        })
    });

これは機能しますが、毎回これを書きたくない場合があります。また、それぞれから単一のフィールドが必要な場合は、製品と顧客のレコード全体をセッションにロードする必要があります。これは、変換が役立つ場所です。静的インデックスは次のように定義できます。

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 では、これを非常に簡単にする機能が追加されました。関連ドキュメントのインデックス作成です。

LoadDocumentこのメソッドを使用するには、次のようにインデックス マップを変更する必要があります。

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

必要に応じて、これをインクルードまたは変換手法と組み合わせて、完全なビュー モデルを取り戻すことができます。

もう 1 つの手法は、これらのフィールドを格納し、インデックスから投影することです。これは のような単一のフィールドでは非常にうまく機能しますがCustomerName、 のような複雑な値ではおそらくやり過ぎですOrderLines

最後に、考慮すべきもう 1 つの手法は非正規化です。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、インデックス エントリの形状を定義していることに注意してください。指定できるようにラムダを設定するだけx.CustomerName == "Joe"です。多くの場合、この目的で使用される特別な「結果」クラスが表示されます。それは本当に問題ではありませんCustomerName。文字列フィールドを持つ任意のクラスを使用できます。

指定する.As<OrderVM>()と (ここで実際にOrder型から型に移動します)、フィールド ストレージをオンにしたのでOrderVM、フィールドがそれに沿って移動します。CustomerName

TL;DR

RavenDB には多くのオプションがあります。実験して、ニーズに合ったものを見つけてください。適切なドキュメント デザインと、関連ドキュメントのインデックス作成を慎重に使用するLoadDocument()と、ほとんどの場合、インデックス変換の必要がなくなります。

于 2013-02-18T23:05:59.680 に答える