Правильное использование Multimapping в Dapper - PullRequest
94 голосов
/ 19 сентября 2011

Я пытаюсь использовать функцию Multimapping в dapper, чтобы вернуть список ProductItems и связанных с ними клиентов.

[Table("Product")]
public class ProductItem
{
    public decimal ProductID { get; set; }        
    public string ProductName { get; set; }
    public string AccountOpened { get; set; }
    public Customer Customer { get; set; }
} 

public class Customer
{
    public decimal CustomerId { get; set; }
    public string CustomerName { get; set; }
}

Мой код выглядит следующим образом

var sql = @"select * from Product p 
            inner join Customer c on p.CustomerId = c.CustomerId 
            order by p.ProductName";

var data = con.Query<ProductItem, Customer, ProductItem>(
    sql,
    (productItem, customer) => {
        productItem.Customer = customer;
        return productItem;
    },
    splitOn: "CustomerId,CustomerName"
);

Это работает нормально, но мне кажется, что мне нужно добавить полный список столбцов в параметр splitOn, чтобы вернуть все свойства клиентов. Если я не добавлю "CustomerName", он возвращает ноль. Я неправильно понимаю основную функциональность функции многократного отображения. Я не хочу добавлять полный список имен столбцов каждый раз.

Ответы [ 5 ]

160 голосов
/ 20 сентября 2011

Я только что выполнил тест, который работает нормально:

var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(1 as decimal) CustomerId, 'name' CustomerName";

var item = connection.Query<ProductItem, Customer, ProductItem>(sql,
    (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();

item.Customer.CustomerId.IsEqualTo(1);

Параметр splitOn должен быть указан как точка разделения, по умолчанию это Id.Если есть несколько точек разделения, вам нужно будет добавить их в список с разделителями-запятыми.

Скажем, ваш набор записей выглядит следующим образом:

ProductID | ProductName | AccountOpened | CustomerId | CustomerName 
---------------------------------------   -------------------------

Dapper должен знать, как разбивать столбцыв этом порядке на 2 объекта.Беглый просмотр показывает, что Заказчик начинается со столбца CustomerId, следовательно, splitOn: CustomerId.

Здесь есть большой предостережение, если порядок столбцов в базовой таблице перевернут дляпо какой-то причине:

ProductID | ProductName | AccountOpened | CustomerName | CustomerId  
---------------------------------------   -------------------------

splitOn: CustomerId приведет к нулевому имени клиента.

Если вы укажете CustomerId,CustomerName в качестве точек разделения, Dapper предполагает, что вы пытаетесь разделить результирующий набор на 3 объекта.Первый начинается с начала, второй начинается с CustomerId, третий начинается с CustomerName.

22 голосов
/ 06 августа 2017

Наши таблицы названы так же, как ваши, где что-то вроде «CustomerID» может быть возвращено дважды с помощью операции «select *».Таким образом, Dapper выполняет свою работу, но просто разделяет слишком рано (возможно), потому что столбцы будут:

(select * might return):
ProductID,
ProductName,
CustomerID, --first CustomerID
AccountOpened,
CustomerID, --second CustomerID,
CustomerName.

Это делает параметр spliton: не очень полезным, особенно если вы не уверены, в каком порядкестолбцы возвращаются. Конечно, вы можете указать столбцы вручную ... но сейчас 2017 год, и мы просто редко делаем это для получения базовых объектов.

То, что мы делаем, и прекрасно работало с тысячами запросов в течение многих лет, просто использует псевдоним для Id и никогда не указывает spliton (используя «Id» по умолчанию в Dapper).

select 
p.*,

c.CustomerID AS Id,
c.*

... вуаля!Dapper будет делиться только на Id по умолчанию, и этот Id встречается перед всеми столбцами Customer.Конечно, это добавит дополнительный столбец к вашему возвращаемому набору результатов, но это чрезвычайно минимальные издержки для дополнительной утилиты, позволяющей точно знать, какие столбцы принадлежат какому объекту.И вы можете легко расширить это.Вам нужна информация об адресе и стране?

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

Лучше всего, вы ясно показываете в минимальном количестве sql, какие столбцы связаны с каким объектом.Даппер делает все остальное.

2 голосов
/ 19 апреля 2013

Есть еще одна оговорка.Если поле CustomerId равно нулю (обычно в запросах с левым соединением), Dapper создает ProductItem с Customer = null.В приведенном выше примере:

var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(null as decimal) CustomerId, 'n' CustomerName";
var item = connection.Query<ProductItem, Customer, ProductItem>(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();
Debug.Assert(item.Customer == null); 

И еще одно предупреждение / ловушка.Если вы не отображаете поле, указанное в splitOn, и это поле содержит нулевое значение, Dapper создает и заполняет связанный объект (в данном случае Customer).Для демонстрации используйте этот класс с предыдущим sql:

public class Customer
{
    //public decimal CustomerId { get; set; }
    public string CustomerName { get; set; }
}
...
Debug.Assert(item.Customer != null);
Debug.Assert(item.Customer.CustomerName == "n");  
1 голос
/ 07 марта 2019

Предполагая следующую структуру запроса sql (представление имен столбцов, значения не имеют значения)

col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8

Так что в dapper вы будете использовать следующее определение QueryAsync

Query<TFirst, TSecond, TThird, TFourth, TResut> (
    sql : query,
    map: Func<TFirst, TSecond, TThird, TFourth, TResut> myFunc,
    parma: optional,
    splitOn: "col_3, col_n, col_A, col_9")

где мы хотим, чтобы TFirst отобразил первую часть TSecond 2nd и так далее.

Выражение splitOn переводится в:

Отобразите все столбцы в TFrist, пока не найдете столбец с именем или псевдонимом 'col_3', включите этот столбец также в отображение.

Затем сопоставьте TSecond, начиная с col_n до конца или найдя новый разделитель (также включите его в отображение col_n)

Затем сопоставьте TThird начальный col_A до конца или новый разделитель найден (также включите его в отображение col_A)

Затем сопоставьте TFourth, начиная с col_9 до конца, или пока не будет найден новый разделитель (также включите его в отображение col_9)

Столбцы запроса SQL и реквизиты объекта сопоставления находятся в отношении 1: 1 (это означает, что они должны называться одинаково), если имена столбцов, полученные в результате запроса SQL, отличаются, вы будете использовать псевдонимы AS [ Some_Alias_Name]

1 голос
/ 28 апреля 2016

Я делаю это в общем случае в моем репо, хорошо работает в моем случае использования. Я думал, что поделюсь. Может быть, кто-то расширит это дальше.

Некоторые недостатки:

  • Предполагается, что ваши свойства внешнего ключа - это имя вашего дочернего объекта + "Id", например, UnitID.
  • У меня есть только одно сопоставление дочернего объекта с родителем.

код:

    public IEnumerable<TParent> GetParentChild<TParent, TChild>()
    {
        var sql = string.Format(@"select * from {0} p 
        inner join {1} c on p.{1}Id = c.Id", 
        typeof(TParent).Name, typeof(TChild).Name);

        Debug.WriteLine(sql);

        var data = _con.Query<TParent, TChild, TParent>(
            sql,
            (p, c) =>
            {
                p.GetType().GetProperty(typeof (TChild).Name).SetValue(p, c);
                return p;
            },
            splitOn: typeof(TChild).Name + "Id");

        return data;
    }
...