私は手数料追跡システムに取り組んでいます。ビジネスケースでは、営業スタッフと顧客の間に多対多の関係が必要です。(簡略化された)エンティティは次のとおりです。
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
ASPNETMVCサイトの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は2つの新しいクエリを提案しました。それらを試してみると、SQLがCustomerフィールドとSellerフィールドの結合テーブルCustomerSellerでNULLを処理することに余分な労力を費やしていることに気付きました。これらのフィールドは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)結合テーブルで多対多のマッピングフィールドにnullを設定できない場合は、[必須]とマークして、クエリを少し簡略化します。
(3)特に頻繁に使用される場合、または大量のデータに触れる可能性がある場合は、linqクエリのバックグラウンドSQLを確認してください。モンスターが隠れている可能性があります。
(4)Slaumaは紳士であり学者です。:-)