Convert Results

My colleagues asked me if the orderline example is given , it would be possible to initialize the viewmodel, which looks like this:

OrderViewModel string OrderId string CustomerName List<OrderLineViewModel> OrderLines OrderLineViewModel string ProductName string ROI int Quantity 

From the index?

I tried to make a conversion that successfully loads the customer name, but can never get information about the corresponding product from the order line. Is it possible to do this with a conversion, or do I need to project from index fields?

Greetings

James

EDIT:

We are trying to populate view models directly from the request. 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. } } 
+7
source share
1 answer

First, make sure that all indexes automatically map Id to an index entry named __document_id . Thus, when comparing it again, not so much. Everything you do on this index map copies it again to another OrderId index OrderId .

Secondly, understand that transformations are not part of the index, but simply tied to the definition of the index and are executed at run time. All they really provide is a way to convert query results to a server. In most cases, this is what you can do on the client side.

Third, indexes are designed to query non-id fields and provide possibly outdated , but ultimately consistent Results. When you retrieve documents by their Id (also called a document key), then it makes no sense to use an index at all. Instead, you want to use the .Load() method, which provides an ACID and simply retrieves a document from the database.

Now you have a question about how to get a customer name when your document has only a customer identifier, and how to get a product name, not just a product identifier. Let your documents look like this:

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

If you are retrieving one order using your identifier, you must do the following:

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

But now you want to populate some view models as follows:

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

You would do this using 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 }) }; 

Despite several calls to session.Load() , there is only one call to the database. The .Include made sure that all related documents were loaded into the session with the first call. Subsequent calls simply pull it out of the local session.

All of the above is intended to obtain one order by its identifier. If instead you want to receive all orders or to receive all orders for a specific client - then you need to request.

A dynamic request for specific customer orders will look like this:

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

If you want to project them onto your viewing models, for example, before you can use them, follow these steps.

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

It works, but you may not write it every time. In addition, all product and customer records must be loaded into the session when you need only one field from each. Transformations can be useful here. You can define a static index as follows:

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

Now, when you request, the conversion has already set up the data for you. You just need to specify the resulting VM for the project.

 var results = session.Query<Order, Orders_Transformed>().As<OrderVM>(); 

You may have noticed that I did not include any fields in the index map at all. This is because I did not try to request any specific field. All data is taken from the document itself β€” the only entries in the index are the automatically added __document_id entries, and Raven uses them to represent data from the document store β€” to return or to convert.

Now let's say that I want to query one of these related fields. For example, I want to receive all orders for customers named Joe. To do this, I need to specify the client name in my index. RavenDB 2.0 adds a feature that makes it very simple - Related indexing documents .

You will need to modify the index map to use the LoadDocument method, as shown below:

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

If you like, you can combine this using the Includes or Transform methods to return the full view model.

Another method is to save these fields and the project from the index . This works very well for single fields like CustomerName , but it can probably be redundant for complex values ​​like OrderLines .

And finally, another way to consider is denormalization . Consider for a moment that a Product can change its name or delete. You probably do not want to cancel previous orders. It would be nice to copy any product data related to the order into the OrderLine object.

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

Once you do this, you no longer need to download product data when you receive orders. The Transform section becomes unnecessary, and you are left with a simple index projection, as shown below:

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

with which you can easily request:

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

Note in the query, the first time I specify OrderVM , I define the form of the index entries. It just installs lambdas, so I can specify x.CustomerName == "Joe" . Often you will see a special "Results" class used for this purpose. It really doesn't matter - I could use any class that had a CustomerName string field.

When I specify .As<OrderVM>() - this is where I actually go from the Order type to the OrderVM type - and the CustomerName field comes for the trip, since we turned it on to store the field.

TL; DR

RavenDB has many options. Experiment to find what works for your needs. Proper document design and careful use of Linked Indexing documents using LoadDocument() will always eliminate the need for index conversion.

+18
source

All Articles