Dapper'da Multimapping'in doğru kullanımı


111

Ürün Öğelerinin ve ilişkili Müşterilerin bir listesini döndürmek için dapper'ın Çoklu Eşleme özelliğini kullanmaya çalışıyorum.

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

Şık kodum aşağıdaki gibidir

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

Bu iyi çalışıyor, ancak tüm müşteri özelliklerini döndürmek için splitOn parametresine tam sütun listesini eklemem gerekiyor gibi görünüyor. "MüşteriAdı" eklemezsem boş döndürür. Çoklu eşleme özelliğinin temel işlevini anlamıyor muyum? Her seferinde tam bir sütun adları listesi eklemek istemiyorum.


o zaman her iki tabloyu da datagridview'da nasıl gösterirsiniz? küçük bir örnek çok takdir edilecektir.
Ankur Soni

Yanıtlar:


184

İyi çalışan bir test yaptım:

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 parametresinin bölünme noktası olarak belirtilmesi gerekir, varsayılan olarak Id'dir. Birden fazla bölme noktası varsa, bunları virgülle ayrılmış bir listeye eklemeniz gerekecektir.

Kayıt kümenizin şuna benzediğini varsayalım:

Ürün Kimliği | ÜrünAdı | AccountOpened | Müşteri Kimliği | Müşteri adı
--------------------------------------- ----------- --------------

Dapper'ın bu sıradaki sütunların 2 nesneye nasıl bölüneceğini bilmesi gerekir. Üstün bir bakış, Müşterinin CustomerIdbu nedenle sütundan başladığını gösterir splitOn: CustomerId.

Bir yoktur büyük temel tablodaki sütun sipariş nedense çevrilirse, ikaz, burada:

Ürün Kimliği | ÜrünAdı | AccountOpened | MüşteriAdı | Müşteri Kimliği  
--------------------------------------- ----------- --------------

splitOn: CustomerId boş bir müşteri adıyla sonuçlanır.

CustomerId,CustomerNameBölme noktaları olarak belirtirseniz , dapper, sonuç kümesini 3 nesneye bölmeye çalıştığınızı varsayar. İlk baştan başlar, ikincisi başlar CustomerId, üçüncüsü CustomerName.


2
Teşekkürler Sam. Evet haklısın, CustomerName ile ilgili sorun sütunların iade sırasıydı | MüşteriAdı iade edilen CustomerId null geri geliyordu.
Richard Forrest

18
Unutulmaması gereken bir şey, içinde boşluklar olamaz spliton, yani CustomerId,CustomerNameyok CustomerId, CustomerName, çünkü Dapper Trimdizge bölünmesinin sonuçlarını değil . Sadece genel bölünme hatasını atar. Bir gün beni çılgına çevirdi.
jes

2
@vaheeds HER ZAMAN sütun isimleri kullanmalı ve asla yıldız kullanmamalısınız, SQL daha az iş sağlar ve bu durumda olduğu gibi sütun sırasının yanlış olduğu durumlarla karşılaşmazsınız.
Harag

3
@vaheeds - id, Id, ID ile ilgili olarak zarif koda bakılırsa, büyük / küçük harfe duyarlı değildir ve ayrıca splitOn metnini de kırpar - bu, dapper'ın v1.50.2.0'ıdır.
Harag

2
Merak edenler için, bir sorguyu 3 nesneye bölmeniz gerekmesi durumunda: "Id" adlı bir sütunda ve "somethingId" adlı bir sütunda, split yan tümcesine ilk "Id" yi eklediğinizden emin olun. Dapper varsayılan olarak "Id" olarak bölünse de, bu durumda açıkça ayarlanması gerekir.
Sbu

28

Tablolarımız sizinkine benzer şekilde adlandırılmıştır ve burada "Müşteri Kimliği" gibi bir şey "seçme *" işlemi kullanılarak iki kez döndürülebilir. Bu nedenle, Dapper işini yapıyor, ancak çok erken (muhtemelen) bölüyor, çünkü sütunlar:

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

Bu, spliton: parametresini, özellikle sütunların hangi sırayla döndürüldüğünden emin olmadığınızda o kadar kullanışlı hale getirmez. Elbette sütunları manuel olarak belirtebilirsiniz ... ama 2017 ve artık temel object gets için bunu nadiren yapıyoruz.

Yaptığımız ve uzun yıllar binlerce sorgu için harika çalıştı, sadece Id için bir takma ad kullanmak ve asla spliton belirtmemek (Dapper'in varsayılan 'Id' özelliğini kullanarak).

select 
p.*,

c.CustomerID AS Id,
c.*

... işte! Dapper, varsayılan olarak yalnızca Id'ye göre bölünür ve bu Kimlik, tüm Müşteri sütunlarından önce gerçekleşir. Elbette, dönüş sonuç kümenize fazladan bir sütun ekleyecektir, ancak bu, hangi sütunların tam olarak hangi nesneye ait olduğunu bilmenin ek faydası için son derece minimum ek yüktür. Ve bunu kolayca genişletebilirsiniz. Adres ve ülke bilgilerine mi ihtiyacınız var?

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

Hepsinden iyisi, hangi sütunların hangi nesneyle ilişkili olduğunu minimum miktarda sql ile açıkça gösteriyorsunuz. Dapper gerisini halleder.


Hiçbir tabloda Kimlik alanları olmadığı sürece bu kısa bir yaklaşımdır.
Bernard Vander Beken

Bu yaklaşımla, bir tablo hala bir Kimlik alanına sahip olabilir ... ancak bu PK olmalıdır. Takma adı oluşturmanız gerekmez, bu yüzden aslında biraz daha az iş olur. (Bence PK olmayan 'Id' adlı bir sütuna sahip olmanın oldukça sıra dışı (kötü bir biçim?) Olduğunu düşünüyorum.)
BlackjacketMack

5

Aşağıdaki yapının '|' olduğu varsayıldığında bölme noktasıdır ve Ts, eşlemenin uygulanması gereken varlıklardır.

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

Aşağıda yazmanız gereken zarif sorgu yer almaktadır.

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

Öyleyse TFirst için col_1 col_2 col_3'i eşlemesini istiyoruz, TSecond the col_n col_m ...

SplitOn ifadesi şu anlama gelir:

'Col_3' olarak adlandırılan veya takma adı verilen bir sütun bulana kadar tüm sütunların TFrist'e eşlenmesine başlayın ve ayrıca eşleme sonucuna 'col_3' ekleyin.

Ardından, 'col_n'den başlayarak tüm sütunları TSecond ile eşlemeye başlayın ve yeni ayırıcı bulunana kadar eşlemeye devam edin, bu durumda bu' col_A 'dır ve TThird eşlemesinin başlangıcını işaretler ve benzeri.

Sql sorgusunun sütunları ve eşleme nesnesinin propsları 1: 1 ilişki içindedir (yani aynı şekilde adlandırılmaları gerekir), eğer sql sorgusundan kaynaklanan sütun adları farklıysa bunları 'AS [ Bazı_Alias_Adı] 'ifadesi.


2

Bir uyarı daha var. CustomerId alanı null ise (genellikle sol birleşimli sorgularda) Dapper, Customer = null ile ProductItem oluşturur. Yukarıdaki örnekte:

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

Ve hatta bir uyarı / tuzak daha. SplitOn'da belirtilen alanı eşleştirmezseniz ve bu alan boş Dapper içeriyorsa ilgili nesneyi oluşturur ve doldurur (bu durumda Müşteri). Bu sınıfı önceki sql ile kullanmak göstermek için:

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

sınıfa Müşteri Kimliğini eklemenin yanı sıra ikinci örneğe bir çözüm var mı? Boş bir nesneye ihtiyacım olduğu bir sorun yaşıyorum, ancak bu bana boş bir nesne veriyor. ( stackoverflow.com/questions/27231637/… )
jmzagorski

1

Bunu genel olarak repomda yapıyorum, kullanım durumum için iyi çalışıyor. Paylaşacağımı düşündüm. Belki birisi bunu daha da uzatır.

Bazı dezavantajlar şunlardır:

  • Bu, yabancı anahtar özelliklerinizin alt nesnenizin adı + "Id", örneğin UnitId olduğunu varsayar.
  • Sadece 1 alt nesneyi üst nesneye eşleştiriyorum.

Kod:

    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

Büyük bir varlığı eşlemeniz gerekiyorsa, her alanı yazın zor bir görev olmalıdır.

@ BlackjacketMack cevabını denedim, ancak tablolarımdan birinde bir Id Sütunu var, diğerlerinde yok (bunun bir DB tasarım problemi olduğunu biliyorum, ama ...) sonra bu, zarif üzerine fazladan bir bölme ekliyor, bu yüzden

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

Benim için çalışmıyor. Sonra bu biraz değişiklik ile sona erdi sadece ayında değişmiş vaka olabilir tablolar üzerinde herhangi bir alanıyla eşleşmeyen bir adla bir bölünme noktası eklemek as Idtarafından as _SplitPoint_böyle, nihai sql komut dosyası görünüyor:

select
p.*,

c.CustomerID AS _SplitPoint_,
c.*,

address.AddressID AS _SplitPoint_,
address.*,

country.CountryID AS _SplitPoint_,
country.*

Daha sonra, bunun gibi sadece bir splitOn ekleyin

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();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.