2013-02-18 64 views
7

我的同事們都問我如果有orderline例如,它會是可能的初始化視圖模型看起來像這樣:轉化結果

OrderViewModel 
    string OrderId 
    string CustomerName 
    List<OrderLineViewModel> OrderLines 

OrderLineViewModel 
    string ProductName 
    string ROI 
    int Quantity 

從索引?

我試過做成功加載客戶名稱的轉換,但無法從訂單行中獲取關聯的產品信息。這可以通過轉換來完成,還是需要從索引字段進行投影?

乾杯,

詹姆斯

編輯:

我們試圖直接從查詢填充視圖模型。 我們嘗試了以下指標:

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

在大多數情況下,您不需要轉換,只需在自己的代碼中執行此操作即可。請發佈你已經嘗試過的。 – 2013-02-18 18:41:44

回答

18

首先,實現各項指標的自動Id映射到名爲__document_id索引條目。所以再次映射它沒有太大的價值。您在這張索引圖中所做的所有操作都是將其複製到另一個索引條目OrderId。其次,瞭解轉換不是索引的一部分,但只是附加到索引定義並在運行時執行。他們真正提供的是一種在服務器上改變查詢結果的方法。在大多數情況下,這些都是客戶端可以做的事情。

第三,索引用於查詢非id字段,並提供possibly stale但是eventually consistent結果。當您通過Id(也稱爲文檔鍵)檢索文檔時,根本沒有必要使用索引。您希望使用.Load()方法,該方法提供了ACID保證,並且只是從數據庫中檢索文檔。

現在 - 當您的文檔具有客戶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檢索一個訂單,你會做以下幾點:

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(),實際上只有一個調用數據庫。 .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增加了一項功能,使其非常簡單 - Indexing Related Documents

您需要修改索引地圖使用LoadDocument方法,如下所示:

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

如果你喜歡,你可以用任何的包括結合這一點,或者變換技術,以全額取回查看模型。

另一種技術是存儲這些字段和project from the index。這對於CustomerName這樣的單個字段非常有效,但對於像OrderLines這樣的複雜值可能是過度的。

最後,另一種考慮的技術是denormalization。暫時考慮一下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; } 
} 

一旦你這樣做 - 當檢索訂單時,不再需要加載產品數據。變換部件變得不必要,並且如果只剩下一個簡單的指標投影,如下所示:

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,我正在定義索引條目的形狀。它只是設置lambda表達式,所以我可以指定x.CustomerName == "Joe"。通常,您會看到一個特殊的「結果」類用於此目的。這真的沒關係 - 我可以使用任何具有CustomerName字符串字段的類。

當我指定.As<OrderVM>() - 那是我真正從Order型移動到OrderVM型 - 和CustomerName場走來的行列,因爲我們打開字段存儲它。

TL; DR

RavenDB有很多的選擇。試着找出適合你需求的東西。正確的文檔設計和小心使用Indexing Related DocumentsLoadDocument()大多數情況下都會消除對索引轉換的需求。

+0

非常感謝您花了Matt的時間,一如既往,您的答案真的很有幫助..這一個超過他們! - 我和工作中的人一直在努力弄清楚do/dont's和when/whys--我會讓每個人都閱讀這個答案! - 再次感謝! – Jamez 2013-02-19 07:49:51

+0

在RavenDB 2.5中,現在有[Results Transformers]的概念(http://ravendb.net/docs/2.5/client-api/querying/results-transformation/result-transformers)。我會盡快更新這個答案來解釋它們。 – 2013-09-04 03:30:36