Penggunaan Multimapping dengan benar di Dapper


111

Saya mencoba menggunakan fitur Multimapping necis untuk mengembalikan daftar ProductItems dan Pelanggan terkait.

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

Kode necis saya adalah sebagai berikut

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

Ini berfungsi dengan baik tetapi saya tampaknya harus menambahkan daftar kolom lengkap ke parameter splitOn untuk mengembalikan semua properti pelanggan. Jika saya tidak menambahkan "CustomerName" itu mengembalikan null. Apakah saya salah memahami fungsi inti dari fitur multimapping. Saya tidak ingin setiap kali menambahkan daftar lengkap nama kolom.


bagaimana Anda benar-benar menampilkan kedua tabel di datagridview? sebuah contoh kecil akan sangat dihargai.
Ankur Soni

Jawaban:


184

Saya baru saja menjalankan tes yang berfungsi dengan baik:

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

Parameter splitOn perlu ditentukan sebagai titik pemisah, defaultnya adalah Id. Jika ada beberapa titik pemisah, Anda perlu menambahkannya dalam daftar yang dipisahkan koma.

Katakanlah kumpulan data Anda terlihat seperti ini:

ProductID | ProductName | Akun Dibuka | CustomerId | Nama Pelanggan
--------------------------------------- ----------- --------------

Dapper perlu mengetahui cara membagi kolom dalam urutan ini menjadi 2 objek. Pandangan sepintas menunjukkan bahwa Pelanggan mulai di kolom CustomerId, karenanya splitOn: CustomerId.

Ada peringatan besar di sini, jika urutan kolom dalam tabel yang mendasarinya dibalik karena suatu alasan:

ProductID | ProductName | Akun Dibuka | CustomerName | ID Pelanggan  
--------------------------------------- ----------- --------------

splitOn: CustomerId akan menghasilkan nama pelanggan nol.

Jika Anda menentukan CustomerId,CustomerNamesebagai titik pemisah, necis berasumsi Anda mencoba untuk membagi hasil set menjadi 3 objek. Pertama dimulai dari awal, kedua dimulai pada CustomerId, ketiga pada CustomerName.


2
Terima kasih Sam. Ya hak Anda itu adalah urutan pengembalian kolom yang menjadi masalah dengan CustomerName | CustomerId dikembalikan CustomerName kembali null.
Richard Forrest

18
Satu hal yang perlu diingat adalah Anda tidak boleh memiliki spasi di dalamnya spliton, yaitu CustomerId,CustomerNametidak CustomerId, CustomerName, karena Dapper tidak melakukan Trimpemisahan string. Ini hanya akan membuang kesalahan pemisahan umum. Membuatku gila suatu hari nanti.
jes

2
@vaheeds Anda harus SELALU menggunakan nama kolom dan tidak pernah menggunakan bintang, ini memberi sql lebih sedikit pekerjaan yang harus dilakukan, dan Anda tidak mendapatkan situasi di mana urutan kolom salah, seperti dalam kasus ini.
Harag

3
@vaheeds - mengenai id, Id, ID melihat kode necis itu tidak case sensitive, dan juga memotong teks untuk splitOn - ini adalah v1.50.2.0 necis.
Harag

2
Bagi siapa pun yang bertanya-tanya, jika Anda harus membagi kueri dalam 3 objek: pada satu kolom bernama "Id" dan pada satu kolom bernama "somethingId", pastikan untuk menyertakan "Id" pertama dalam klausa pemisahan. Meskipun Dapper membagi secara default pada "Id", dalam hal ini ia harus disetel secara eksplisit.
Sbu

27

Tabel kami diberi nama yang mirip dengan Anda, di mana sesuatu seperti "ID Pelanggan" mungkin ditampilkan dua kali menggunakan operasi 'pilih *'. Oleh karena itu, Dapper melakukan tugasnya tetapi memisahkan terlalu dini (mungkin), karena kolomnya adalah:

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

Hal ini membuat parameter spliton: tidak begitu berguna, terutama ketika Anda tidak yakin dengan urutan apa kolom dikembalikan. Tentu saja Anda dapat menentukan kolom secara manual ... tapi ini tahun 2017 dan kami jarang melakukannya lagi untuk objek dasar.

Apa yang kami lakukan, dan berfungsi dengan baik untuk ribuan kueri selama bertahun-tahun, hanyalah menggunakan alias untuk Id, dan tidak pernah menentukan pemisahan (menggunakan 'Id' default Dapper).

select 
p.*,

c.CustomerID AS Id,
c.*

... voila! Dapper hanya akan membagi Id secara default, dan Id itu muncul sebelum semua kolom Pelanggan. Tentu saja ini akan menambahkan kolom ekstra ke kumpulan hasil pengembalian Anda, tetapi itu adalah biaya overhead yang sangat minimal untuk utilitas tambahan untuk mengetahui dengan tepat kolom mana yang termasuk dalam objek apa. Dan Anda dapat dengan mudah mengembangkannya. Butuh alamat dan informasi negara?

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

Yang terbaik dari semuanya, Anda dengan jelas menunjukkan dalam jumlah minimal sql kolom mana yang terkait dengan objek mana. Dapper melakukan sisanya.


Ini adalah pendekatan yang ringkas selama tidak ada tabel yang memiliki kolom Id.
Bernard Vander Beken

Dengan pendekatan ini, tabel masih dapat memiliki kolom Id ... tetapi harus berupa PK. Anda tidak perlu membuat alias jadi pekerjaannya sedikit berkurang. (Saya akan berpikir sangat tidak biasa (bentuk buruk?) Memiliki kolom yang disebut 'Id' yang bukan PK.)
BlackjacketMack

5

Dengan asumsi struktur berikut di mana '|' adalah titik pemisahan dan Ts adalah entitas tempat pemetaan harus diterapkan.

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

Berikut ini adalah kueri rapi yang harus Anda tulis.

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

Jadi kami ingin TF pertama memetakan col_1 col_2 col_3, untuk TSkedua col_n col_m ...

Ekspresi splitOn diterjemahkan menjadi:

Mulailah memetakan semua kolom ke dalam TFrist sampai Anda menemukan kolom bernama atau alias 'col_3', dan juga memasukkan 'col_3' ke dalam hasil pemetaan.

Kemudian mulai memetakan ke dalam TSkedua semua kolom mulai dari 'col_n' dan lanjutkan pemetaan sampai pemisah baru ditemukan, yang dalam hal ini adalah 'col_A' dan menandai dimulainya pemetaan ketiga dan seterusnya.

Kolom dari query sql dan props dari objek pemetaan berada dalam relasi 1: 1 (artinya harus diberi nama yang sama), jika nama kolom yang dihasilkan dari query sql berbeda Anda dapat membuat alias menggunakan 'AS [ Some_Alias_Name] '.


2

Ada satu peringatan lagi. Jika field CustomerId adalah null (biasanya dalam query dengan left join) Dapper membuat ProductItem dengan Customer = null. Pada contoh di atas:

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

Dan bahkan satu peringatan / jebakan lagi. Jika Anda tidak memetakan bidang yang ditentukan dalam splitOn dan bidang itu berisi null, Dapper akan membuat dan mengisi objek terkait (Pelanggan dalam hal ini). Untuk mendemonstrasikan gunakan kelas ini dengan sql sebelumnya:

public class Customer
{
    //public decimal CustomerId { get; set; }
    public string CustomerName { get; set; }
}
...
Debug.Assert(item.Customer != null);
Debug.Assert(item.Customer.CustomerName == "n");  

apakah ada solusi untuk contoh kedua selain menambahkan Customerid ke kelas? Saya mengalami masalah di mana saya membutuhkan objek null, tetapi itu memberi saya objek kosong. ( stackoverflow.com/questions/27231637/… )
jmzagorski

1

Saya melakukan ini secara umum di repo saya, berfungsi dengan baik untuk kasus penggunaan saya. Saya pikir saya akan berbagi. Mungkin seseorang akan memperpanjang ini lebih jauh.

Beberapa kekurangannya adalah:

  • Ini mengasumsikan properti kunci asing Anda adalah nama objek anak Anda + "Id", misalnya UnitId.
  • Saya hanya memetakan 1 objek anak ke orang tua.

Kode:

    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

Jika Anda perlu memetakan entitas besar, menulis setiap bidang pasti menjadi tugas yang sulit.

Saya mencoba jawaban @BlackjacketMack, tetapi salah satu tabel saya memiliki Kolom Id yang lain tidak (saya tahu ini masalah desain DB, tapi ...) lalu ini menyisipkan perpecahan ekstra pada necis, itu sebabnya

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

Tidak berhasil untuk saya. Kemudian saya akhiri dengan sedikit perubahan, cukup masukkan titik terpisah dengan nama yang tidak sesuai dengan bidang apa pun pada tabel, Jika mungkin diubah as Idoleh as _SplitPoint_, skrip sql terakhir terlihat seperti ini:

select
p.*,

c.CustomerID AS _SplitPoint_,
c.*,

address.AddressID AS _SplitPoint_,
address.*,

country.CountryID AS _SplitPoint_,
country.*

Kemudian di necis tambahkan hanya satu splitOn seperti ini

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();
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.