次の方法で共有


複雑なクエリ演算子

言語統合クエリ (LINQ) には、複数のデータ ソースを組み合わせたり、複雑な処理を行ったりする複雑な演算子が多数含まれています。 すべての LINQ 演算子がサーバー側で適切な変換を行っているわけではありません。 場合によっては、1 つのフォームのクエリがサーバーに変換されますが、別の形式で記述した場合、結果が同じであっても翻訳されない場合があります。 このページでは、複雑な演算子とそのサポートされるバリエーションについて説明します。 今後のリリースでは、より多くのパターンを認識し、対応する翻訳を追加する可能性があります。 また、翻訳のサポートはプロバイダーによって異なる点に注意することも重要です。 SQLServer で変換される特定のクエリは、SQLite データベースでは機能しない可能性があります。

ヒント

この記事の サンプル は、GitHub で確認できます。

参加する

LINQ Join 演算子を使用すると、各ソースのキー セレクターに基づいて 2 つのデータ ソースを接続し、キーが一致したときに値のタプルを生成できます。 リレーショナル データベースの INNER JOIN に自然に変換されます。 LINQ 結合には外部キー セレクターと内部キー セレクターが含まれていますが、データベースには 1 つの結合条件が必要です。 そのため、EF Core では、外側のキー セレクターと内部キー セレクターを比較して、等価性を得ることで結合条件が生成されます。

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on photo.PersonPhotoId equals person.PhotoId
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON [p0].[PersonPhotoId] = [p].[PhotoId]

さらに、キー セレクターが匿名型の場合、EF Core によって結合条件が生成され、等値コンポーネントごとの比較が行われます。

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on new { Id = (int?)photo.PersonPhotoId, photo.Caption }
                equals new { Id = person.PhotoId, Caption = "SN" }
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON ([p0].[PersonPhotoId] = [p].[PhotoId] AND ([p0].[Caption] = N'SN'))

グループに参加

LINQ GroupJoin 演算子を使用すると、Join に似た 2 つのデータ ソースを接続できますが、外部要素を照合するための内部値のグループが作成されます。 次の例のようなクエリを実行すると、 Blog & IEnumerable<Post>の結果が生成されます。 データベース (特にリレーショナル データベース) にはクライアント側オブジェクトのコレクションを表す方法がないため、多くの場合、GroupJoin はサーバーに変換しません。 特別なセレクターを使用せずに GroupJoin を実行するには、サーバーからすべてのデータを取得する必要があります (以下の最初のクエリ)。 ただし、セレクターが選択されているデータを制限している場合は、サーバーからすべてのデータをフェッチすると、パフォーマンスの問題が発生する可能性があります (以下の 2 番目のクエリ)。 そのため、EF Core では GroupJoin は翻訳されません。

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, grouping };
var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, Posts = grouping.Where(p => p.Content.Contains("EF")).ToList() };

SelectMany

LINQ SelectMany 演算子を使用すると、各外部要素のコレクション セレクターを列挙し、各データ ソースから値のタプルを生成できます。 ある意味では結合ですが、条件がないため、すべての外部要素がコレクション ソースの要素に接続されます。 コレクション セレクターが外部データ ソースとどのように関連しているかに応じて、SelectMany はサーバー側のさまざまなクエリに変換できます。

コレクション セレクターが外部を参照しない

コレクション セレクターが外部ソースから何も参照していない場合、結果は両方のデータ ソースのデカルト積になります。 リレーショナル データベースの CROSS JOIN に変換されます。

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
CROSS JOIN [Posts] AS [p]

コレクション セレクターが where 句の外側を参照する

コレクション セレクターに、外部要素を参照する where 句がある場合、EF Core はそれをデータベース結合に変換し、結合条件として述語を使用します。 通常、このケースは、外部要素のコレクション ナビゲーションをコレクション セレクターとして使用する場合に発生します。 コレクションが外側の要素に対して空の場合、その外部要素に対する結果は生成されません。 ただし、コレクション セレクターに DefaultIfEmpty が適用されている場合、外側の要素は内部要素の既定値で接続されます。 この違いにより、この種のクエリは、INNER JOINがない場合はDefaultIfEmptyに変換され、LEFT JOINが適用されるときにDefaultIfEmptyされます。

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

コレクションセレクターが、条件指定なしで外部を参照しています。

コレクション セレクターが where 句にない外部要素を参照する場合 (上記の場合)、データベース結合には変換されません。 そのため、各外部要素のコレクション セレクターを評価する必要があります。 これは、多くのリレーショナル データベースの APPLY 操作に変換されます。 コレクションが外側の要素に対して空の場合、その外部要素に対する結果は生成されません。 ただし、コレクション セレクターに DefaultIfEmpty が適用されている場合、外側の要素は内部要素の既定値で接続されます。 この違いにより、この種のクエリは、CROSS APPLYがない場合はDefaultIfEmptyに変換され、OUTER APPLYが適用されるときにDefaultIfEmptyされます。 SQLite などの一部のデータベースでは APPLY 演算子がサポートされていないため、この種のクエリは変換されない可能性があります。

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
CROSS APPLY [Posts] AS [p]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
OUTER APPLY [Posts] AS [p]

グループ化

LINQ GroupBy 演算子は、IGrouping<TKey, TElement>型の結果を作成します。この結果は、TKeyTElementが任意の型になる可能性があります。 さらに、 IGroupingIEnumerable<TElement>を実装します。つまり、グループ化の後に任意の LINQ 演算子を使用して構成できます。 IGroupingを表すことができるデータベース構造がないため、ほとんどの場合、GroupBy 演算子に変換はありません。 スカラーを返す各グループに集計演算子を適用すると、リレーショナル データベースの SQL GROUP BY に変換できます。 SQL GROUP BY も制限されています。 スカラー値によってのみグループ化する必要があります。 プロジェクションには、グループ化キー列または列に対して適用される集計を含めることのみが可能です。 EF Core はこのパターンを識別し、次の例のようにサーバーに変換します。

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]

EF Core では、グループ化の集計演算子が Where または OrderBy (またはその他の順序付け) LINQ 演算子に表示されるクエリも変換されます。 where 句には SQL の HAVING 句を使用します。 GroupBy 演算子を適用する前のクエリの一部は、サーバーに変換できる限り、任意の複雑なクエリにすることができます。 さらに、グループ化クエリに集計演算子を適用して結果のソースからグループ化を削除すると、他のクエリと同様に、その上に作成できます。

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            where g.Count() > 0
            orderby g.Key
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId]

EF Core でサポートされる集計演算子は次のとおりです。

。網 SQL
平均(x => x.Property) AVG(プロパティ)
Count() COUNT(*)
LongCount() COUNT(*)
Max(x => x.Property) MAX(プロパティ)
Min(x => x.Property) MIN(プロパティ)
Sum(x => x.プロパティ) 合計(プロパティ)

追加の集計演算子がサポートされる場合があります。 その他の関数マッピングについては、プロバイダーのドキュメントを参照してください。

IGroupingを表すデータベース構造がない場合でも、EF Core 7.0 以降では、結果がデータベースから返された後にグループ化を作成できる場合があります。 これは、関連するコレクションを含める場合の Include 演算子の動作に似ています。 次の LINQ クエリでは、GroupBy 演算子を使用して、結果を Price プロパティの値でグループ化します。

var query = context.Books.GroupBy(s => s.Price);
SELECT [b].[Price], [b].[Id], [b].[AuthorId]
FROM [Books] AS [b]
ORDER BY [b].[Price]

この場合、GroupBy 演算子は SQL の GROUP BY 句に直接変換するのではなく、サーバーから結果が返された後に EF Core によってグループ化が作成されます。

左結合

左結合は LINQ 演算子ではありませんが、リレーショナル データベースには、クエリでよく使用される左結合の概念があります。 LINQ クエリの特定のパターンでは、サーバー上の LEFT JOIN と同じ結果が得られます。 EF Core は、このようなパターンを識別し、サーバー側で同等の LEFT JOIN を生成します。 このパターンでは、両方のデータ ソース間に GroupJoin を作成し、グループ化ソースで SelectMany 演算子と DefaultIfEmpty を使用してグループ化をフラット化し、内部に関連する要素がない場合に null と一致させる必要があります。 次の例は、そのパターンがどのように表示され、どのようなパターンが生成されるかを示しています。

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            from p in grouping.DefaultIfEmpty()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

上記のパターンでは、式ツリーに複雑な構造が作成されます。 そのため、EF Core では、演算子の直後の手順で GroupJoin 演算子のグループ化結果をフラット化する必要があります。 GroupJoin-DefaultIfEmpty-SelectMany が使用されているが、パターンが異なる場合でも、左結合として識別されない場合があります。