Dapper.Netで1対多問合せを作成するにはどうすればよいですか?

.net c# dapper

質問

私はこのコードを書いて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);
   }
}

誰かが間違いを見つけられますか?

編集:

これらは私のエンティティです:

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>();
        }
    }

編集:

私は、クエリを変更します:

            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");

私は例外を取り除く!ただし、従業員はまったくマップされていません。私はまだ最初のクエリでIEnumerable<Employee>でどのような問題があったのか分かりません。

受け入れられた回答

この記事では、 高度に正規化されたSQLデータベースを照会し、その結果を一連の高度にネストされたC#POCOオブジェクトにマップする方法を説明します。

材料:

  • 8行のC#
  • いくつかの結合を使用する、かなり簡単なSQL。
  • 二つの素晴らしい図書館。

私がこの問題を解決することを可能にした洞察は、 MicroORMmapping the result back to the POCO Entities MicroORMから切り離すことMicroORM 。したがって、2つの別々のライブラリを使用します。

基本的には、 Dapperを使用してデータベースを照会し、次にSlapper.Automapperを使用して結果をPOCOに直接マッピングします。

利点

  • 簡単です。その8行未満のコード。私はこれを理解し、デバッグし、そして変更するのがずっと簡単であると思います。
  • 少ないコードほんの数行のコードですべてがSlapper.Automapperが複雑な入れ子になったPOCO(つまり、POCOにList<MyClass1>が含まれ、 List<MySubClass2>などが含まれているなど)を処理する必要があります。
  • スピードこれらのライブラリはどちらも、手動で調整されたADO.NETクエリとほぼ同じ速度で実行できるようにするために、並外れた量の最適化とキャッシュを備えています。
  • 懸案事項の分離 MicroORMを別のものに変更しても、マッピングは機能し、その逆も可能です。
  • 柔軟性 Slapper.Automapperは任意にネストされた階層を扱います、それは2、3レベルのネストに限定されません。私たちは簡単に素早く変更を加えることができ、それでもすべてうまくいくでしょう。
  • デバッグまず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自体がDapperをデータアクセス層(DAL)として使っているので、Dapperはもっと多くの追加作業でこれを実行できる以上の能力があると確信しています。

性能試験

私のテストでは、 Slapper.Automapperは Dapperから返される結果にわずかなオーバーヘッドを追加しました。それはEntity Frameworkよりも10倍高速であり、その組み合わせはSQL + C#が可能な理論上の最大速度に近いままです

ほとんどの実用的なケースでは、ほとんどのオーバーヘッドは最適とは言えないSQLクエリにあり、C#側での結果のマッピングによっては発生しません。

性能テスト結果

総繰り返し回数:1000

  • Dapper by itself おしゃべりする :1クエリあたり1.889ミリ秒3 lines of code to return the dynamic使用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は1つ以上のphone numbers持つことができ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

このテーブルにはTestContactテーブルを参照する外部キーContactIDあります(これは上記のPOCOのList<TestPhone>に対応します)。

ここに画像の説明を入力してください

フラットな結果を生み出すSQL

SQLクエリでは、必要なすべてのデータを取得するために必要な数のJOINステートメントをフラットな非正規化形式で使用します。はい、これにより出力に重複が生じる可能性がありますが、 Slapper.Automapperを使用してこのクエリの結果を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の行をSlapper.AutoMapper.Configuration.AddIdentifiers )。これにはPOCOでAttributesを使用することもできます。 Slapper.Automapperはマッピングを適切に行う方法を知らないので、このステップをスキップすると(理論的には)間違ってしまう可能性があります。

更新2015-06-14

40以上の正規化されたテーブルを持つ巨大な本番データベースにこの手法を適用することに成功しました。高度なSQLクエリを16を超えるinner joinleft joinを適切なPOCO階層(4レベルのネスト)にマップするのに完全に機能しました。クエリは、ADO.NETで手作業でコーディングするのとほぼ同じくらい高速です(通常、クエリで52ミリ秒、フラット結果からPOCO階層へのマッピングで50ミリ秒)。これは本当に革命的なことではありませんが、スピードと使いやすさの点でEntity Frameworkに勝るものです。

更新2016-02-19

コードは9か月間、本番環境で問題なく実行されています。 Slapper.Automapperの最新バージョンには、SQLクエリで返されるnullに関する問題を解決するために私が適用したすべての変更が含まれています。

更新2017-02-20

コードは21か月間、本番環境で問題なく実行されており、FTSE 250企業の何百人ものユーザーからの継続的なクエリを処理してきました。

Slapper.Automapperは、.csvファイルをSlapper.Automapperリストに直接マッピングするのにも最適です。 .csvファイルをIDictionaryのリストに読み込み、それをPOCOのターゲットリストに直接マッピングします。唯一のトリックは、適切なint Id {get; set}を追加する必要があるということですint Id {get; set}し、それがすべての行に対してユニークだことを確認します(または他のautomapperは、行を区別することはできません)。

更新2019-01-29

コードのコメントを追加するためのマイナーアップデート。

https://github.com/SlapperAutoMapper/Slapper.AutoMapperを参照してください。


人気のある回答

私はできるだけシンプルにしたい、私の解決策:

public List<ForumMessage> GetForumMessagesByParentId(int parentId)
{
    var sql = @"
    select d.id_data as Id, d.cd_group As GroupId, d.cd_user as UserId, d.tx_login As Login, 
        d.tx_title As Title, d.tx_message As [Message], d.tx_signature As [Signature], d.nm_views As Views, d.nm_replies As Replies, 
        d.dt_created As CreatedDate, d.dt_lastreply As LastReplyDate, d.dt_edited As EditedDate, d.tx_key As [Key]
    from 
        t_data d
    where d.cd_data = @DataId order by id_data asc;

    select d.id_data As DataId, di.id_data_image As DataImageId, di.cd_image As ImageId, i.fl_local As IsLocal
    from 
        t_data d
        inner join T_data_image di on d.id_data = di.cd_data
        inner join T_image i on di.cd_image = i.id_image 
    where d.id_data = @DataId and di.fl_deleted = 0 order by d.id_data asc;";

    var mapper = _conn.QueryMultiple(sql, new { DataId = parentId });
    var messages = mapper.Read<ForumMessage>().ToDictionary(k => k.Id, v => v);
    var images = mapper.Read<ForumMessageImage>().ToList();

    foreach(var imageGroup in images.GroupBy(g => g.DataId))
    {
        messages[imageGroup.Key].Images = imageGroup.ToList();
    }

    return messages.Values.ToList();
}

私はまだデータベースへの呼び出しを1回行い、1つではなく2つのクエリを実行しますが、2番目のクエリでは、LEFT結合が最適ではなくINNER結合を使用しています。



ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ