Entity Framework Core でテーブルのGROUP JOIN (テーブル結合)をするコードを紹介します。
概要
Entity Framework Core ではオブジェクト階層の構造でレコードを結合するGroupJoinの機能があります。
一般的なリレーショナルデータベースには無い機能のため、対応する SQL文はありません。
この記事では、GroupJoinを利用するとどのような動作になるか、どのようなデータ構造が取得できるかを紹介します。
書式
メソッド形式
[結合元DbSetオブジェクト].GroupJoin([結合先DbSetオブジェクト], [結合元レコード一時名] => [結合元レコード結合フィールド],
[結合先レコード一時名] => [結合元レコード結合フィールド],
([結合元レコード一時名], [結合先レコード一時名]) => new { [結合元レコード一時名], [結合先レコード一時名] });
クエリ形式
from [結合元レコード一時名] in [結合元DbSetオブジェクト]
join [結合先レコード一時名] in [結合先DbSetオブジェクト] on [結合元レコード結合フィールド] equals [結合先レコード結合フィールド]
into [結果レコード一時名] select new { [結合元レコード一時名], [結果レコード一時名] };
プログラム例:メソッド形式
テーブル
以下のテーブルを作成します。
hst_level1 id | label | value |
1 | sound | 音楽 |
2 | image | 画像 |
3 | movie | 動画 |
hst_level2 id | parent | label | value |
1 | 1 | wav | wave-sound |
2 | 1 | mp3 | mp3-sound |
3 | 1 | flac | flac-sound |
4 | 2 | jpeg | jpeg-image |
5 | 2 | png | png-image |
6 | 3 | mp4 | mp4-movie |
7 | 3 | wmv | wmv-movie |
UI
下図のフォームを作成します。ボタンと複数行のテキストボックスを配置します。
コード
以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using EntityFrameworkCoreJoin.Models;
namespace EntityFrameworkCoreJoin
{
public partial class FormGroupJoin : Form
{
public FormGroupJoin()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
IQueryable<dynamic> rec;
IPentecSandBoxContext sc = new IPentecSandBoxContext();
rec = sc.HstLevel1s.GroupJoin(sc.HstLevel2s, hst1 => hst1.Id, hst2 => hst2.Parent, (hst1, hst2) => new { hst1, hst2 });
foreach (var r in rec) {
foreach (var r2 in r.hst2) {
textBox1.Text += string.Format("{0}:{1} - {2}:{3}\r\n", r.hst1.Label.Trim(), r.hst1.Value.Trim(), r2.Label.Trim(), r2.Value.Trim());
}
}
}
}
}
解説
HstLevel1 テーブルをHstLevel2とGroupJoinします。HstLevel1 テーブルのidフィールドと、HstLevel2テーブルのParentフィールドを結合します。
結果は、HstLevel1 テーブルのHstLevel1型とHstLevel2 テーブルのHstLevel2型の2つのオブジェクトを持つ匿名オブジェクトで返します。
rec = sc.HstLevel1s.GroupJoin(sc.HstLevel2s, hst1 => hst1.Id, hst2 => hst2.Parent, (hst1, hst2) => new { hst1, hst2 });
foreachループで、結果の
IQueryable<dynamic> rec
オブジェクトの内容をテキストボックスに表示します。
foreach (var r in rec) {
foreach (var r2 in r.hst2) {
textBox1.Text += string.Format("{0}:{1} - {2}:{3}\r\n", r.hst1.Label.Trim(), r.hst1.Value.Trim(), r2.Label.Trim(), r2.Value.Trim());
}
}
今回のテーブルの場合、GroupJoinの結果は以下となります。
Join元のレコード1オブジェクトに対して結合先のレコードが配列状に格納されます。
rec | rec.hst1.Label | rec.hst1.Value | rec.hst2[0].Label | rec.hst2[0].Value | rec.hst2[1].Label | rec.hst2[1].Value | rec.hst2[2].Label | rec.hst2[2].Value |
[0] | sound | 音楽 | wav | wave-sound | mp3 | mp3-sound | flac | flac-sound |
[1] | image | 画像 | jpeg | jpeg-image | png | png-image | | |
[2] | movie | 動画 | mp4 | mp4-movie | wmv | wmv-movie | | |
オブジェクト内にオブジェクトの配列でJoin先のレコードの値が格納されるため、foreachのネスト文で値をテキストボックスに表示します。
実行結果
プロジェクトを実行します。下図のウィンドウが表示されます。
[button1]をクリックします。テキストボックスに下図の結果が表示されます。
sound:音楽 - wav:wave-sound
sound:音楽 - mp3:mp3-sound
sound:音楽 - flac:flac-sound
image:画像 - jpeg:jpeg-image
image:画像 - png:png-image
movie:動画 - mp4:mp4-movie
movie:動画 - wmv:wmv-movie
![](https://resources.ipentec.com/document/image/csharp-entity-framework-core-group-join-table-04)
サーバー側では以下のSQL文が実行されます。
SELECT [h].[id], [h].[label], [h].[value], [t].[id], [t].[label], [t].[parent], [t].[value]
FROM [hst_level1] AS [h]
OUTER APPLY (
SELECT [h0].[id], [h0].[label], [h0].[parent], [h0].[value]
FROM [hst_level2] AS [h0]
WHERE [h].[id] = [h0].[parent]
) AS [t]
ORDER BY [h].[id]
プログラム例:クエリ形式
UI
下図のフォームを作成します。ボタンと複数行のテキストボックスを配置します。
コード
以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using EntityFrameworkCoreJoin.Models;
namespace EntityFrameworkCoreJoin
{
public partial class FormGroupJoin : Form
{
public FormGroupJoin()
{
InitializeComponent();
}
private void button2_Click(object sender, EventArgs e)
{
IQueryable<dynamic> rec;
IPentecSandBoxContext sc = new IPentecSandBoxContext();
rec = from hst1 in sc.HstLevel1s join hst2 in sc.HstLevel2s on hst1.Id equals hst2.Parent into groupjoin select new { hst1, groupjoin };
foreach (var r in rec) {
foreach (var r2 in r.groupjoin) {
textBox1.Text += string.Format("{0}:{1} - {2}:{3}\r\n", r.hst1.Label.Trim(), r.hst1.Value.Trim(), r2.Label.Trim(), r2.Value.Trim());
}
}
}
}
}
解説
先のコードと同じ処理です。LINQの記述をメソッド構文ではなく、クエリ構文で記述しています。クエリ構文で記述する場合には
join
の記述の後ろに
into
句を記述します。
rec = from hst1 in sc.HstLevel1s join hst2 in sc.HstLevel2s on hst1.Id equals hst2.Parent into groupjoin select new { hst1, groupjoin };
実行結果
プロジェクトを実行します。下図のウィンドウが表示されます。
[button2]をクリックします。先のコードと同じ結果が表示され、GroupJoinが実行できていることが確認できます。
プログラム例:匿名型を利用しないコード
UI
下図のフォームを作成します。
コード
以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using EntityFrameworkCoreJoin.Models;
namespace EntityFrameworkCoreJoin
{
public partial class FormGroupJoin : Form
{
public class QueryResult
{
public HstLevel1 hst1;
public IEnumerable<HstLevel2> hst2;
}
public FormGroupJoin()
{
InitializeComponent();
}
private void button3_Click(object sender, EventArgs e)
{
IQueryable<QueryResult> rec;
IPentecSandBoxContext sc = new IPentecSandBoxContext();
//メソッド構文
rec = sc.HstLevel1s.GroupJoin(sc.HstLevel2s, h1 => h1.Id, h2 => h2.Parent, (h1, h2) => new QueryResult(){ hst1=h1, hst2=h2 });
//クエリ構文
//rec = from h1 in sc.HstLevel1s join h2 in sc.HstLevel2s on h1.Id equals h2.Parent into groupjoin select new QueryResult(){ hst1=h1, hst2=groupjoin };
foreach (QueryResult r in rec) {
foreach (HstLevel2 r2 in r.hst2) {
textBox1.Text += string.Format("{0}:{1} - {2}:{3}\r\n", r.hst1.Label.Trim(), r.hst1.Value.Trim(), r2.Label.Trim(), r2.Value.Trim());
}
}
}
}
}
解説
結果を格納するクラスオブジェクトを宣言します。
1つ目のJOIN元のレコードは、テーブル型のメンバ変数を宣言します。
2つ目のJOIN先のレコードは複数のレコードが代入されるため、IEnumerable型で宣言します。
public class QueryResult
{
public HstLevel1 hst1;
public IEnumerable<HstLevel2> hst2;
}
GroupJoinを実行し、結果を
IQueryable<QueryResult>
オブジェクトに代入します。'
IQueryable<QueryResult> rec;
rec = sc.HstLevel1s.GroupJoin(sc.HstLevel2s, h1 => h1.Id, h2 => h2.Parent, (h1, h2) => new QueryResult(){ hst1=h1, hst2=h2 });
クエリ構文の場合は、以下のコードになります。
IQueryable<QueryResult> rec;
rec = from h1 in sc.HstLevel1s join h2 in sc.HstLevel2s on h1.Id equals h2.Parent into groupjoin select new QueryResult(){ hst1=h1, hst2=groupjoin };
実行結果
プロジェクトを実行します。下図のウィンドウが表示されます。
[button3]をクリックします。先のコードと同じ結果が表示され、GroupJoinが実行できていることが確認できます。
補足:Join先がない場合の動作
hst_level1のテーブルを以下に変更します。
hst_level1 id | label | value |
1 | sound | 音楽 |
2 | image | 画像 |
3 | movie | 動画 |
4 | document | 文章 |
以下のコードに変更します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using EntityFrameworkCoreJoin.Models;
namespace EntityFrameworkCoreJoin
{
public partial class FormGroupJoin : Form
{
public class QueryResult
{
public HstLevel1 hst1;
public IEnumerable<HstLevel2> hst2;
}
public FormGroupJoin()
{
InitializeComponent();
}
private void button3_Click(object sender, EventArgs e)
{
IQueryable<QueryResult> rec;
IPentecSandBoxContext sc = new IPentecSandBoxContext();
rec = sc.HstLevel1s.GroupJoin(sc.HstLevel2s, h1 => h1.Id, h2 => h2.Parent, (h1, h2) => new QueryResult(){ hst1=h1, hst2=h2 });
//rec = from h1 in sc.HstLevel1s join h2 in sc.HstLevel2s on h1.Id equals h2.Parent into groupjoin select new QueryResult(){ hst1=h1, hst2=groupjoin };
foreach (QueryResult r in rec) {
textBox1.Text += string.Format("{0}:{1}\r\n", r.hst1.Label.Trim(), r.hst1.Value.Trim());
foreach (HstLevel2 r2 in r.hst2) {
textBox1.Text += string.Format("{0}:{1} - {2}:{3}\r\n", r.hst1.Label.Trim(), r.hst1.Value.Trim(), r2.Label.Trim(), r2.Value.Trim());
}
}
}
}
}
プロジェクトを実行し[button3]をクリックすると下図の結果が表示されます。
Join先がないレコードもrec変数には代入されます。ただしJoin先が無いため、hst2の長さは0になり、何も代入されていない状態です。
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2023-07-09
作成日: 2023-07-08