2013-02-18 18 views
7

Moi koledzy pytali mnie, czy dany przykład orderline, że byłoby to możliwe, aby zainicjować ViewModel wygląda tak:Transforming Wyniki

OrderViewModel 
    string OrderId 
    string CustomerName 
    List<OrderLineViewModel> OrderLines 

OrderLineViewModel 
    string ProductName 
    string ROI 
    int Quantity 

z indeksu?

Próbowałem wykonać transformację, która pomyślnie ładuje nazwę klienta, ale nigdy nie uda się uzyskać informacji o powiązanych produktach z linii zamówienia. Czy można to zrobić z transformacją, czy też powinienem projektować z pól indeksu?

Cheers,

James

EDIT:

Próbujemy zapełnić widoku modele bezpośrednio z zapytania. Staraliśmy następujący wskaźnik:

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. 
               } 
} 
+0

W większości przypadków nie potrzebujesz transformacji i możesz to zrobić we własnym kodzie. Opublikuj, co próbujesz. –

Odpowiedz

18

pierwsze, uświadomić sobie, że wszystkie indeksy automatycznie odwzorować Id do wpisu indeksu o nazwie __document_id. W związku z tym ponowne mapowanie nie ma zbyt dużej wartości. Wszystko, co robisz na tej mapie indeksu, jest ponownie kopiowane do innej pozycji indeksu o nazwie OrderId.

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.

+0

Bardzo dziękuję za cały czas spędzony Matt, jak zawsze twoje odpowiedzi są naprawdę pomocne .. Ten jeden na szczycie wszystkich! - ja i faceci w pracy walczymy, aby rozgryźć, co robimy/czego nie robimy i kiedy/dlaczego - będę kazał wszystkim przeczytać tę odpowiedź! - dzięki jeszcze raz! – Jamez

+0

W RavenDB 2.5, istnieje teraz pojęcie [Results Transformers] (http://ravendb.net/docs/2.5/client-api/querying/results-transformation/result-transformers). Niedługo zaktualizuję tę odpowiedź, aby je uwzględnić. –