まず、すべてのインデックスが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()
と、ほとんどの場合、インデックス変換の必要がなくなります。