Правильне використання Multimapping в Dapper


111

Я намагаюся використовувати функцію Multimapping of 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; }
}

Мій dapper-код наступний

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", він повернеться до нуля. Чи я не розумію основної функціональності функції мультиплікації. Я не хочу кожного разу додавати повний список назв стовпців.


як ви насправді показуєте обидві таблиці тоді у datagridview? невеликий приклад буде дуже вдячний.
Анкур Соні

Відповіді:


184

Я щойно пройшов тест, який працює чудово:

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 | Назва продукту | Рахунок відкрито | CustomerId | Ім'я клієнта
--------------------------------------- ----------- --------------

Dapper повинен знати, як розділити стовпці в цьому порядку на 2 об’єкти. Побіжний погляд показує, що Клієнт починається біля стовпця CustomerId, отже splitOn: CustomerId.

Тут є великий застереження, якщо замовлення стовпців у нижній таблиці з якоїсь причини перевернуто:

ProductID | Назва продукту | Рахунок відкрито | Ім'я клієнта | Ідентифікатор клієнта  
--------------------------------------- ----------- --------------

splitOn: CustomerId це призведе до нульового імені клієнта.

Якщо ви вказали CustomerId,CustomerNameяк точки розділення, dapper припускає, що ви намагаєтесь розділити набір результатів на 3 об’єкти. Перший починається на початку, другий - у CustomerIdтретьому CustomerName.


2
Дякую Сем. Так, ваше право, це був порядок повернення стовпців, який був проблемою з CustomerName | CustomerId повернуто CustomerName повертається нульовим.
Річард Форест

18
Варто пам’ятати, що ви не можете мати пробілів у spliton, тобто CustomerId,CustomerNameні CustomerId, CustomerName, оскільки Dapper не має Trimрезультатів розбиття рядка. Це просто викине загальну помилку сплітону. Одного дня мене звели з розуму.
JES

2
@vaheeds ВИНАГИ слід використовувати імена стовпців і ніколи не використовувати зірку, це дає sql менше роботи, і ви не отримуєте ситуацій, коли порядок стовпців невірний, як у цьому випадку.
Хараг

3
@vaheeds - що стосується ідентифікатора, ідентифікатора, ідентифікатора, що дивиться на код дапера, він не відрізняється від регістру, і він також обрізає текст для splitOn - це v1.50.2.0 dapper.
Хараг

2
Для всіх, хто цікавиться, якщо вам доведеться розділити запит на 3 об’єкти: на один стовпець під назвою "Id" та на один стовпець під назвою "somethingId", обов'язково включіть у розділ розділення перший "Id". Навіть незважаючи на те, що Dapper розділяється за замовчуванням на "Id", у цьому випадку його потрібно встановити чітко.
Сб

27

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

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

Це робить параметр spliton: не настільки корисним, особливо коли ви не впевнені, у якому порядку повертаються стовпці. Звичайно, ви можете вручну вказати стовпці ... але це 2017 рік, і ми просто рідко робимо це вже для отримання базового об'єкта.

Те, що ми робимо, і воно спрацьовує чудово для тисяч запитів протягом багатьох-багатьох років, це просто використовувати псевдонім для Id і ніколи не вказувати сплітон (використовуючи "Id" за замовчуванням "Даппера").

select 
p.*,

c.CustomerID AS Id,
c.*

... вуаля! Dapper розділяється на Id лише за замовчуванням, і цей Id відбувається перед усіма стовпцями клієнта. Звичайно, це додасть додатковий стовпець до набору результатів повернення, але це вкрай мінімально накладні витрати для додаткової корисності того, щоб точно знати, які стовпці належать до якого об’єкта. І ви можете легко розширити це. Потрібна інформація про адресу та країну?

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

Найкраще, що ви чітко показуєте в мінімальній кількості sql, які стовпці пов’язані з яким об’єктом. Dapper робить все інше.


Це стислий підхід, поки жодна таблиця не має полів Id.
Бернар Вандер Став

При такому підході таблиця все ще може мати поле Id ... але це має бути ПК. Вам просто не доведеться створювати псевдоніми, тому це насправді трохи менше роботи. (Я вважаю, що це дуже незвично (погана форма?) Мати стовпчик під назвою "Id", що не є ПК.)
BlackjacketMack

5

Якщо припустити таку структуру, де '|' є точкою розщеплення і Ц - сутності, до яких слід застосувати відображення.

       TFirst         TSecond         TThird           TFourth
------------------+-------------+-------------------+------------
col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8
------------------+-------------+-------------------+------------

Далі йде запит на деперс, який вам доведеться написати.

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

Таким чином, ми хочемо, щоб TFirst відобразив col_1 col_2 col_3, для TSecond - col_n col_m ...

Вираз splitOn означає:

Почніть зіставлення всіх стовпців у TFrist, поки не знайдете стовпчик з ім'ям або псевдонімом як «col_3», а також не включіть у результат відображення «col_3».

Потім почніть зіставляти в TSecond всі стовпці, починаючи з 'col_n' і продовжуйте картографувати, поки не знайдеться новий роздільник, який у цьому випадку є 'col_A' і позначає початок відображення TThird і так один.

Стовпці запиту sql та реквізити об’єкта відображення знаходяться у співвідношенні 1: 1 (це означає, що вони повинні бути названі однаковими), якщо назви стовпців, отримані в результаті запиту sql, різні, ви можете їх псевдонімом використовувати "AS [ Some_Alias_Name] 'вираз.


2

Є ще один застереження. Якщо поле 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, і це поле містить нульовий Даппер створює та заповнює відповідний об’єкт (Клієнт у цьому випадку). Для демонстрації використовуйте цей клас з попереднім 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");  

Чи є рішення щодо другого прикладу, крім додавання Customerid до класу? У мене виникає проблема, коли мені потрібен нульовий об’єкт, але він дає мені порожній об’єкт. ( Stackoverflow.com/questions/27231637 / ... )
jmzagorski

1

Я роблю це загалом у своєму репо, добре працює для мого використання. Я думав, що поділюсь. Можливо, хтось продовжить це далі.

Деякі недоліки:

  • Це передбачає, що ваші властивості іноземного ключа - це ім'я вашого дочірнього об'єкта + "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;
    }

0

Якщо вам потрібно скласти карту великої сутності, записування кожного поля має бути важким завданням.

Я спробував відповісти @BlackjacketMack, але в одній з моїх таблиць є стовпець Id, інші - не (я знаю, що це проблема проектування БД, але ...), тоді вставте додатковий спліт на Dapper, ось чому

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

Не працює для мене. Потім я закінчив з невеликою зміною до цього, просто вставити точку поділу з ім'ям , яке не збігається з будь-яким полем на столах, в разі може змінена as Idшляхом as _SplitPoint_, остаточний зовнішній вигляд SQL скрипт , як це:

select
p.*,

c.CustomerID AS _SplitPoint_,
c.*,

address.AddressID AS _SplitPoint_,
address.*,

country.CountryID AS _SplitPoint_,
country.*

Потім в dapper додайте лише один splitOn як цей

cmd =
    "SELECT Materials.*, " +
    "   Product.ItemtId as _SplitPoint_," +
    "   Product.*, " +
    "   MeasureUnit.IntIdUM as _SplitPoint_, " +
    "   MeasureUnit.* " +
    "FROM   Materials INNER JOIN " +
    "   Product ON Materials.ItemtId = Product.ItemtId INNER JOIN " +
    "   MeasureUnit ON Materials.IntIdUM = MeasureUnit.IntIdUM " +
List < Materials> fTecnica3 = (await dpCx.QueryAsync<Materials>(
        cmd,
        new[] { typeof(Materials), typeof(Product), typeof(MeasureUnit) },
        (objects) =>
        {
            Materials mat = (Materials)objects[0];
            mat.Product = (Product)objects[1];
            mat.MeasureUnit = (MeasureUnit)objects[2];
            return mat;
        },
        splitOn: "_SplitPoint_"
    )).ToList();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.