2

我正在开发佣金跟踪系统。商业案例需要销售人员和客户之间的多对多关系。(简化的)实体是:

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) 斯劳马是一位绅士和一位学者。:-)

4

1 回答 1

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();

如果SellerCustomerinCustomerSellers具有复合唯一约束,则生成Payment的 s 应该没有重复项。否则你需要Distinct()SelectMany.

于 2012-06-29T16:20:26.710 回答