2012-06-29 66 views
2

我正在處理一個委託跟蹤系統。業務案例需要銷售人員和客戶之間的多對多關係。 (簡化)實體是:實體框架多對多查詢生成粗糙SQL

public class Customer { 
    public int Id { get; set; } 
    public string Name { get; set; } 

    public virtual ICollection<CustomerSeller> CustomerSellers { get; set; } 
    public virtual ICollection<Payment> Payments { get; set; } 
} 

public class Seller { 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public virtual ICollection<CustomerSeller> CustomerSellers { get; set; } 
} 

// join table w/payload for many-to-many relationship 
public class CustomerSeller { 
    public int Id { get; set; } 
    public Seller Seller { get; set; } 
    public Customer Customer { get; set; } 
    public decimal CommissionRate { get; set; } 
} 

public class Payment { 
    public int Id { get; set; } 
    public Customer ReceivedFrom { get; set; } 
    public DateTime Date { get; set; } 
    public decimal Amount { get; set; } 
} 

我一時目標是讓客戶的所有付款鏈接到特定銷售人員的名單。如果我寫直接的SQL它會是這個樣子:

select Payment.* 
from Payment 
inner join CustomerSeller on CustomerSeller.Customer_Id = Payment.ReceivedFrom_Id 
where CustomerSeller.Seller_Id = @sellerIdToQuery 

我使用LINQ/EF想這對我的ASP NET MVC的網站使用此代碼:

public ActionResult Sales(int id) { 
    var qry = (
    from p in db.Payments 
    join cs in db.CustomerSellers on p.ReceivedFrom equals cs.Customer 
    where cs.Seller.Id == id 
    select p); 
    var paymentList = qry.ToList(); 
    return View(paymentList); 
} 

這並不工作,但幕後的SQL看起來waaay複雜(這是出乎我的SQL解碼能力來分析):

{SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Date] AS [Date], 
[Extent1].[Amount] AS [Amount], 
[Extent1].[ReceivedFrom_Id] AS [ReceivedFrom_Id] 
FROM [dbo].[Payment] AS [Extent1] 
INNER JOIN [dbo].[CustomerSeller] AS [Extent2] ON EXISTS (SELECT 
    1 AS [C1] 
    FROM  (SELECT 1 AS X) AS [SingleRowTable1] 
    LEFT OUTER JOIN (SELECT 
     [Extent3].[Id] AS [Id] 
     FROM [dbo].[Customer] AS [Extent3] 
     WHERE [Extent1].[ReceivedFrom_Id] = [Extent3].[Id]) AS [Project1] ON 1 = 1 
    LEFT OUTER JOIN (SELECT 
     [Extent4].[Id] AS [Id] 
     FROM [dbo].[Customer] AS [Extent4] 
     WHERE [Extent2].[Customer_Id] = [Extent4].[Id]) AS [Project2] ON 1 = 1 
    LEFT OUTER JOIN (SELECT 
     [Extent5].[Id] AS [Id] 
     FROM [dbo].[Customer] AS [Extent5] 
     WHERE [Extent1].[ReceivedFrom_Id] = [Extent5].[Id]) AS [Project3] ON 1 = 1 
    LEFT OUTER JOIN (SELECT 
     [Extent6].[Id] AS [Id] 
     FROM [dbo].[Customer] AS [Extent6] 
     WHERE [Extent2].[Customer_Id] = [Extent6].[Id]) AS [Project4] ON 1 = 1 
    WHERE ([Project1].[Id] = [Project2].[Id]) OR (([Project3].[Id] IS NULL) AND ([Project4].[Id] IS NULL)) 
) 
WHERE [Extent2].[Seller_Id] = @p__linq__0} 

我什麼格式錯誤我的LINQ查詢(我是新)特別感興趣..怎麼寫呢?或者我的實體結構有問題嗎?或者SQL很好,並且可以在生產環境中高效運行大量數據?

進展情況:

首先,Slauma提出了兩個新的查詢。在對它們進行實驗時,我注意到SQL在處理Customer和Customer字段的連接表CustomerSeller上付出了額外的努力。這是我的錯誤,因爲這些字段應該是不可空的。我將它們設置爲[必填]在實體:

public class CustomerSeller { 
    public int Id { get; set; } 
    [Required] 
    public Seller Seller { get; set; } 
    [Required] 
    public Customer Customer { get; set; } 
    public decimal CommissionRate { get; set; } 
} 

第一個查詢Slauma寫道:

var qry = from p in db.Payments 
      where p.ReceivedFrom.CustomerSellers.Any(cs => cs.Seller.Id == id) 
      select p; 

產生了大大優於SQL:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Date] AS [Date], 
[Extent1].[Amount] AS [Amount], 
[Extent1].[ReceivedFrom_Id] AS [ReceivedFrom_Id] 
FROM [dbo].[Payment] AS [Extent1] 
WHERE EXISTS (SELECT 
    1 AS [C1] 
    FROM [dbo].[CustomerSeller] AS [Extent2] 
    WHERE ([Extent1].[ReceivedFrom_Id] = [Extent2].[Customer_Id]) AND ([Extent2].[Seller_Id] = @p__linq__0) 
) 

下TWEAK Slauma建議是加入我的原始查詢的Id字段,而不是整個實體:

var qry = from p in db.Payments 
      //old: join cs in db.CustomerSellers on p.ReceivedFrom equals cs.Customer 
      //new: 
       join cs in db.CustomerSellers on p.ReceivedFrom.Id equals cs.Customer.Id 
      where cs.Seller.Id == id 
      select p; 

這會產生一個非常高品質的SQL語句:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Date] AS [Date], 
[Extent1].[Amount] AS [Amount], 
[Extent1].[ReceivedFrom_Id] AS [ReceivedFrom_Id] 
FROM [dbo].[Payment] AS [Extent1] 
INNER JOIN [dbo].[CustomerSeller] AS [Extent2] ON [Extent1].[ReceivedFrom_Id] = [Extent2].[Customer_Id] 
WHERE [Extent2].[Seller_Id] = @p__linq__0 

本質是什麼,我會在SQL直接寫我自己。

小貼士:

(1)加入上的按鍵,而不是完整的實體。 (2)如果連接表不能包含多對多映射字段的空值,請將它們標記爲[必需],這會簡化查詢。 (3)檢查linq查詢的幕後SQL,特別是在經常使用或者他們可能會觸及大量數據的情況下。可能有怪物躲藏。

(4)Slauma是一位紳士和學者。:-)

+0

說實話 - 它看起來很亂 - 你看過查詢計劃還是你寫的查詢? – Charleh

回答

1

我會寫的查詢是這樣的:

var qry = from p in db.Payments 
      where p.ReceivedFrom.CustomerSellers.Any(cs => cs.Seller.Id == id) 
      select p; 
var paymentList = qry.ToList(); 

或完全地被擴展方法:

var qry = db.Payments 
    .Where(p => p.ReceivedFrom.CustomerSellers.Any(cs => cs.Seller.Id == id)); 
var paymentList = qry.ToList(); 

我不知道,有多少的SQL將從不同您的查詢。

編輯

備選:

var qry = db.CustomerSellers 
    .Where(cs => cs.Seller.Id == id) 
    .SelectMany(cs => cs.Customer.Payments); 
var paymentList = qry.ToList(); 

如果SellerCustomerCustomerSellers有複合唯一約束產生的Payment S的關係有沒有重複。否則,您需要在SelectMany後附加Distinct()

+0

大爲改善事實證明:。 '{SELECT [Extent1] [ID] AS [ID], [Extent1] [日期] AS [日期], [Extent1] [量] AS [金額], [Extent1] [ReceivedFrom_Id] AS [ReceivedFrom_Id] FROM [DBO]。[付款] AS [Extent1] WHERE EXISTS(SELECT \t 1 AS [C1] \t FROM [DBO]。[CustomerSeller] AS [Extent2] \t WHERE([Extent2]。[Customer_Id] IS NOT NULL)AND([Extent1]。[ReceivedFrom_Id] = [Extent2]。[Customer_Id])AND([Extent2]。[Seller_Id] = @ p__linq__0) )}' –

+0

@LarryS:順便說一句,看起來很奇怪的部分我在你的LINQ查詢中加入了完整的實體'p.ReceivedFrom等於cs.Customer'而不是隻有鍵:'p.ReceivedFrom.Id等於cs.Customer.Id'。也許即使這種改變會改善你的查詢... – Slauma

+0

@LarryS:另一種選擇,請參閱我的編輯。 – Slauma