Po drugie, rozumiem, że transformaty nie są częścią indeksu, ale są po prostu dołączane do definicji indeksu i wykonywane w czasie wykonywania. Wszystko, co naprawdę dostarczają, jest sposobem na przekształcenie wyników zapytania na serwerze. W większości przypadków są to rzeczy, które możesz wykonywać po stronie klienta.
Po trzecie, indeksy służą do odpytywania w polach niebędących identyfikatorami i dostarczają wyniki possibly stale, ale eventually consistent. Podczas pobierania dokumentów przez ich Id
(zwany także) kluczem dokumentowym , nie ma sensu w ogóle korzystać z indeksu. Zamiast tego chcesz użyć metody .Load()
, która zapewnia gwarancje ACID i po prostu pobiera dokument z bazy danych.
Teraz - masz pytanie, jak uzyskać nazwę klienta, gdy dokument ma tylko identyfikator klienta, i jak uzyskać nazwę produktu, a nie tylko identyfikator produktu. Załóżmy, że Twoje dokumenty wyglądają tak:
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; }
}
Jeśli pobieranie pojedynczego zlecenia za pomocą jego id, należy wykonać następujące czynności:
var order = session.Load<Order>(theOrderId);
Ale teraz chcesz, aby wypełnić jakieś Przeglądanie modeli takich jak te :
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; }
}
Można to zrobić za pomocą 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
})
};
Pomimo wielokrotnych połączeń z numerem session.Load()
, istnieje tylko jedno połączenie z bazą danych. Instrukcje .Include
upewniły się, że wszystkie powiązane dokumenty zostały załadowane do sesji przy pierwszym wywołaniu. Kolejne wywołania po prostu wyciągają je z sesji lokalnej.
Wszystkie powyższe informacje dotyczą pobierania pojedynczego zamówienia według jego identyfikatora.Jeśli zamiast tego chcesz uzyskać wszystkie zamówienia lub uzyskać wszystkie zamówienia dla konkretnego klienta - , następnie musisz zapytać.
Dynamiczna kwerendy dla zamówień danego klienta będzie wyglądać następująco:
var results = session.Query<Order>().Where(x => x.CustomerId == theCustomerId);
Jeśli chciał wystają one do widoku modeli, jak wcześniej można użyć obejmuje:
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
})
});
To działa, ale możesz nie chcieć pisać tego za każdym razem. Ponadto, wszystkie rekordy produktu i klienta muszą być załadowane w sesji, kiedy po prostu chciałeś mieć jedno pole z każdego. Tutaj mogą się przydać transformacje. Możesz zdefiniować indeks statyczny w następujący sposób:
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
})
};
}
}
Teraz, gdy zapytasz, transformacja już skonfigurowała dane za Ciebie. Musisz tylko określić wynikową maszynę wirtualną do projektu.
var results = session.Query<Order, Orders_Transformed>().As<OrderVM>();
Mogłeś zauważyć, że nie uwzględniłem żadnych pól na mapie indeksu. To dlatego, że nie starałem się zapytać o żadne konkretne pole. Wszystkie dane pochodziły z samego dokumentu - jedynymi pozycjami w indeksie są automatycznie dodawane wpisy o numerach __document_id
, a Raven używa ich do prezentacji danych ze składnicy dokumentów - do zwrócenia lub transformacji.
Załóżmy teraz, że chcę zapytać o jedno z tych powiązanych pól. Na przykład chcę uzyskać wszystkie zamówienia dla klientów o imieniu Joe. Aby to osiągnąć, muszę podać nazwę klienta w moim indeksie. RavenDB 2.0 dodał funkcję, która czyni to bardzo łatwym - Indexing Related Documents.
Trzeba będzie zmodyfikować mapę indeksu do korzystania z metody LoadDocument
, co następuje:
Map = orders => from order in orders
select new
{
CustomerName = LoadDocument<Customer>(order.CustomerId)
};
Jeśli chcesz, można łączyć z tym albo zawiera lub Transform technik wrócić pełne zobacz model.
Inną techniką będzie przechowywanie tych pól i project from the index. Działa to bardzo dobrze w przypadku pojedynczych pól, takich jak CustomerName
, ale jest prawdopodobnie przesadzone w przypadku złożonych wartości, takich jak OrderLines
.
I wreszcie inną techniką do rozważenia jest denormalization. Zastanówmy się przez chwilę, czy nazwa Product
mogła zostać zmieniona lub usunięta. Prawdopodobnie nie chcesz unieważnić poprzednich zamówień. Dobrym pomysłem byłoby skopiowanie wszelkich danych dotyczących produktu związanych z zamówieniem do obiektu OrderLine
.
public class OrderLine
{
public string ProductId { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
Po wykonaniu tej czynności nie trzeba już ładować danych produktu podczas pobierania zamówień. Sekcja Transform staje się zbędne, a pozostaje ci prostą projekcją indeksu, co następuje:
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);
}
}
które można wyszukać łatwo:
var results = session.Query<OrderVM, Orders_ByCustomerName>()
.Where(x => x.CustomerName == "Joe")
.As<OrderVM>();
Note w zapytaniu, po raz pierwszy określił OrderVM
, Definiuję kształt wpisów indeksu. Po prostu tworzy lambdy, więc mogę określić x.CustomerName == "Joe"
.Często pojawia się specjalna klasa "Wyniki" używana do tego celu. To naprawdę nie ma znaczenia - mogłem użyć dowolnej klasy, która miała pole string CustomerName
.
Kiedy określić .As<OrderVM>()
- czyli gdzie tak naprawdę przejść od typu Order
do typu OrderVM
- a pole CustomerName
przychodzi na przejażdżkę ponieważ włączone przechowywania pole do niego.
TL; DR
RavenDB ma wiele opcji. Eksperymentuj, aby znaleźć to, co działa zgodnie z Twoimi potrzebami. Prawidłowy projekt dokumentu i ostrożne korzystanie z Indexing Related Documents z LoadDocument()
będzie zawsze usuwać potrzebę transformacji indeksu.
W większości przypadków nie potrzebujesz transformacji i możesz to zrobić we własnym kodzie. Opublikuj, co próbujesz. –