[解決済み] Dapper.Netで一対多のクエリを書くには?
質問
1対多のリレーションを投影するためにこのコードを書きましたが、うまくいきません。
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
IEnumerable<Store> stores = connection.Query<Store, IEnumerable<Employee>, Store>
(@"Select Stores.Id as StoreId, Stores.Name,
Employees.Id as EmployeeId, Employees.FirstName,
Employees.LastName, Employees.StoreId
from Store Stores
INNER JOIN Employee Employees ON Stores.Id = Employees.StoreId",
(a, s) => { a.Employees = s; return a; },
splitOn: "EmployeeId");
foreach (var store in stores)
{
Console.WriteLine(store.Name);
}
}
誰か間違いに気付かないか?
EDITです。
これらは私のエンティティです。
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public IList<Store> Stores { get; set; }
public Product()
{
Stores = new List<Store>();
}
}
public class Store
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Employee> Employees { get; set; }
public Store()
{
Products = new List<Product>();
Employees = new List<Employee>();
}
}
EDITです。
クエリを変更すると
IEnumerable<Store> stores = connection.Query<Store, List<Employee>, Store>
(@"Select Stores.Id as StoreId ,Stores.Name,Employees.Id as EmployeeId,
Employees.FirstName,Employees.LastName,Employees.StoreId
from Store Stores INNER JOIN Employee Employees
ON Stores.Id = Employees.StoreId",
(a, s) => { a.Employees = s; return a; }, splitOn: "EmployeeId");
で、例外が解消されました しかし、Employeeは全くマッピングされません。との間にどんな問題があったのか、まだよく分かっていません。
IEnumerable<Employee>
にどんな問題があったのか、まだよくわかりません。
どのように解決するのですか?
この投稿では 高度に正規化された SQL データベース に問い合わせ、その結果を高度にネストされたC# POCOオブジェクトの集合にマップする方法を紹介します。
成分です。
- 8行のC#。
- いくつかの結合を使用する、適度にシンプルなSQL。
- 2 つの素晴らしいライブラリ。
この問題を解決することができた洞察は、2つのライブラリを分離することです。
MicroORM
から
mapping the result back to the POCO Entities
. このように、2つの別々のライブラリを使っています。
- ダッパー をMicroORMにしました。
- スラッパー.オートマッパー マッピング用。
基本的に、私たちは ダッパー を使ってデータベースに問い合わせ、次に スラッパー.オートマッパー を使って、その結果をそのままPOCOにマッピングします。
利点
- 簡便性 . 8行以下のコードです。私はこの方が理解、デバッグ、変更がしやすいと思います。
-
より少ないコード
. 数行のコードだけで
スラッパー.オートマッパー
は、たとえ複雑なネストしたPOCOがあったとしても(つまり、POCOには
List<MyClass1>
を含み、それがさらにList<MySubClass2>
など)。 - 速度 . これらのライブラリは、手作業で調整された ADO.NET クエリとほぼ同等の速度で実行できるように、非常に多くの最適化とキャッシングを備えています。
- 懸念事項の分離 . MicroORMを別のものに変えてもマッピングは機能しますし、その逆も可能です。
- 柔軟性 . スラッパー.オートマッパー は、任意にネストされた階層を扱うので、数レベルのネストに限定されることはない。私たちは簡単に迅速な変更を行うことができ、すべてがまだ動作します。
- デバッギング . まず、SQLクエリが正常に動作していることを確認し、次にSQLクエリの結果が対象のPOCOエンティティに正しくマッピングされていることを確認することができます。
-
SQLでの開発のしやすさ
. で平坦化されたクエリを作成するのは、とても簡単だと思います。
inner joins
でフラットなクエリを作成し、フラットな結果を返すことは、クライアント側でステッチして複数の select ステートメントを作成するよりもはるかに簡単です。 - SQL における最適化されたクエリ . 高度に正規化されたデータベースでは、フラットなクエリを作成することにより、SQL エンジンは、多くの小さな個々のクエリを構築して実行した場合には通常不可能である、全体に対する高度な最適化を適用することができます。
- 信頼 . DapperはStackOverflowのバックエンドで、まあ、Randy Burdenはちょっとしたスーパースターです。これ以上言うことはないでしょう?
- 開発のスピード。 非常に複雑なクエリ、多くのレベルのネストを行うことができましたが、開発時間は非常に少なかったです。
- バグが少ない。 一度書いただけで、それがうまくいき、このテクニックは今、FTSE企業の動力源として役立っています。コードは非常に少なく、予期せぬ動作はありませんでした。
不利な点
-
1,000,000 行を超えるスケーリングが返されます。
100,000行を返す場合はうまくいきます。しかし、>1,000,000 行を返す場合、我々と SQL サーバー間のトラフィックを減らすために、フラット化するのではなく、以下のようにします。
inner join
(これは重複をもたらします) を使って平坦化するのではなく、複数のselect
ステートメントを使用し、クライアント側ですべてをつなぎ合わせます (このページの他の回答を参照してください)。 - このテクニックはクエリ指向です。 . 私はこのテクニックを使ってデータベースに書き込んだことはありませんが、StackOverflow自身がDAL(Data Access Layer)としてDapperを使っているので、もう少し手を加えればDapperでも十分可能なはずです。
パフォーマンス テスト
私のテストでは スラッパー.オートマッパー はDapperが返す結果に小さなオーバーヘッドを追加しましたが、それでもEntity Frameworkの10倍速であることを意味し この組み合わせは、SQL + C# が可能な理論上の最大速度にまだかなり近いものです。 .
ほとんどの実用的なケースでは、オーバーヘッドのほとんどは、C# 側の結果のマッピングではなく、最適とは言えない SQL クエリにあるでしょう。
パフォーマンス テスト結果
反復の総数 1000
-
Dapper by itself
: 1.889 ミリ秒、クエリごとに3 lines of code to return the dynamic
. -
Dapper + Slapper.Automapper
: 2.463 ミリ秒、追加の3 lines of code for the query + mapping from dynamic to POCO Entities
.
動作確認済み例
この例では
Contacts
のリストがあり、それぞれの
Contact
は一つまたはそれ以上の
phone numbers
.
POCOエンティティ
public class TestContact
{
public int ContactID { get; set; }
public string ContactName { get; set; }
public List<TestPhone> TestPhones { get; set; }
}
public class TestPhone
{
public int PhoneId { get; set; }
public int ContactID { get; set; } // foreign key
public string Number { get; set; }
}
SQLテーブル
TestContact
SQL テーブル
TestPhone
このテーブルには外部キー
ContactID
を参照しています。
TestContact
テーブル(これは
List<TestPhone>
に相当する)。
フラットな結果を生成するSQL
この SQL クエリでは、できるだけ多くの
JOIN
ステートメントを必要なだけ使用し、必要なデータすべてを
フラットで非正規化されたフォーム
. 確かに、この方法では出力に重複が生じるかもしれませんが、これらの重複は、次のようにすれば自動的に排除されます。
スラッパー.オートマッパー
を使って、このクエリの結果をそのままPOCOオブジェクト・マップに自動的にマッピングすることで、重複を排除することができます。
USE [MyDatabase];
SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId
<イグ
C#コード
const string sql = @"SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId";
string connectionString = // -- Insert SQL connection string here.
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
// Can set default database here with conn.ChangeDatabase(...)
{
// Step 1: Use Dapper to return the flat result as a Dynamic.
dynamic test = conn.Query<dynamic>(sql);
// Step 2: Use Slapper.Automapper for mapping to the POCO Entities.
// - IMPORTANT: Let Slapper.Automapper know how to do the mapping;
// let it know the primary key for each POCO.
// - Must also use underscore notation ("_") to name parameters in the SQL query;
// see Slapper.Automapper docs.
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestContact), new List<string> { "ContactID" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestPhone), new List<string> { "PhoneID" });
var testContact = (Slapper.AutoMapper.MapDynamic<TestContact>(test) as IEnumerable<TestContact>).ToList();
foreach (var c in testContact)
{
foreach (var p in c.TestPhones)
{
Console.Write("ContactName: {0}: Phone: {1}\n", c.ContactName, p.Number);
}
}
}
}
出力
POCOエンティティの階層構造
Visual Studioを見ると、Slapper.AutomapperがPOCOエンティティを適切に配置したことがわかります。
List<TestContact>
があり、それぞれの
TestContact
には
List<TestPhone>
.
<イグ
注意事項
DapperとSlapper.Automapperの両方は、速度のために内部で全てをキャッシュします。もし、メモリの問題が発生した場合(非常に低い確率ですが)、両方のキャッシュを時々クリアすることを確実にします。
戻ってくるカラムに名前をつけることを確実にします。
アンダースコア (
_
) 記法を使用します。
という記法を用いて、Slapper.AutomapperにPOCOエンティティにマッピングする手がかりを与えています。
各POCOエンティティの主キーの手がかりをSlapper.Automapperに与えていることを確認してください(以下の行を参照)。
Slapper.AutoMapper.Configuration.AddIdentifiers
). また
Attributes
を使うこともできます。もしこのステップを飛ばしてしまうと、Slapper.Automapperがマッピングを正しく行う方法を知らないため、(理論的には)うまくいかない可能性があります。
2015-06-14更新
40以上の正規化テーブルを持つ巨大な本番データベースにこのテクニックを適用することに成功しました。16 以上の高度な SQL クエリをマッピングするために完璧に機能しました。
inner join
と
left join
を適切な POCO 階層(4 レベルのネスト)に変換します。クエリは非常に高速で、ADO.NET でハンドコーディングするのとほぼ同じ速度です(通常、クエリに 52 ミリ秒、フラットな結果から POCO 階層へのマッピングに 50 ミリ秒かかりました)。これは本当に革命的なことではありませんが、速度と使いやすさの点でEntity Frameworkを確実に上回り、特に私たちが行っていることがクエリを実行しているだけであれば、それは間違いありません。
更新日 2016-02-19
コードは9ヶ月間、本番で完璧に動作しています。最新バージョンの
Slapper.Automapper
には、SQL クエリで返される NULL に関連する問題を修正するために適用したすべての変更が含まれています。
更新日 2017-02-20
コードは21ヶ月間、本番環境で完璧に動作しており、FTSE250企業の数百人のユーザーからの継続的なクエリを処理しています。
Slapper.Automapper
は、.csv ファイルを POCO のリストに直接マッピングするのにも最適です。.csv ファイルを IDictionary のリストに読み込んで、それをターゲットとなる POCO のリストに直接マッピングします。唯一のトリックは、プロパティに
int Id {get; set}
を追加し、それが各行で一意であることを確認することです(さもなければ、オートマッパーは行を区別することができません)。
2019-01-29 更新
より多くのコードコメントを追加するマイナーアップデート。
参照してください。 https://github.com/SlapperAutoMapper/Slapper.AutoMapper
関連
-
[解決済み】「The breakpoint will not currently be hit」を改善するには?このドキュメントにはシンボルが読み込まれていません。" という警告はどうすれば改善されますか?
-
[解決済み】エラー。「戻り値を変更できません」 C#
-
[解決済み] 他のスレッドからGUIを更新するにはどうすればよいですか?
-
[解決済み] enumを列挙するには
-
[解決済み] intをenumにキャストするにはどうすればよいですか?
-
[解決済み] C#で文字列のエンコーディングを手動で指定せずに、一貫性のあるバイト表現を得るには?
-
[解決済み] 乱数(int)を生成する方法を教えてください。
-
[解決済み] DateTime型の誕生日から年齢を計算するにはどうしたらいいですか?
-
[解決済み] Microsoft Officeをインストールせずに、C#でExcel(.XLSおよび.XLSX)ファイルを作成するにはどうすればよいですか?
-
[解決済み] DataTableに対するLINQクエリ
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】トランスポート接続からデータを読み取れない:既存の接続は、リモートホストによって強制的に閉じられました。
-
[解決済み】「namespace x already contains a definition for x」エラーの修正方法は?VS2010にコンバートした後に発生しました。
-
[解決済み】Visual studio 2019がデバッグ時にフリーズする件
-
[解決済み】「...は'型'であり、与えられたコンテキストでは有効ではありません」を解決するにはどうすればよいですか?(C#)
-
[解決済み】ランダムなブーリアンを生成する最速の方法
-
[解決済み】WSACancelBlockingCallの例外について
-
[解決済み】2つ(またはそれ以上)のリストを1つに統合する(C# .NETで
-
[解決済み】2年前のMSDateを把握する【クローズド
-
[解決済み】パラメータ付きRedirectToAction
-
[解決済み】データが存在しないのに読み込もうとする試みが無効である