-
Notifications
You must be signed in to change notification settings - Fork 0
MS_LINQ
- 戻る(.NET開発、データアクセスのいろいろ)
- LINQ
- Entity Framework / Dapper
統合言語クエリ (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などは
メソッド構文でしか書けない。
このため、実務ではメソッド ベースが主流である。
https://learn.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/linq/linq-to-objects
- LINQ to Object で抽出と並び替え(配列編) - どっとねっとふぁん
https://blogonos.wordpress.com/2007/09/06/linq-to-object%E3%81%A7%E6%8A%BD%E5%87%BA%E3%81%A8%E4%B8%A6%E3%81%B3%E6%9B%BF%E3%81%88%EF%BC%88%E9%85%8D%E5%88%97%E7%B7%A8%EF%BC%89/
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.支給年金額);
}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();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<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();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();移行メモ(コメントの補足): 「
WhereとGroupByの両方を記述できない」の
意図は「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>("支給年金額")), });
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**である。
補足(
IQueryableとIEnumerableの境界): LINQ で最も重要な区別。
LINQ to Objects LINQ to Entities 対象 IEnumerable<T>IQueryable<T>ラムダの扱い デリゲート(即実行可能なコード) 式ツリー(解析可能なデータ) 実行場所 メモリ上(アプリ側) DB 側(SQL に翻訳) 使える式 C# の任意のコード プロバイダが翻訳できる式のみ
ToList()/ToArray()/AsEnumerable()を挟むと
IQueryable→IEnumerableに切り替わり、
以降の絞り込みはメモリ上で行われる。// 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 SQL って死滅しちゃうの - ものがたり
http://d.hatena.ne.jp/atsushieno/20080609/p1 -
Linq to SQL が死滅するようです。って当然じゃん。 - ものがたり
http://d.hatena.ne.jp/atsushieno/20081103/p1-
Update on LINQ to SQL and LINQ to Entities Roadmap
ADO.NET Blog
https://learn.microsoft.com/en-us/archive/blogs/adonet/ -
Is LINQ to SQL Truly Dead
http://www.infoq.com/news/2008/11/DLINQ-Future
-
-
LINQ to SQL は本当に終わりなのか?
http://www.infoq.com/jp/news/2008/11/DLINQ-Future
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 xxx)のサポートする処理が異なるためです。
- 例えば、他のプロバイダ(LINQ to xxx)では OUTER JOIN も実装可能です。
-
参考
- C# LINQ to SQL で LEFT OUTER JOIN をシンプル記述する
http://programmers.high-way.info/cs/linqtosql-join.html - Use LINQ to Construct a Query
https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/org-service/use-linq-construct-query- join
Represents an inner join. You cannot perform outer joins.
- join
- C# LINQ to SQL で LEFT OUTER JOIN をシンプル記述する
補足(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に翻訳される。
- LINQ to Objects
https://learn.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/linq/linq-to-objects - LINQ to DataSet
https://learn.microsoft.com/ja-jp/dotnet/framework/data/adonet/linq-to-dataset - LINQ to Entities
https://learn.microsoft.com/ja-jp/dotnet/framework/data/adonet/ef/language-reference/linq-to-entities - LINQ to SQL
https://learn.microsoft.com/ja-jp/dotnet/framework/data/adonet/sql/linq/
- クエリ式の構文例 射影 (LINQ to DataSet)
- クエリ式の構文例 射影 (LINQ to Entities)
- メソッド ベースのクエリ構文例 射影 (LINQ to DataSet)
- メソッド ベースのクエリ構文例 射影 (LINQ to Entities)
- LINQ (C# によるプログラミング入門)
http://ufcpp.net/study/csharp/sp3_linq.html
- MTG Blog ◆LINQ で射影(Select)
http://madogiwatosan.blogspot.jp/2011/04/linqselect.html - LINQ to Objects で グループ化 - 当面 C# と .NET な記録
http://d.hatena.ne.jp/siokoshou/20070726/p1 - Linq 入門記 -27 (LINQ To Object, GroupBy) - いろいろ備忘録日記
http://d.hatena.ne.jp/gsf_zero1/20100819/p1 - .net - LinQ to objects GroupBy() by object and Sum() by amount - Stack Overflow
http://stackoverflow.com/questions/2551086/linq-to-objects-groupby-by-object-and-sum-by-amount
- LINQ と for 文のコスト検証 - 渋谷ほととぎす通信
http://www.shibuya24.info/entry/2017/04/20/090000 - 半端に LINQ を使う悪手の代表例 - C#+WPF チューニング戦記
http://proprogrammer.hatenadiary.jp/entry/2014/08/17/022958 - neue cc - LINQ の Where や Select 連打のパフォーマンス最適化について
http://neue.cc/2012/03/08_367.html - C# で Linq を使うより Python の方が 2 倍速かったのでベンチマークをしてみた - Qiita
https://qiita.com/yniji/items/6585011633289a257888
補足(最新化:性能面): LINQ to Objects は
デリゲート呼び出しとイテレータのオーバーヘッドがあるため、
ホット パスではfor/foreachの直書きより遅い。
ただし、
- .NET 7 / .NET 9 で LINQ の実装自体が大幅に最適化されている
(Spanベースの高速パス、Countの短絡評価など)- 大半のコードでは可読性の利得のほうが大きい
ため、「LINQ だから避ける」ではなく
測ってから判断するのが正しい。
数百万回まわる処理や割り当てを避けたい場面でのみ手書きに落とす。
- LINQtoObject.zip(本ページのサンプル一式)
Tags: 移行, .NET開発, データアクセス, ADO.NET, Entity Framework
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。