⚙️

LINQ to Entities クイック リファレンス

2022/01/01に公開

はじめに

この記事は C# Advent Calendar 2012 の参加記事です。

LINQ to Entities におけるクエリの記述方法は以下に記載があるのですが「SQL ではこうやりたいのに」というときの逆引きリファレンスがなかったので、まとめてみました。

https://docs.microsoft.com/ja-jp/dotnet/framework/data/adonet/ef/language-reference/queries-in-linq-to-entities?WT.mc_id=M365-MVP-5002941

リファレンス

選択

基本は比較演算子がそのまま使えますが、後述するように、一部例外があります。

説明 SQL LINQ to Entities
等しい x = 'y' Where(item => item.x == y) または Where(item => item.x.CompareTo(y) == 0)
等しくない x <> 'y' Where(item => item.x != y) または Where(item => item.x.CompareTo(y) != 0)
以上 x >= 'y' Where(item => item.x >= y) または x.Where(item => item.x.CompareTo(y) >= 0)
以下 x <= 'y' Where(item => item.x <= y) または x.Where(item => item.x.CompareTo(y) <= 0)
で始まる x LIKE 'y%' Where(item => item.x.StartsWith(y))
で終わる x LIKE '%y' Where(item => item.x.EndsWith(y))
に一致する x LIKE '%y%' Where(item => item.x.Contains(y))
を含む x IN ('y','z') Where(item => new [] { y, z }.Contains(x))

文字列の場合、>=<= の演算子に対するオーバーロードがないので、CompareTo メソッドで比較する必要があります。もちろん、数値や日付でも CompareTo メソッドで比較できますが、あまり直観的ではないのでおすすめはしないです。

SQL Server Compact の場合は、StartsWithLIKE ではなく CHARINDEX で変換されるようです。また、EndsWith を使うと例外が発生してしまいます。

結合

ナビゲーション プロパティが非常に便利ですが、設計上の理由で外部キーが使えない場合などは、クエリで結合する必要があります。

説明 SQL LINQ to Entities
内部結合 INNER JOIN Join(y, left => left.z, right => right.z, (left, right) => ...)
外部結合 OUTER JOIN GroupJoin(y, left => left.z, right => right.z, (left, right) => ...).SelectMany(...)

Join メソッドを使う他に、ナビゲーション プロパティを参照することで自動的に内部結合が行われます。つまり、以下のコードはどちらも同じになります。

// Join メソッドを使う場合
context.Employees.Join(
    context.Department,
    employee => employee.DepartmentId,
    department => department.Id,
    (employee, department) => {
        EmployeeName = employee.Name,
        DepartmentName = department.Name
    });
// ナビゲーションプロパティを参照する場合
context.Employees
    .Select(employee => new {
        EmployeeName = employee.Name,
        DepartmentName = employee.Department.Name
    });

上記のコードでは、Departmentnull の場合に Name プロパティを参照していて NullReferenceException が発生してしまいそうに見えます。しかし実際は式ツリーとして解釈されるだけで実際に実行されるわけではないので問題ありません。逆に、単体テストなどでモックして IEnumerable として評価する場合は例外が発生しますので、注意が必要です。

GroupJoin メソッドでは、結合されるエンティティはコレクションになるので、プロパティを参照するにはさらに SelectMany します。

context.Employees
    .GroupJoin(
        context.Departments,
        employee => employee.DepartmentId,
        department => department.Id,
        (employee, department) => new
        {
            Employee = employee,
            Departments = departments.DefaultIfEmpty(),
        })
    .SelectMany((x => x.Departments, (x, y) => new
    {
        EmployeeName = x.Employee.Name,
        DepartmentName = y.Name
    }));

集合

だんだん複雑になってきました。

説明 SQL LINQ to Entities
和集合 x UNION y x.Union(y)
差集合 x EXCEPT y x.Except(y)
積集合 x INTERSECT y x.Intersect(y)
連結 x UNION ALL y x.Concat(y)

Union メソッドは Distinct と同じく重複するデータを排除してしまうため、重複を許容する場合は Concat メソッドを使用する必要があります。これを知らずにずいぶん悩んでしまいました。

Except メソッドは x にあって y にない要素を、Intersect メソッドは xy のいずれにもある要素をそれぞれ抽出します。

ExceptIntersect メソッド (さっきの Join メソッドも) で比較するキーを指定する場合は IEqualityComparer インターフェイスを実装したクラスを作成する必要があります。それはあまりに面倒なので、以下の記事で紹介されているような方法を使うことをおすすめします。

並べ替え

並べ替えとは直接関係ないメソッドも紹介しますが、理由は後述します。

説明 SQL LINQ to Entities
昇順 ORDER BY x OrderBy(item => item.x)
昇順 ORDER BY x DESC OrderByDescending(item => item.x)
最初の n 件 TOP n Take(n)
n 件読み飛ばし ROW_NUMBER() OVER(...) WHERE ROW_NUMBER >= n Skip(n)

Skip メソッドは必ず OrderBy メソッドに続けて指定する必要があります。OrderBy メソッドがない場合、以下のような実行時例外が発生します。

メソッド 'Skip' は、LINQ to Entities では並べ替え済みの入力に対してのみサポートされます。メソッド 'Skip' の前にメソッド 'OrderBy' を呼び出す必要があります。

これは Skip メソッドが SQL では OVER 句 (SQL Server Compact では OFFSET FETCH 句) に置き換えられるためのようです。できればコンパイル時に解決してほしいですが、LINQ to Object との整合性も考えると致し方ないようです。

おわりに

とりあえずこれだけあればだいたいのクエリは書けるはずです。他にも LINQ to Entities はラムダ式に ToString メソッドが書けないとか (CONVERT に置き換えてくれてもいいのに) 他にもいろいろ注意するポイントがあります。万能というわけではないのでうまく SQL と棲み分けしするのがよさそうです。

Discussion