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

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

私はこれを少しいじっていました。文書化された投稿/ユーザーの例ですが、少し違っていて、私にはうまくいきません。

次の簡略化された設定(連絡先に複数の電話番号がある)を想定します。

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

最終的には、複数の Phone オブジェクトを持つ Contact を返すようなものにしたいです。そうすれば、2 つの連絡先があり、それぞれに 2 つの電話がある場合、SQL はそれらの結合を合計 4 行の結果セットとして返します。その後、Dapper はそれぞれ 2 つの電話を持つ 2 つの連絡先オブジェクトをポップします。

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

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

これを試してみましたが、タプルは 4 つになりました (これは問題ありませんが、期待していたものではありません... つまり、結果を再正規化する必要があるということです)。

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

私が何か間違っているのでしょうか? 子から親ではなく、親から子に移動していることを除けば、投稿/所有者の例とまったく同じようです。

ベストアンサー1

何も悪いことをしていません。APIの設計方法が違うだけです。すべてのQueryAPIはいつもデータベースの行ごとにオブジェクトを返します。

したがって、これは多 -> 1 方向ではうまく機能しますが、1 -> 多のマルチマップではうまく機能しません。

ここでは 2 つの問題があります。

  1. クエリで機能する組み込みマッパーを導入する場合、重複したデータを「破棄」することが予想されます。(Contacts.* はクエリ内で重複しています)

  2. これを 1 -> 多数のペアで動作するように設計する場合、何らかのアイデンティティ マップが必要になります。これにより複雑さが増します。


たとえば、限られた数のレコードを取得するだけであれば効率的なクエリを考えてみましょう。しかし、これを 100 万件まで増やすと、ストリーミングが必要になり、すべてをメモリにロードすることができないため、処理が難しくなります。

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

これは少しトリッキーで複雑なので、注意が必要です。私はこれをコアに組み込むつもりはありません。

おすすめ記事