2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【.NET 10 Preview】EF CoreにLEFT JOIN、RIGHT JOINが追加されるらしい

Posted at

何があった?

.NET 10(EF Core 10)では、LINQにLeftJoinメソッド、RightJoinメソッドが新しく追加され、LEFT JOIN(左外部結合)、RIGHT JOIN(右外部結合)をより直感的・簡潔に書けるようになりました。

LeftJoin は .NET 10 SDK において、System.Linq 名前空間内に追加されたLINQの拡張メソッドであり、既存のLINQクエリ式の構文を拡張して、左外部結合(LEFT JOIN)を簡潔に表現可能にするものです。

以下では、LeftJoinメソッドについて解説します。

.NET 10 以前の書き方

.NET 10以前では、LEFT JOINを表現するには、以下のように、GroupJoinSelectManyDefaultIfEmpty を組み合わせる必要がありました。
コードが長くて読みにくい。

var query = students
    .GroupJoin(
        departments,
        student => student.DepartmentID,
        department => department.ID,
        (student, departmentList) => new { student, subgroup = departmentList })
    .SelectMany(
        joinedSet => joinedSet.subgroup.DefaultIfEmpty(),
        (student, department) => new
        {
            student.student.FirstName,
            student.student.LastName,
            Department = department.Name ?? "[NONE]"
        });

.NET 10 ではどうなる?

.NET 10 からは、以下のように LeftJoin を使って書けます。シンプルで読みやすい!

var query = students
    .LeftJoin(
        departments,
        student => student.DepartmentID,
        department => department.ID,
        (student, department) => new 
        { 
            student.FirstName,
            student.LastName,
            Department = department.Name ?? "[NONE]"
        });

LeftJoinメソッドのシグネチャ

public static IEnumerable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer,
    IEnumerable<TInner> inner,
    Func<TOuter, TKey> outerKeySelector,
    Func<TInner, TKey> innerKeySelector,
    Func<TOuter, TInner?, TResult> resultSelector,
    IEqualityComparer<TKey>? comparer = null)

SQL変換について

上記のコードをEF Core(v10.0 以降)を通して実行すると、生成されるSQLは次のようになります。

SELECT s.FirstName, s.LastName, 
       COALESCE(d.Name, '[NONE]') AS Department
FROM Students AS s
LEFT JOIN Departments AS d ON s.DepartmentID = d.ID

LINQがそのまま自然にSQLにマッピングされるので、効率的かつ直感的なコードで実行可能なSQLを構築できます。シンプル故にミスが紛れ込みにくくなるのは良いですね。

パフォーマンス面は?

.GroupJoin(...).SelectMany(...DefaultIfEmpty())を使ったパターンでも、
.LeftJoin(...)を使ったパターンでも、正しく書けていれば生成されるSQLは同じなので、パフォーマンスに差はありません。

ただし、GroupJoinを使った書き方は構造が複雑なため、記述ミスが入りやすく、意図しないSQLが生成されてしまうリスクが高まります。
その結果、パフォーマンスが低下する可能性も高くなります。

例1. DefaultIfEmpty()のつけ忘れ
var query = context.Students
    .GroupJoin(
        context.Departments,
        student => student.DepartmentID,
        department => department.ID,
        (student, departments) => new { student, departments })
    .SelectMany(
        x => x.departments, // ← `DefaultIfEmpty()` をつけ忘れている!
        (x, department) => new
        {
            x.student.FirstName,
            Department = department.Name
        });

問題

  • LEFT JOIN ではなく INNER JOIN 相当のクエリになる
  • 部署がない学生は結果から除外される(ビジネスロジック的にもNG)
SELECT s.FirstName, d.Name
FROM Students AS s
JOIN Departments AS d ON s.DepartmentID = d.ID
例2. 匿名型を深くネストしてしまう
var query = context.Students
    .GroupJoin(
        context.Departments,
        student => student.DepartmentID,
        department => department.ID,
        (student, departments) => new 
        {
            Nested = new { Student = student, Departments = departments }
        })
    .SelectMany(
        x => x.Nested.Departments.DefaultIfEmpty(),
        (x, department) => new
        {
            x.Nested.Student.FirstName,
            Department = department != null ? department.Name : "[NONE]"
        });

問題

  • EF Coreがうまく最適化できず、OUTER APPLY を含む複雑で遅いSQLに変換されることがある
SELECT ...
FROM Students AS s
OUTER APPLY (
    SELECT ...
    FROM Departments AS d
    WHERE s.DepartmentID = d.ID
) AS d

参考

2
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?