我正在研究佣金跟踪系统。业务案例需要销售人员和客户之间的多对多关系。 (简化)实体是:
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)检查幕后SQL的linq查询,特别是在经常使用时,或者它们可能触及大量数据时。可能有怪物藏匿。
(4)Slauma是一位绅士和学者。 : - )答案 0 :(得分: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();
如果Seller
中的Customer
和CustomerSellers
具有复合唯一约束,则生成的Payment
应该没有重复项。否则,您需要在Distinct()
后添加SelectMany
。