2013-02-18 8 views
7

Meine Kollegen mir das orderline Beispiel gefragt, ob gegeben, würde es möglich sein, ein Ansichtsmodell zu initialisieren wie folgt aussehen:Transforming Ergebnisse

OrderViewModel 
    string OrderId 
    string CustomerName 
    List<OrderLineViewModel> OrderLines 

OrderLineViewModel 
    string ProductName 
    string ROI 
    int Quantity 

aus einem Index?

Ich habe versucht, eine Transformation durchzuführen, die erfolgreich den Kundennamen lädt, aber nie die zugehörigen Produktinformationen aus dem Bestellposten bekommen konnte. Kann dies mit einer Transformation durchgeführt werden oder müsste ich aus Indexfeldern projizieren?

Cheers,

James

EDIT:

Wir versuchen Ansicht Modelle aus einer Abfrage direkt zu füllen. Wir haben versucht, den folgenden 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. 
               } 
} 
+0

In den meisten Fällen benötigen Sie keine Transformation und können dies nur in Ihrem eigenen Code tun. Bitte posten Sie, was Sie versucht haben. –

Antwort

18

Erstens erkennen, dass alle Indizes automatisch den Id in einen Indexeintrag __document_id genannt Karte. Es ist also nicht viel Wert, es erneut zu kartieren. Alles, was Sie in dieser Index-Map tun, ist, sie erneut in einen anderen Index-Eintrag namens OrderId zu kopieren.

Zweitens verstehen, dass Transformationen nicht wirklich Teil des Indexes sind, sondern nur an die Indexdefinition angehängt und zur Laufzeit ausgeführt werden. Alles, was sie wirklich bieten, ist eine Möglichkeit, die Abfrageergebnisse auf dem Server zu morphen. In den meisten Fällen sind dies Dinge, die Sie clientseitig tun können.

Drittens sind Indizes zum Abfragen von nicht-ID-Feldern und liefern possibly stale aber eventually consistent Ergebnisse. Wenn Sie Dokumente über ihre Id (auch Dokumentschlüssel abgerufen), dann ist es sinnvoll, überhaupt einen Index zu verwenden. Sie möchten stattdessen die .Load()-Methode verwenden, die ACID garantiert, und ruft das Dokument nur aus der Datenbank ab.

Jetzt - Sie hatten die Frage, wie Sie den Kundennamen erhalten, wenn Ihr Dokument nur die Kundennummer hat, und wie Sie den Produktnamen anstelle nur der Produkt-ID erhalten. Nehmen wir an, Ihre Dokumente dies wie folgt aussehen:

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

Wenn Sie einen einzelnen Auftrag mit seiner ID abrufen, würden Sie wie folgt vorgehen:

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

Aber jetzt wollen Sie einige Ansicht Modelle wie diese bevölkern :

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

Sie würden dies mit tun.

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

Trotz mehrerer Anrufe zu session.Load() sehen, ist es wirklich nur ein Aufruf an die Datenbank. Die .Include-Anweisungen haben sichergestellt, dass alle zugehörigen Dokumente beim ersten Aufruf in die Sitzung geladen wurden. Die nachfolgenden Aufrufe ziehen es nur aus der lokalen Sitzung heraus.

Alles oben genannte ist für das Abrufen einer einzelnen Bestellung durch seine ID.Wenn Sie stattdessen alle Bestellungen erhalten möchten oder alle Bestellungen für einen bestimmten Kunden erhalten möchten - , dann müssen Sie abfragen.

Eine dynamische Abfrage für Bestellungen eines bestimmten Kunden würde wie folgt aussehen:

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

Wenn Sie diese zu Ihrer Ansicht Modelle projizieren wollen, wie vor können Sie verwenden, beinhaltet:

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

Das funktioniert, aber Sie möchten das vielleicht nicht jedes Mal schreiben. Außerdem müssen die gesamten Produkt- und Kundendatensätze in die Sitzung geladen werden, wenn Sie nur ein einzelnes Feld von jedem wollen. Hier können Transformationen nützlich sein. Sie können einen statischen Index wie folgt definieren:

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

Wenn Sie sich jetzt fragen, hat die Transformation bereits die Daten für Sie einrichten. Sie müssen nur die resultierende VM angeben, in die das Projekt projiziert werden soll.

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

Sie haben vielleicht bemerkt, dass ich überhaupt keine Felder in die Indexkarte aufgenommen habe. Das ist, weil ich nicht versuchte, über ein bestimmtes Feld abzufragen. Alle Daten stammen aus dem Dokument selbst - die einzigen Einträge im Index sind die automatisch hinzugefügten __document_id Einträge, und Raven verwendet diese, um Daten aus dem Dokumentenspeicher darzustellen - für die Rückgabe oder für die Transformation.

Sagen wir jetzt, dass ich nach einem dieser verwandten Felder abfragen möchte. Zum Beispiel möchte ich alle Bestellungen für Kunden namens Joe erhalten. Um dies zu erreichen, muss ich den Kundennamen in meinen Index aufnehmen. RavenDB 2.0 hat eine Funktion hinzugefügt, die das sehr einfach macht - Indexing Related Documents.

Sie müssen die Indexkarte modifizieren, um die LoadDocument Methode zu verwenden, wie folgt:

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

Wenn Sie möchten, können Sie mit diesem kombinieren kann entweder die Includes oder die Transformation Techniken, um die volle zurück zu bekommen Modell ansehen.

Eine andere Technik wäre, diese Felder zu speichern und project from the index. Dies funktioniert sehr gut für einzelne Felder wie CustomerName, aber ist wahrscheinlich für komplexe Werte wie OrderLines übertrieben.

Und schließlich ist eine andere Technik in Betracht zu ziehen denormalization. Überlegen Sie kurz, ob sich der Name einer ändern oder gelöscht werden kann. Wahrscheinlich möchten Sie vorherige Bestellungen nicht ungültig machen. Es wäre eine gute Idee, alle für den Auftrag relevanten Produktdaten in das Objekt OrderLine zu kopieren.

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

Sobald Sie das tun - müssen Sie beim Abrufen von Bestellungen keine Produktdaten mehr laden. Die Transform Abschnitt unnötig, und Sie sind mit einem einfachen Index Projektion links wie folgt:

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

, die Sie leicht mit abfragen können: Geben Sie I

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

Hinweis in der Abfrage, das erste Mal, OrderVM, definiere ich die Form der Indexeinträge. Es richtet nur lambdas ein, also kann ich x.CustomerName == "Joe" spezifizieren.Oft sehen Sie eine spezielle "Ergebnis" -Klasse, die für diesen Zweck verwendet wird. Es ist wirklich egal - ich könnte jede Klasse verwenden, die ein CustomerName Zeichenfeld hat.

Wenn ich .As<OrderVM>() angeben - das ist, wo ich von einem Order Typ in einen OrderVM Typ tatsächlich bewegen - und das CustomerName Feld für die Fahrt kommt, da wir auf dem Feld Speicher für sie gedreht.

TL; DR

RavenDB hat viele Möglichkeiten. Experimentieren Sie, um herauszufinden, was für Ihre Bedürfnisse funktioniert. Ein richtiger Dokumententwurf und die sorgfältige Verwendung von Indexing Related Documents mit LoadDocument() werden immer die Notwendigkeit einer Indextransformation beseitigen.

+0

Vielen Dank für die ganze Zeit, die Sie Matt verbracht haben, wie immer sind Ihre Antworten wirklich hilfreich .. Dieser eine überstieg sie alle! - Ich selbst und die Jungs bei der Arbeit haben gekämpft, um die Do's/Dont's und das Wann/Warum herauszufinden - ich werde jeden dazu bringen, diese Antwort zu lesen! - Danke noch einmal! – Jamez

+0

In RavenDB 2.5 gibt es jetzt das Konzept [Results Transformers] (http://ravendb.net/docs/2.5/client-api/querying/results-transformation/result-transformers). Ich werde diese Antwort bald aktualisieren, um sie zu berücksichtigen. –