私はこれを少しいじっていました。文書化された投稿/ユーザーの例ですが、少し違っていて、私にはうまくいきません。
次の簡略化された設定(連絡先に複数の電話番号がある)を想定します。
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の設計方法が違うだけです。すべてのQuery
APIはいつもデータベースの行ごとにオブジェクトを返します。
したがって、これは多 -> 1 方向ではうまく機能しますが、1 -> 多のマルチマップではうまく機能しません。
ここでは 2 つの問題があります。
クエリで機能する組み込みマッパーを導入する場合、重複したデータを「破棄」することが予想されます。(Contacts.* はクエリ内で重複しています)
これを 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;
}
これは少しトリッキーで複雑なので、注意が必要です。私はこれをコアに組み込むつもりはありません。