Multi-Mapper用於創建對象層次結構

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

我希望最終能夠返回一個與多個Phone對象聯繫的東西。這樣,如果我有2個聯繫人,每個聯繫人有2個電話,我的SQL將返回一個連接,作為結果集共4行。然後Dapper將彈出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);

我只是做錯了嗎?它看起來就像帖子/所有者的例子,除了我從父母到孩子而不是孩子到父母。

提前致謝

一般承認的答案

你沒有做錯任何事,這不是API的設計方式。所有Query API將始終為每個數據庫行返回一個對象。

因此,這在很多 - >一個方向上運行良好,但對於一個方向不太好 - >多個多地圖。

這裡有兩個問題:

  1. 如果我們引入一個適用於您的查詢的內置映射器,我們將需要“丟棄”重複數據。 (聯繫人。*在您的查詢中重複)

  2. 如果我們將它設計為使用一對 - >多對,我們將需要某種身份映射。這增加了複雜性。


例如,如果您只需要提取有限數量的記錄,那麼這個查詢是有效的,如果您將其推高到一百萬個,那麼就會變得更加棘手,因為您需要流式傳輸並且無法將所有內容加載到內存中:

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

因為這有點棘手和復雜,有警告。我並不傾向於將其納入核心。


熱門答案

僅供參考 - 我通過以下方式獲得了Sam的回答:

首先,我添加了一個名為“Extensions.cs”的類文件。我必須在兩個地方將“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合法嗎? 是的,了解原因