Skip to content

MS_LINQ

nishi_74322014 edited this page Aug 21, 2026 · 2 revisions

LINQ

概要

統合言語クエリ (LINQ: Language-Integrated Query)

補足(LINQ を支える言語機能): LINQ は単体の機能ではなく、
C# 3.0 で追加された以下の言語機能の組み合わせで成り立っている。

機能 役割
拡張メソッド IEnumerable<T>Where / Select 等を後付けする
ラムダ式 述語や射影を簡潔に書く(ラムダ式って
匿名型 射影結果の型を都度定義せずに済ませる
型推論(var 匿名型を受け取る
式ツリー ラムダをデータとして保持し、SQL 等へ翻訳する(式木(Expression Tree)

このうち式ツリーが、LINQ to Objects と LINQ to Entities を
分ける決定的な要素である(後述)。

構文

クエリは、

  • クエリ式の構文
  • メソッド ベースのクエリ構文

という 2 とおりの構文を使って作成できる。

クエリ式の構文

SQL っぽい書き方。

メソッド ベースのクエリ構文

Fluent API(≒ メソッド・チェーン)的に書く書き方。

補足(両者は等価): クエリ式はコンパイル時に
メソッド ベースの構文へ変換される(構文糖衣)。
ただし、クエリ式で書けるのは一部の演算子のみで、
Count / Sum / First / Skip / Take などは
メソッド構文でしか書けない。
このため、実務ではメソッド ベースが主流である。

LINQ to Objects

https://learn.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/linq/linq-to-objects

匿名型配列 + クエリ式の構文例

匿名型配列

var AnonymousType = new[]{
   new { 年度=2013, 種別=14, 生年度=1979,平均人員=200,支給年金額=120000},
   new { 年度=2012, 種別=15, 生年度=1980,平均人員=120,支給年金額=65000},
   new { 年度=2012, 種別=21, 生年度=1981,平均人員=25,支給年金額=23000},
   new { 年度=2010, 種別=43, 生年度=1982,平均人員=12,支給年金額=12000},
   new { 年度=2012, 種別=21, 生年度=1981,平均人員=25,支給年金額=23000}
};

クエリ式の構文例

Console.WriteLine("匿名型配列 + クエリ式の構文例");
Console.WriteLine("--------------------------------------------------");

var query1 = from p in AnonymousType
          where p.年度 == 2012
          select new {
   p.年度,
   p.種別,
   p.生年度,
   平均人員2 = p.平均人員 / 12,
   p.支給年金額
          };

foreach (var item in query1)
{
    Console.WriteLine("年度={0}, 種別={1}, 生年度={2}, 平均人員2={3}, 支給年金額={4}",
        item.年度, item.種別, item.生年度, item.平均人員2, item.支給年金額);
}

クラス(POCO) + クエリ式の構文例

クラス(POCO)

class BeanClass
{
    public int 年度;
    public int 種別;
    public int 生年度;
    public int 平均人員2;
    public int 支給年金額;
}

クエリ式の構文例

Console.WriteLine("匿名型配列 + クエリ式の構文例(クラス指定)");
Console.WriteLine("--------------------------------------------------");

var query2 = from p in AnonymousType
  where p.年度 == 2012
  select new BeanClass
  {
      年度 = p.年度,
      種別 = p.種別,
      生年度 = p.生年度,
      平均人員2 = p.平均人員 / 12,
      支給年金額 = p.支給年金額
  };

foreach (var item in query2)
{
    Console.WriteLine("年度={0}, 種別={1}, 生年度={2}, 平均人員2={3}, 支給年金額={4}",
        item.年度, item.種別, item.生年度, item.平均人員2, item.支給年金額);
}

Console.WriteLine("--------------------------------------------------");
Console.ReadKey();

メソッド ベースのクエリ構文例 + GroupBy

Console.WriteLine("匿名型配列 + メソッド ベースのクエリ構文例 + GroupBy");
Console.WriteLine("--------------------------------------------------");

// 参考
var query8 = AnonymousType
    .Where(p => p.年度 == 2012)
    .GroupBy(p => new { p.年度, p.種別 })
    .Select(p => new
    {
        年度 = p.Key.年度,
        種別 = p.Key.種別,
        平均人員2合計 = p.Sum(x => x.平均人員) / 12,
        支給年金額合計 = p.Sum(x => x.支給年金額)
    });

foreach (var item in query8)
{
    Console.WriteLine("年度={0}, 種別={1}, 平均人員2合計={2}, 支給年金額合計={3}",
        item.年度, item.種別, item.平均人員2合計, item.支給年金額合計);
}

インデクサList + クエリ式の構文例

インデクサList

List<DataRow> RowList = new List<DataRow>();

DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("年度", typeof(int)));
dt.Columns.Add(new DataColumn("種別", typeof(int)));
dt.Columns.Add(new DataColumn("生年度", typeof(int)));
dt.Columns.Add(new DataColumn("平均人員", typeof(int)));
dt.Columns.Add(new DataColumn("支給年金額", typeof(int)));

dr = dt.NewRow();

dr["年度"] = 2013;
dr["種別"] = 14;
dr["生年度"] = 1979;
dr["平均人員"] = 200;
dr["支給年金額"] = 120000;

RowList.Add(dr);

dr = dt.NewRow();

dr["年度"] = 2012;
dr["種別"] = 15;
dr["生年度"] = 1980;
dr["平均人員"] = 120;
dr["支給年金額"] = 65000;

RowList.Add(dr);

// ...(以降、同様に行を追加)

移行メモ(体裁): 元ページはこの後も同じ形の行追加が
3 件続いていたが、内容が反復であるため省略した
(データは前掲の匿名型配列と同一である)。
また、dr の宣言(DataRow dr = null;)が
このコード片には含まれていなかったため、
後述の「LINQ to DataSet」の例を参照のこと。

クエリ式の構文例

Console.WriteLine("List<DataRow> + クエリ式の構文例");
Console.WriteLine("--------------------------------------------------");

var query4 = from p in RowList
    where p.Field<int>("年度") == 2012
    select new
    {
        年度 = p.Field<int>("年度"),
        種別 = p.Field<int>("種別"),
        生年度 = p.Field<int>("生年度"),
        平均人員2 = p.Field<int>("平均人員") / 12,
        支給年金額 = p.Field<int>("支給年金額")
    };

foreach (var item in query4)
{
    Console.WriteLine("年度={0}, 種別={1}, 生年度={2}, 平均人員2={3}, 支給年金額={4}",
        item.年度, item.種別, item.生年度, item.平均人員2, item.支給年金額);
}

Console.WriteLine("--------------------------------------------------");
Console.ReadKey();

メソッド ベースのクエリ構文例

Console.WriteLine("List<DataRow> + メソッド ベースのクエリ構文例");
Console.WriteLine("--------------------------------------------------");

var query5 = RowList
    .Where(p => p.Field<int>("年度") == 2012)
    .Select(p => new
{
    年度 = p.Field<int>("年度"),
    種別 = p.Field<int>("種別"),
    生年度 = p.Field<int>("生年度"),
    平均人員2 = p.Field<int>("平均人員") / 12,
    支給年金額 = p.Field<int>("支給年金額")
});

foreach (var item in query5)
{
    Console.WriteLine("年度={0}, 種別={1}, 生年度={2}, 平均人員2={3}, 支給年金額={4}",
        item.年度, item.種別, item.生年度, item.平均人員2, item.支給年金額);
}

Console.WriteLine("--------------------------------------------------");
Console.ReadKey();

メソッド ベースのクエリ構文例 + GroupBy

Console.WriteLine("List<DataRow> + メソッド ベースのクエリ構文例 + GroupBy");
Console.WriteLine("--------------------------------------------------");

// WhereとGroupByの両方を記述できない。
var query6 = RowList
    .Where(p => p.Field<int>("年度") == 2012)
    .Select(p => new
{
    年度 = p.Field<int>("年度"),
    種別 = p.Field<int>("種別"),
    生年度 = p.Field<int>("生年度"),
    平均人員2 = p.Field<int>("平均人員") / 12,
    支給年金額 = p.Field<int>("支給年金額")
});

var query7 = query6
    .GroupBy(p => new { p.年度, p.種別 })
    .Select(p => new
{
    年度 = p.Key.年度,
    種別 = p.Key.種別,
    平均人員2合計 = p.Sum(x => x.平均人員2),
    支給年金額合計 = p.Sum(x => x.支給年金額)
});

foreach (var item in query7)
{
    Console.WriteLine("年度={0}, 平均人員2合計={1}, 支給年金額合計={2}",
        item.年度, item.平均人員2合計, item.支給年金額合計);
}

Console.WriteLine("--------------------------------------------------");
Console.ReadKey();

移行メモ(コメントの補足): 「WhereGroupBy の両方を記述できない」の
意図は「1 つの式にまとめて書けなかったので 2 段に分けた」ということだが、
技術的には 1 つのチェーンで書ける
(前掲の匿名型配列の例が実際にそう書けている)。

ここで 2 段に分かれているのは、DataRow に対する
p.Field<int>("...") の呼び出しを GroupBy のキーに直接書けないため、
先に Select で匿名型へ射影する必要があった、というのが実情である。

// 1 つのチェーンでも書ける
var q = RowList
    .Where(p => p.Field<int>("年度") == 2012)
    .GroupBy(p => new { 年度 = p.Field<int>("年度"),
                        種別 = p.Field<int>("種別") })
    .Select(g => new
    {
        g.Key.年度,
        g.Key.種別,
        平均人員2合計 = g.Sum(x => x.Field<int>("平均人員") / 12),
        支給年金額合計 = g.Sum(x => x.Field<int>("支給年金額")),
    });

データアクセス

LINQ to SQL

https://learn.microsoft.com/ja-jp/dotnet/framework/data/adonet/sql/linq/

  • LINQ と呼ばれるクエリで DB アクセスを行う手法。
  • LINQ で書かれたクエリは最適化された SQL に変換され発行される。
  • LINQ to SQL で実行可能な SQL は、SELECT のみである。

2008 年 11 月に LINQ to SQL は discon となり(ADO.NET Entity Framework と融合し)、
以降は ADO.NET Entity Frameworkの LINQ to Entities として利用することになる。

移行メモ(補足): 「実行可能な SQL は SELECT のみ」は
LINQ 式から生成される SQL の話であり、
LINQ to SQL 自体は SubmitChanges() による
Insert / Update / Delete をサポートしている
(これは Entity Framework
SaveChanges() と同じ発想)。

補足(最新化): LINQ to SQL は .NET Framework にのみ存在し、
.NET Core 以降には移植されていない
現在の LINQ ベースのデータアクセスは
**EF Core**である。

LINQ to Entities

Entity Framework

補足(IQueryableIEnumerable の境界): LINQ で最も重要な区別。

LINQ to Objects LINQ to Entities
対象 IEnumerable<T> IQueryable<T>
ラムダの扱い デリゲート(即実行可能なコード) 式ツリー(解析可能なデータ)
実行場所 メモリ上(アプリ側) DB 側(SQL に翻訳)
使える式 C# の任意のコード プロバイダが翻訳できる式のみ

ToList() / ToArray() / AsEnumerable() を挟むと
IQueryableIEnumerable に切り替わり、
以降の絞り込みはメモリ上で行われる

// NG: Users を全件フェッチしてからメモリ上で Where
var bad = context.Users.ToList().Where(u => u.DeptId == 1);

// OK: WHERE 句として SQL に翻訳される
var good = await context.Users.Where(u => u.DeptId == 1).ToListAsync();

また、IQueryable ではプロバイダが翻訳できない式を書くと実行時に落ちる
(EF Core 3.0 以降、暗黙のクライアント評価は廃止された)。

参考

その他

LINQ to DataSet

https://learn.microsoft.com/ja-jp/dotnet/framework/data/adonet/linq-to-dataset

DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("年度", typeof(int)));
dt.Columns.Add(new DataColumn("種別", typeof(int)));
dt.Columns.Add(new DataColumn("生年度", typeof(int)));
dt.Columns.Add(new DataColumn("平均人員", typeof(int)));
dt.Columns.Add(new DataColumn("支給年金額", typeof(int)));

DataRow dr = null;

dr = dt.NewRow();

dr["年度"] =2013;
dr["種別"] =14;
dr["生年度"] =1979;
dr["平均人員"] =200;
dr["支給年金額"] =120000;

dt.Rows.Add(dr);

// ...(以降、同様に行を追加)

// --------------------------------------------------

Console.WriteLine("DataTable + クエリ式の構文例");
Console.WriteLine("--------------------------------------------------");

var query3 = from p in dt.AsEnumerable()
    where p.Field<int>("年度") == 2012
    select new
    {
        年度 = p.Field<int>("年度"),
        種別 = p.Field<int>("種別"),
        生年度 = p.Field<int>("生年度"),
        平均人員2 = p.Field<int>("平均人員") / 12,
        支給年金額 = p.Field<int>("支給年金額")
    };

foreach (var item in query3)
{
    Console.WriteLine("年度={0}, 種別={1}, 生年度={2}, 平均人員2={3}, 支給年金額={4}",
        item.年度, item.種別, item.生年度, item.平均人員2, item.支給年金額);
}

Console.WriteLine("--------------------------------------------------");
Console.ReadKey();

補足: dt.AsEnumerable()Field<T>()
System.Data.DataSetExtensions アセンブリの拡張メソッドである。
なお、AsEnumerable() を呼んだ時点で
**LINQ to Objects(メモリ上の処理)**になっている点に注意。
DB 側で絞り込みたい場合は、DataTable に読み込む前の
SQL 側で WHERE を書く必要がある。

LINQ to Dynamics CRM

補足(LINQ の外部結合): LINQ には LEFT OUTER JOIN に相当する
演算子が無く、GroupJoin + DefaultIfEmpty() で表現する。
これを翻訳できるかどうかがプロバイダによって異なる、というのが
本節の趣旨である。

var q = from o in orders
        join c in customers on o.CustomerId equals c.Id into g
        from c in g.DefaultIfEmpty()
        select new { o.Id, CustomerName = c == null ? null : c.Name };

EF Core では上記が LEFT JOIN に翻訳される。

参考

内部

Microsoft Learn

構文例

  • クエリ式の構文例 射影 (LINQ to DataSet)
  • クエリ式の構文例 射影 (LINQ to Entities)
  • メソッド ベースのクエリ構文例 射影 (LINQ to DataSet)
  • メソッド ベースのクエリ構文例 射影 (LINQ to Entities)

その他

LINQ to Object

パフォーマンス関連

補足(最新化:性能面): LINQ to Objects は
デリゲート呼び出しとイテレータのオーバーヘッドがあるため、
ホット パスでは for / foreach の直書きより遅い
ただし、

  • .NET 7 / .NET 9 で LINQ の実装自体が大幅に最適化されている
    Span ベースの高速パス、Count の短絡評価など)
  • 大半のコードでは可読性の利得のほうが大きい

ため、「LINQ だから避ける」ではなく
測ってから判断するのが正しい。
数百万回まわる処理や割り当てを避けたい場面でのみ手書きに落とす。

添付ファイル


Tags: 移行, .NET開発, データアクセス, ADO.NET, Entity Framework

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally