オブジェクト階層を作成するためのマルチマッパー

dapper multi-mapping

質問

私はこれを少しやっていましたが、これは文書化された投稿/ユーザーの例のように感じられるようですが、若干異なり、私のために働いていないようです。

次の簡略化されたセットアップを想定します(連絡先には複数の電話番号があります)。

public class Contact
{
    public int ContactID { get; set; }
    public string ContactName { get; set; }
    public IEnumerable<Phone> Phones { get; set; }
}

public class Phone
{
    public int PhoneId { get; set; }
    public int ContactID { get; set; } // foreign key
    public string Number { get; set; }
    public string Type { get; set; }
    public bool IsActive { get; set; }
}

私は複数の電話オブジェクトとの連絡先を返す何かで終わりたいです。そうすれば、2つの電話機を持つ2つの連絡先があれば、私のSQLは4つの合計行で結果セットとしてそれらの結合を返します。 Dapperは2つの電話でそれぞれ2つの連絡先オブジェクトを表示します。

ストアドプロシージャのSQLは次のとおりです。

SELECT *
FROM Contacts
    LEFT OUTER JOIN Phones ON Phones.ReferenceId=Contacts.ReferenceId
WHERE clientid=1

私はこれを試してみましたが、4つのタプルで終わってしまいました(これはOKですが、私が望んでいたものではありません...結果を再正規化しなければならないということです)。

var x = cn.Query<Contact, Phone, Tuple<Contact, Phone>>("sproc_Contacts_SelectByClient",
                              (co, ph) => Tuple.Create(co, ph), 
                                          splitOn: "PhoneId", param: p, 
                                          commandType: CommandType.StoredProcedure);

私は別の方法(下記)を試してみると、 "タイプ 'System.Int32'のオブジェクトを 'System.Collections.Generic.IEnumerable`1 [Phone]'とタイプすることができません。

var x = cn.Query<Contact, IEnumerable<Phone>, Contact>("sproc_Contacts_SelectByClient",
                               (co, ph) => { co.Phones = ph; return co; }, 
                                             splitOn: "PhoneId", param: p,
                                             commandType: CommandType.StoredProcedure);

私はちょうど何か間違っていますか?投稿/所有者の例と似ているようですが、私は親から子供の代わりに子供から親に行くことを除いています。

前もって感謝します

受け入れられた回答

あなたは間違って何もしていない、それはAPIが設計された方法ではない。すべてのQuery APIは、 常にデータベース行ごとにオブジェクトを返します。

だから、これは多くの - >一方向ではうまくいくが、1 - >多くのマルチマップではうまくいかない。

ここに2つの問題があります:

  1. あなたのクエリで動作するビルトインマッパーを導入すれば、重複データを "破棄"することが予想されます。 (連絡先。*はあなたの質問に複製されています)

  2. 1対多対で動作するように設計すれば、ある種のアイデンティティマップが必要になります。複雑さが増します。


たとえば、限られた数のレコードをプルする必要がある場合に効率的なこのクエリを考えてみましょう。何百万ものレコードをプッシュすると、ストリームが必要になり、すべてをメモリにロードすることができないため、

var sql = "set nocount on
DECLARE @t TABLE(ContactID int,  ContactName nvarchar(100))
INSERT @t
SELECT *
FROM Contacts
WHERE clientid=1
set nocount off 
SELECT * FROM @t 
SELECT * FROM Phone where ContactId in (select t.ContactId from @t t)"

あなたができることは、再マッピングを可能にするためにGridReaderを拡張することです:

var mapped = cnn.QueryMultiple(sql)
   .Map<Contact,Phone, int>
    (
       contact => contact.ContactID, 
       phone => phone.ContactID,
       (contact, phones) => { contact.Phones = phones };  
    );

GridReaderをマッパーで拡張すると仮定します:

public static IEnumerable<TFirst> Map<TFirst, TSecond, TKey>
    (
    this GridReader reader,
    Func<TFirst, TKey> firstKey, 
    Func<TSecond, TKey> secondKey, 
    Action<TFirst, IEnumerable<TSecond>> addChildren
    )
{
    var first = reader.Read<TFirst>().ToList();
    var childMap = reader
        .Read<TSecond>()
        .GroupBy(s => secondKey(s))
        .ToDictionary(g => g.Key, g => g.AsEnumerable());

    foreach (var item in first)
    {
        IEnumerable<TSecond> children;
        if(childMap.TryGetValue(firstKey(item), out children))
        {
            addChildren(item,children);
        }
    }

    return first;
}

これはちょっと複雑で、注意が必要です。私はこれをコアに含める方に傾いていません。


人気のある回答

FYI - 次のようにしてSamの答えが得られました:

まず、 "Extensions.cs"というクラスファイルを追加しました。私は2つの場所で "this"キーワードを "reader"に変更しなければなりませんでした:

using System;
using System.Collections.Generic;
using System.Linq;
using Dapper;

namespace TestMySQL.Helpers
{
    public static class Extensions
    {
        public static IEnumerable<TFirst> Map<TFirst, TSecond, TKey>
            (
            this Dapper.SqlMapper.GridReader reader,
            Func<TFirst, TKey> firstKey,
            Func<TSecond, TKey> secondKey,
            Action<TFirst, IEnumerable<TSecond>> addChildren
            )
        {
            var first = reader.Read<TFirst>().ToList();
            var childMap = reader
                .Read<TSecond>()
                .GroupBy(s => secondKey(s))
                .ToDictionary(g => g.Key, g => g.AsEnumerable());

            foreach (var item in first)
            {
                IEnumerable<TSecond> children;
                if (childMap.TryGetValue(firstKey(item), out children))
                {
                    addChildren(item, children);
                }
            }

            return first;
        }
    }
}

次に、最後のパラメータを変更して、次のメソッドを追加しました。

public IEnumerable<Contact> GetContactsAndPhoneNumbers()
{
    var sql = @"
SELECT * FROM Contacts WHERE clientid=1
SELECT * FROM Phone where ContactId in (select ContactId FROM Contacts WHERE clientid=1)";

    using (var connection = GetOpenConnection())
    {
        var mapped = connection.QueryMultiple(sql)    
            .Map<Contact,Phone, int>     (        
            contact => contact.ContactID,        
            phone => phone.ContactID,
            (contact, phones) => { contact.Phones = phones; }      
        ); 
        return mapped;
    }
}


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