2017-10-04 100 views
0

鑑於兩類生成的代碼先用EF,具有父子關係:屬性總和轉換爲對IQueryable的SQL和LINQ到實體

class Parent { 
    //... 
    public virtual ICollection<Child> Children 
} 

class Child { 
    //... 
    public decimal Amount{ get; set; } 
    public decimal UnitPrice { get; set; } 
} 

我想對家長創建一個屬性Total,像

但如果我這樣做那樣,然後做

var list = ctx.Parents 
        .Where(p => p.Total > 1000) 
        .Select(p => new { 
         p.Id, 
         p.Total, 
         Count = p.Children.Count }); 
foreach(var item in list){ 
     Console.WriteLine($"Id: {item.} ..."); 
} 

我,說

「System.NotSupportedException」類型的未處理的異常出現在EntityFramework.SqlServer.dll

錯誤

其他信息:指定的類型成員「合計」不支持LINQ到實體。僅支持初始化程序,實體成員和實體導航屬性。

雖然,在我看來,它不應該那麼難EF生成查詢使用SUM(UnitPrice * Amount) as Total我無法得到它的工作。

我已經用靜態表達試過像

public static Expression<Func<Parent, decimal>> GetTotal() 
    { 
     return p=> p.Children.Sum(line => line.ItemQty * line.UnitPrice); 
    } 

雖然它應該是可以接受做這個計算只是代碼。我想用這個例子來學習更多關於如何使用IQueryable的知識。

一個小更迭

鑑於家長

public static Expression<Func<Parent, decimal?>> Total => 
     p=> (from c in p.Childeren 
      select c.UnitPrice * c.ItemQty).Sum(); 

而下面的靜態屬性然後做

var list = ctx.Parents.Select(Parent.Total); 

我得到了一個包含所有彙總列表,我看到了EF生成以下查詢

SELECT 
    (SELECT 
     SUM([Filter1].[A1]) AS [A1] 
     FROM (SELECT 
      [Extent2].[UnitPrice] * CAST([Extent2].[Amount] AS decimal(19,0)) AS [A1] 
      FROM [dbo].[Child] AS [Extent2] 
      WHERE [Extent1].[Id] = [Extent2].[ParentId] 
     ) AS [Filter1]) AS [C1] 
    FROM [dbo].[Parent] AS [Extent1] 

因此,它確實能夠將Sum()方法轉換爲SQL。現在我只需要在Where()中使用它。

回答

1

計算的屬性只在類中知道,而不在數據庫中。您需要實例化對象(到類中),然後才能訪問這些計算的屬性。

這不是很難得到這個工作:

var list = ctx.Parents 
       .Where(p => p.Total > 1000) 
       .ToList() //This instantiates your data into objects 
       .Select(p => new { 
        p.Id, 
        p.Total, 
        Count = p.Children.Count }); 

這可能需要包括聲明,這取決於你是否已經有了延遲加載與否:

var list = ctx.Parents 
       .Include(p => p.Children) //Children will be populated upon instantiation 
       .Where(p => p.Total > 1000) 
       .ToList() //This instantiates your data into objects 
       .Select(p => new { 
        p.Id, 
        p.Total, 
        Count = p.Children.Count }); 

雖然在我看來,EF不應該那麼難以使用SUM(UnitPrice * Amount) as Total來生成查詢,我無法實現它的工作。

還有比你提到的更多。

SUM是什麼?不是當前結果(這是Parents表),您需要在Children表(由ParentId分組)完成計算的總和,這是一個完全不同的數據集,與您迄今爲止一直在使用的數據集完全不同。

這就是爲什麼EF不會做你想做的事情。它試圖決定查詢的SELECT部分,但是您的期望需要更復雜的查詢體;一個將孩子和父母聯繫在一起,以便對孩子進行計算,然後將這些結果彙總爲一個單一的值。

如果您嘗試自己編寫SQL查詢,您會發現它的執行比SUM(UnitPrice * Amount) as Total要複雜得多更多

+0

感謝您的解釋。我們確實正在解決性能問題(比此更復雜的場景),其中EF需要長達9秒,而手工查詢在300ms內完成。我正在調查EF的功能,因爲我不想回過頭來手動編寫SQL查詢並在數據庫中複製業務邏輯。 –

+0

@StijnVanAntwerpen EF是一個附加層。充其量,與手工製作SQL查詢相比,您將獲得相同的性能(或性能可以忽略不計)。 EF受限於它可以隱式地從C#轉換爲SQL。 – Flater

+0

嗯,我知道EF有限制將c#翻譯成sql(當然,它仍然不使用魔法)。我希望像Sum()這樣的東西得到支持......如果我確實在DB(VW_ParentWithTotal)中定義了一個視圖讓EF從中生成模型,那麼開銷是可以忽略的。但我正試圖消除這一步。 (因爲VW_ParentWithTotal已經存在於c#中的BL) –