2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LINQのメソッド構文で内部結合、外部結合した時のメモ

Last updated at Posted at 2019-10-12

はじめに

LINQのメソッド構文を使用して内部結合、外部結合した時のメモ。
複数結合するとソースの記述が複雑になったり、発行されるSQLが複雑になったりするので、なるべく簡潔に。

環境

C#
.Net Core 2.1.507
SQLServer 2017 - 14.0.1000.169 (X64)

前提

テーブルA,B,Cを内部結合し、それぞれに対してテーブルD,E,Fを外部結合する。
テーブルA,B,Cは運用テーブルとその詳細テーブルで、テーブルD,E,Fはマスタテーブルというイメージ。

・テーブルA      →外部結合→テーブルD
→内部結合→テーブルB  →外部結合→テーブルE
→内部結合→テーブルC  →外部結合→テーブルF

よくある書き方

var list = 
    // TableA
    dbContext.TableA

    // TableAとTableBを内部結合
    .Join(dbContext.TableB,
        a => a.Id,
        b => b.A_Id,
        (a, b) => new { a, b })

   // TableA,BとTableCを内部結合
   .Join(dbContext.TableC,
        ab => ab.b.Id,
        c => c.B_Id,
        (ab, c) => new { ab, c })

    // TableAとTableDを外部結合
    .GroupJoin(dbContext.TableD,
        abc => abc.ab.a.key,
        d => d.key,
        (abc, d) => new { abc, d })
    .SelectMany(d => d.d.DefaultIfEmpty(), 
        (abc, d) => new { abc, d })

    // TableBとTableEを外部結合
    .GroupJoin(dbContext.TableE,
        abcd => abcd.abc.abc.ab.b.key,
        e => e.key,
        (abcd, e) => new { abcd, e })
    .SelectMany(e => e.e.DefaultIfEmpty(), 
        (abcd, e) => new { abcd, e })

    // TableCとTableFを外部結合
    .GroupJoin(dbContext.TableF),
        abcde => abcde.abcd.abcd.abc.abc.c.key,
        f => f.key,
        (abcde, f) => new { abcde, f })
    .SelectMany(f => f.f.DefaultIfEmpty(), 
        (abcde, f) => new { abcde, f })

    // 各テーブルから必要なデータを取得
    .Select(abcdef => new {
        a_id = abcdef.abcde.abcde.abcd.abcd.abc.abc.ab.a.Id,
        b_id = abcdef.abcde.abcde.abcd.abcd.abc.abc.ab.b.Id,
        c_id = abcdef.abcde.abcde.abcd.abcd.abc.abc.c.Id,
        d_id = abcdef.abcde.abcde.abcd.abcd.d.Id,
        e_id = abcdef.abcde.abcde.e.Id,
        f_id = abcdef.f.Id,
    })
    
    .ToList();

だいたいこんな感じ。テーブルが増えると最後のSelect部分がひどいことになる。
結合するテーブルが増えるたびに修正が必要になるのも気に入らない。

別名を定義してみる

var list =
    // TableA
    dbContext.TableA

    // TableAとTableBを内部結合
    .Join(dbContext.TableB,
        a => a.Id,
        b => b.A_Id,
        (a, b) => new { a, b })

   // TableA,BとTableCを内部結合
   .Join(dbContext.TableC,
        x => x.b.Id,
        c => c.B_Id,
        (x, c) => new { x.a, x.b, c })          // A,B,Cが同階層に並ぶよう定義する。

    // TableAとTableDを外部結合
    .GroupJoin(dbContext.TableD,
        x => x.a.key,
        d => d.key,
        (x, d) => new { x, d })
    .SelectMany(d => d.d.DefaultIfEmpty(), 
        (x, d) => new { x.x.a, x.x.b, x.x.c, d })         // A,B,C,Dが同階層に並ぶよう定義する。以下同文。

    // TableBとTableEを外部結合
    .GroupJoin(dbContext.TableE,
        x => x.b.key,
        e => e.key,
        (x, e) => new { x, e })
    .SelectMany(e => e.e.DefaultIfEmpty(), 
        (x, e) => new { x.x.a, x.x.b, x.x.c, x.x.d, e })

    // TableCとTableFを外部結合
    .GroupJoin(dbContext.TableF,
        x => x.c.key,
        f => f.key,
        (x, f) => new { x, f })
    .SelectMany(f => f.f.DefaultIfEmpty(), 
        (x, f) => new { x.x.a, x.x.b, x.x.c, x.x.d, x.x.e, f })

    // 各テーブルから必要なデータを取得
    .Select(x => new {
        a_id = x.a.Id,
        b_id = x.b.Id,
        c_id = x.c.Id,
        d_id = x.d.Id,
        e_id = x.e.Id,
        f_id = x.f.Id,
    })

    .ToList();

これなら最後のSelectはきれいに表現できる。
結局、途中でいちいち別名を定義することになるが・・・まあまだマシか。

検索条件を指定

var list =
    // TableA
    dbContext.TableA

    // TableAとTableBを内部結合
    .Join(dbContext.TableB,
        a => a.Id,
        b => b.A_Id,
        (a, b) => new { a, b })

   // TableA,BとTableCを内部結合
   .Join(dbContext.TableC,
        x => x.b.Id,
        c => c.B_Id,
        (x, c) => new { x.a, x.b, c })          // A,B,Cが同階層に並ぶよう定義する。

    // TableAとTableDを外部結合
    .GroupJoin(dbContext.TableD,
        x => x.a.key,
        d => d.key,
        (x, d) => new { x, d })
    .SelectMany(d => d.d.DefaultIfEmpty(), 
        (x, d) => new { x.x.a, x.x.b, x.x.c, d })         // A,B,C,Dが同階層に並ぶよう定義する。以下同文。

    // TableBとTableEを外部結合
    .GroupJoin(dbContext.TableE,
        x => x.b.key,
        e => e.key,
        (x, e) => new { x, e })
    .SelectMany(e => e.e.DefaultIfEmpty(), 
        (x, e) => new { x.x.a, x.x.b, x.x.c, x.x.d, e })

    // TableCとTableFを外部結合
    .GroupJoin(dbContext.TableF,
        x => x.c.key,
        f => f.key,
        (x, f) => new { x, f })
    .SelectMany(f => f.f.DefaultIfEmpty(), 
        (x, f) => new { x.x.a, x.x.b, x.x.c, x.x.d, x.x.e, f })

    // 検索条件が指定されていたら絞り込む
    .Where(x =>
       (key1 == null ? true : key1 == x.a.key1)
    && (key2 == null ? true : key2 == x.a.key2)
    )

    // 各テーブルから必要なデータを取得
    .Select(x => new {
        a_id = x.a.Id,
        b_id = x.b.Id,
        c_id = x.c.Id,
        d_id = x.d.Id,
        e_id = x.e.Id,
        f_id = x.f.Id,
    })

    .ToList();

ついでに、検索条件が指定されていたら絞り込むようにWhere句を追加。
これもよく使うので。。。

発行されたSQL

exec sp_executesql 
N'SELECT [a].[id] AS [a_id], [b].[id] AS [b_id], [c].[id] AS [c_id], [t].[id] AS [d_id], [t0].[id] AS [e_id], [t1].[id] AS [f_id]
FROM [TableA] AS [a]
INNER JOIN [TableB] AS [b] ON [a].[id] = [b].[a_id]
INNER JOIN [TableC] AS [c] ON [b].[id] = [c].[b_id]
LEFT JOIN (
    SELECT [x].*
    FROM [TableD] AS [x]
) AS [t] ON [a].[key] = [t].[key]
LEFT JOIN (
    SELECT [x0].*
    FROM [TableE] AS [x0]
) AS [t0] ON [b].[key] = [t0].[key]
LEFT JOIN (
    SELECT [x1].*
    FROM [TableF] AS [x1]
) AS [t1] ON [c].[key] = [t1].[key]
WHERE (@__key1_0 IS NULL OR (@__key1_0 IS NOT NULL AND (@__key1_1 = [a].[key1])))
 AND (@__key2_2 IS NULL OR (@__key2_2 IS NOT NULL AND (@__key2_3 = [a].[key2])))'
,N'@__key1_0 nvarchar(4000),@__key1_1 nvarchar(20),@__key2_2 nvarchar(4000),@__key2_3 nvarchar(15)'
,@__key1_0=N'AAAAAAAA',@__key1_1=N'AAAAAAAA',@__key2_2=N'BBBBBBBB',@__key2_3=N'BBBBBBBB'

実際に発行されたSQL。
見慣れた形式になっている。
(メソッド構文の書き方によってはとんでもなく無駄に長いSQLになるので。。。)

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?