我正在开发佣金跟踪系统。商业案例需要销售人员和客户之间的多对多关系。(简化的)实体是:
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
我正在我的 ASP NET MVC 站点上使用 linq/EF 尝试此代码:
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 看起来很复杂(这超出了我的 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 和 Seller 字段的连接表 CustomerSeller 上的 NULL 时付出了额外的努力。这是我的一个错误,因为这些字段应该是不可为空的。我在实体中将它们设置为 [必需]:
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)
)
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) 斯劳马是一位绅士和一位学者。:-)