Sütun adlarını sınıf özellikleriyle manuel olarak eşleme


173

Dapper micro ORM'de yeniyim. Şimdiye kadar basit ORM ile ilgili şeyler için kullanabilirsiniz ama sınıf özellikleri ile veritabanı sütun adları eşlemek mümkün değil.

Örneğin, aşağıdaki veritabanı tablosu var:

Table Name: Person
person_id  int
first_name varchar(50)
last_name  varchar(50)

ve Kişi adında bir dersim var:

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Tablodaki sütun adlarımın, sorgu sonucundan aldığım verileri eşlemeye çalıştığım sınıfın özellik adından farklı olduğunu lütfen unutmayın.

var sql = @"select top 1 PersonId,FirstName,LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(sql).ToList();
    return person;
}

Sütun adları nesnenin (Kişi) özellikleriyle eşleşmediğinden yukarıdaki kod çalışmaz. Bu senaryoda, Dapper'da person_id => PersonIdnesne adlarına sahip sütun adlarını el ile eşleştirmek (örn. ) İçin yapabileceğim bir şey var mı?


Yanıtlar:


80

Bu iyi çalışıyor:

var sql = @"select top 1 person_id PersonId, first_name FirstName, last_name LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(sql).ToList();
    return person;
}

Dapper, bir Sütun Özelliği belirtmenize izin veren bir tesise sahip değildir, bağımlılığı çekmemek kaydıyla, destek eklemeye karşı değilim.


@Sam Safran tablo takma adını belirtmek için herhangi bir yolu var. Ülke adlı bir sınıf var ama db tablo arkik adlandırma kuralları nedeniyle çok kıvrık bir adı vardır.
TheVillageIdiot

64
Sütun Özelliği, saklı yordam sonuçlarının eşlenmesi için kullanışlı olacaktır.
Ronnie Overby

2
Sütun özellikleri, alanınız ile varlıklarınızı gerçekleştirmek için kullandığınız araç uygulama ayrıntıları arasındaki sıkı fiziksel ve / veya anlamsal bağlantıyı daha kolay kolaylaştırmak için de yararlı olacaktır. Bu nedenle, bunun için destek eklemeyin !!!! :)
Derek Greer

Tableattribute zaman columnattribe neden yok anlamıyorum. Bu örnek ekler, güncellemeler ve SP'lerle nasıl çalışır? Sütun girişini görmek istiyorum, onun ölü basit ve hayatı artık kolay olmayan linq-sql gibi benzer bir şey uygulayan diğer çözümlerden göç kolaylaştırır.
Vman

197

Dapper artık özellik eşleyicilere özel sütun destekliyor. Bunu ITypeMap arayüzü üzerinden yapar . Bu çalışmanın çoğunu yapabilecek bir CustomPropertyTypeMap sınıfı Dapper tarafından sağlanır. Örneğin:

Dapper.SqlMapper.SetTypeMap(
    typeof(TModel),
    new CustomPropertyTypeMap(
        typeof(TModel),
        (type, columnName) =>
            type.GetProperties().FirstOrDefault(prop =>
                prop.GetCustomAttributes(false)
                    .OfType<ColumnAttribute>()
                    .Any(attr => attr.Name == columnName))));

Ve model:

public class TModel {
    [Column(Name="my_property")]
    public int MyProperty { get; set; }
}

CustomPropertyTypeMap uygulamasının, özelliğin var olmasını ve sütun adlarından biriyle eşleşmesini veya özelliğin eşlenmeyeceğini gerektirdiğini unutmayın . DefaultTypeMap sınıfı standart işlevleri sağlar ve bu davranışı değiştirmek için de yararlanılabilir:

public class FallbackTypeMapper : SqlMapper.ITypeMap
{
    private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;

    public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
    {
        _mappers = mappers;
    }

    public SqlMapper.IMemberMap GetMember(string columnName)
    {
        foreach (var mapper in _mappers)
        {
            try
            {
                var result = mapper.GetMember(columnName);
                if (result != null)
                {
                    return result;
                }
            }
            catch (NotImplementedException nix)
            {
            // the CustomPropertyTypeMap only supports a no-args
            // constructor and throws a not implemented exception.
            // to work around that, catch and ignore.
            }
        }
        return null;
    }
    // implement other interface methods similarly

    // required sometime after version 1.13 of dapper
    public ConstructorInfo FindExplicitConstructor()
    {
        return _mappers
            .Select(mapper => mapper.FindExplicitConstructor())
            .FirstOrDefault(result => result != null);
    }
}

Ve bununla birlikte, mevcutsa öznitelikleri otomatik olarak kullanacak, ancak standart davranışa geri dönecek özel bir tür eşleyici oluşturmak kolaylaşır:

public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
{
    public ColumnAttributeTypeMapper()
        : base(new SqlMapper.ITypeMap[]
            {
                new CustomPropertyTypeMap(
                   typeof(T),
                   (type, columnName) =>
                       type.GetProperties().FirstOrDefault(prop =>
                           prop.GetCustomAttributes(false)
                               .OfType<ColumnAttribute>()
                               .Any(attr => attr.Name == columnName)
                           )
                   ),
                new DefaultTypeMap(typeof(T))
            })
    {
    }
}

Bu, artık özellikleri kullanarak harita gerektiren türleri kolayca destekleyebileceğimiz anlamına gelir:

Dapper.SqlMapper.SetTypeMap(
    typeof(MyModel),
    new ColumnAttributeTypeMapper<MyModel>());

İşte tam kaynak kodunun bir özeti .


Ben bu aynı sorun ile mücadele ediyorum ... ve bu gitmek gerekir rota gibi görünüyor ... Oldukça bu kod "Dapper.SqlMapper.SetTypeMap (typeof (MyModel), new ColumnAttributeTypeMapper <MyModel> ()); " stackoverflow.com/questions/14814972/…
Rohan Büchner

Herhangi bir sorgu yapmadan önce bir kez aramak isteyeceksiniz. Örneğin, yalnızca bir kez çağrılması gerektiğinden statik bir yapıcıda yapabilirsiniz.
Kaleb Pederson

7
Bunu resmi cevap yapmayı önerin - Dapper'ın bu özelliği son derece yararlıdır.
killthrush

3
@Oliver ( stackoverflow.com/a/34856158/364568 ) tarafından gönderilen eşleme çözümü çalışıyor ve daha az kod gerektiriyor.
Riga

4
"Kolayca" kelimesinin bu kadar zahmetsizce nasıl atıldığını seviyorum: P
Jonathan B.

80

Bir süre için aşağıdakilerin çalışması gerekir:

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

6
Bu gerçekten de " Sınıf özelliklerine sahip sütun adlarını Manuel Olarak Eşle " sorusunun cevabı olmasa da , benim için manuel olarak eşlemekten çok daha iyi (ne yazık ki PostgreSQL'de sütun adlarında alt çizgi kullanmak daha iyidir). Lütfen sonraki sürümlerde MatchNamesWithUnderscores seçeneğini kaldırmayın! Teşekkür ederim!!!
victorvartan

5
@victorvartan MatchNamesWithUnderscoresseçeneği kaldırmak için herhangi bir plan yoktur . En iyi ihtimalle , yapılandırma API'sını yeniden düzenlediysek, MatchNamesWithUnderscoresüyeyi yerinde bırakırdım (bu hala işe yarar , ideal olarak) ve [Obsolete]insanları yeni API'ye yönlendirmek için bir işaretçi eklerdim.
Marc Gravell

4
@MarcGravell Cevabınızın başlangıcında "Bir süredir" kelimelerini gelecekteki bir sürümde kaldırabileceğiniz konusunda endişelendim, açıklığa kavuştuğunuz için teşekkürler! ASP.NET Core'da Npgsql ile birlikte küçük bir proje için kullanmaya başladığım harika bir mikro ORM olan Dapper'e çok teşekkür ederim!
victorvartan

2
Bu kolayca en iyi cevaptır. Etrafta yığınlar ve iş yığınları buldum, ama sonunda buna tökezledim. Kolayca en iyi ama en az ilan edilen cevap.
teaMonkeyFruit

29

Altyapı kodunu POCO'larınızdan uzak tutmanıza izin veren özellikler gerektirmeyen basit bir çözüm.

Bu, eşlemelerle ilgilenmek için bir sınıftır. Tüm sütunları eşlediyseniz sözlük işe yarar, ancak bu sınıf yalnızca farkları belirtmenize olanak tanır. Buna ek olarak, ters haritalar içerir, böylece alanı sütundan ve sütunu alandan alabilirsiniz; bu, sql ifadeleri oluşturma gibi şeyler yaparken faydalı olabilir.

public class ColumnMap
{
    private readonly Dictionary<string, string> forward = new Dictionary<string, string>();
    private readonly Dictionary<string, string> reverse = new Dictionary<string, string>();

    public void Add(string t1, string t2)
    {
        forward.Add(t1, t2);
        reverse.Add(t2, t1);
    }

    public string this[string index]
    {
        get
        {
            // Check for a custom column map.
            if (forward.ContainsKey(index))
                return forward[index];
            if (reverse.ContainsKey(index))
                return reverse[index];

            // If no custom mapping exists, return the value passed in.
            return index;
        }
    }
}

ColumnMap nesnesini ayarlayın ve Dapper'a eşlemeyi kullanmasını söyleyin.

var columnMap = new ColumnMap();
columnMap.Add("Field1", "Column1");
columnMap.Add("Field2", "Column2");
columnMap.Add("Field3", "Column3");

SqlMapper.SetTypeMap(typeof (MyClass), new CustomPropertyTypeMap(typeof (MyClass), (type, columnName) => type.GetProperty(columnMap[columnName])));

Temel olarak POCO'nuzda veritabanınızın örneğin saklı yordamdan döndürdüğü öğeyle ilgili özelliklerin uyumsuzluğuna sahip olduğunuzda iyi bir çözümdür.
ezmek

1
Bir öznitelik kullanmanın verdiği kısalığı seviyorum, ancak kavramsal olarak bu yöntem daha temiz - POCO'nuzu veritabanı ayrıntılarıyla eşleştirmiyor.
Bruno Brant

Dapper'ı doğru bir şekilde anlarsam, belirli bir Insert () yöntemi yoktur, yalnızca Execute () ... bu eşleme yaklaşımı eklemeler için işe yarar mı? Veya güncellemeler? Teşekkürler
UuDdLrLrSs

29

Dinamik ve LINQ kullanarak aşağıdakileri yaparım:

    var sql = @"select top 1 person_id, first_name, last_name from Person";
    using (var conn = ConnectionFactory.GetConnection())
    {
        List<Person> person = conn.Query<dynamic>(sql)
                                  .Select(item => new Person()
                                  {
                                      PersonId = item.person_id,
                                      FirstName = item.first_name,
                                      LastName = item.last_name
                                  }
                                  .ToList();

        return person;
    }

12

Bunu başarmanın kolay bir yolu, yalnızca sorgunuzdaki sütunlarda takma adları kullanmaktır. Veritabanı sütunu ise PERSON_IDve nesnenin proprty olan IDsadece yapabileceği select PERSON_ID as Id ...Sorgunuzda ve beklendiği gibi Zarif onu almak olacak.


12

Şu anda Dapper 1.42'de bulunan Dapper Testlerinden alınmıştır .

// custom mapping
var map = new CustomPropertyTypeMap(typeof(TypeWithMapping), 
                                    (type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
Dapper.SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);

Açıklama özelliğinden isim almak için yardımcı sınıf (Şahsen @kalebs örneği gibi Sütunu kullandım)

static string GetDescriptionFromAttribute(MemberInfo member)
{
   if (member == null) return null;

   var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false);
   return attrib == null ? null : attrib.Description;
}

Sınıf

public class TypeWithMapping
{
   [Description("B")]
   public string A { get; set; }

   [Description("A")]
   public string B { get; set; }
}

2
Hiçbir açıklama tanımlanır nereye mülkler için bile çalışmak için, ben dönüşünü değişti GetDescriptionFromAttributeetmek return (attrib?.Description ?? member.Name).ToLower();ve katma .ToLower()için columnNameharitadaki harf duyarlı olmamalı.
Sam White

11

Haritalama ile uğraşmak, gerçek ORM arazisine hareket eden sınır çizgisidir. Bununla savaşmak ve Dapper'ı gerçek basit (hızlı) formunda tutmak yerine, SQL'inizi biraz değiştirin:

var sql = @"select top 1 person_id as PersonId,FirstName,LastName from Person";

8

Veritabanınızla bağlantıyı açmadan önce, poco sınıflarınızın her biri için bu kod parçasını yürütün:

// Section
SqlMapper.SetTypeMap(typeof(Section), new CustomPropertyTypeMap(
    typeof(Section), (type, columnName) => type.GetProperties().FirstOrDefault(prop =>
    prop.GetCustomAttributes(false).OfType<ColumnAttribute>().Any(attr => attr.Name == columnName))));

Sonra veri ek açıklamalarını aşağıdaki gibi poco sınıflarınıza ekleyin:

public class Section
{
    [Column("db_column_name1")] // Side note: if you create aliases, then they would match this.
    public int Id { get; set; }
    [Column("db_column_name2")]
    public string Title { get; set; }
}

Bundan sonra, hepiniz hazırsınız. Sadece bir sorgu çağrısı yapın, şunun gibi:

using (var sqlConnection = new SqlConnection("your_connection_string"))
{
    var sqlStatement = "SELECT " +
                "db_column_name1, " +
                "db_column_name2 " +
                "FROM your_table";

    return sqlConnection.Query<Section>(sqlStatement).AsList();
}

1
Sütun özniteliğine sahip olmak için tüm özelliklere ihtiyaç duyar. Eşleştiricinin kullanılamaması durumunda mülkle eşleştirmenin herhangi bir yolu var mı?
sandeep.gosavi

5

.NET 4.5.1 veya daha üstünü kullanıyorsanız , LINQ stilini eşleştirmek için Dapper.FluentColumnMapping kasasını kullanın . Db eşlemesini modelinizden tamamen ayırmanızı sağlar (ek açıklamalara gerek yoktur)


5
Ben Dapper.FluentColumnMapping'in yazarıyım. Eşlemeleri modellerden ayırmak birincil tasarım hedeflerinden biriydi. Endişelerin temiz bir şekilde ayrılması için temel veri erişimini (yani veri havuzu arayüzleri, model nesneleri, vb.) Veritabanına özgü somut uygulamalardan izole etmek istedim. Söz için teşekkürler ve yararlı bulduğunuz için memnunum! :-)
Alexander

github.com/henkmollema/Dapper-FluentMap benzerdir. Ama artık bir üçüncü taraf paketine ihtiyacınız yok. Dapper, Dapper.SqlMapper'ı ekledi. İlgileniyorsanız daha fazla ayrıntı için cevabımı görün.
Tadej

4

Bu, diğer cevaplardan geri dönen domuzcuk. Bu sadece sorgu dizelerini yönetmek için sahip olduğum bir düşünce.

Person.cs

public class Person 
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public static string Select() 
    {
        return $"select top 1 person_id {nameof(PersonId)}, first_name {nameof(FirstName)}, last_name {nameof(LastName)}from Person";
    }
}

API Yöntemi

using (var conn = ConnectionFactory.GetConnection())
{
    var person = conn.Query<Person>(Person.Select()).ToList();
    return person;
}

1

Dapper 1.12 kullanan herkes için, İşte bunu yapmak için yapmanız gerekenler:

  • Yeni bir sütun özellik sınıfı ekleyin:

      [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property]
    
      public class ColumnAttribute : Attribute
      {
    
        public string Name { get; set; }
    
        public ColumnAttribute(string name)
        {
          this.Name = name;
        }
      }

  • Bu satırı arayın:

    map = new DefaultTypeMap(type);

    ve yorum yapın.

  • Bunun yerine şunu yazın:

            map = new CustomPropertyTypeMap(type, (t, columnName) =>
            {
              PropertyInfo pi = t.GetProperties().FirstOrDefault(prop =>
                                prop.GetCustomAttributes(false)
                                    .OfType<ColumnAttribute>()
                                    .Any(attr => attr.Name == columnName));
    
              return pi != null ? pi : t.GetProperties().FirstOrDefault(prop => prop.Name == columnName);
            });


  • Anladığımdan emin değilim - kullanıcıların sütunlarla öznitelik eşlemesini mümkün kılmak için Dapper'ı değiştirmelerini öneriyor musunuz? Öyleyse, Dapper'da değişiklik yapmadan yukarıda gönderdiğim kodu kullanmak mümkündür.
    Kaleb Pederson

    1
    Ancak, Model Türlerinizin her biri için eşleme işlevini çağırmanız gerekmeyecek mi? tüm türlerim her tür için eşleme çağırmak zorunda kalmadan özniteliği kullanabilirsiniz böylece genel bir çözüm ile ilgileniyorum.
    Uri Abramson

    2
    DefaultTypeMap @UriAbramson bahseder neden değiştirilebilir böylece bir strateji deseni kullanılarak uygulanmasını görmek istiyorum. Bkz. Code.google.com/p/dapper-dot-net/issues/detail?id=140
    Richard Collette

    1

    Kaleb Pederson'un çözümü benim için çalıştı. Özel bir özniteliğe izin vermek için ColumnAttributeTypeMapper'ı güncelledim (aynı etki alanı nesnesinde iki farklı eşleme gereksinimi vardı) ve bir alanın türetilmesi gereken durumlarda ve türlerin farklı olduğu durumlarda özel ayarlayıcılara izin vermek için özellikleri güncelleştirdim.

    public class ColumnAttributeTypeMapper<T,A> : FallbackTypeMapper where A : ColumnAttribute
    {
        public ColumnAttributeTypeMapper()
            : base(new SqlMapper.ITypeMap[]
                {
                    new CustomPropertyTypeMap(
                       typeof(T),
                       (type, columnName) =>
                           type.GetProperties( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(prop =>
                               prop.GetCustomAttributes(true)
                                   .OfType<A>()
                                   .Any(attr => attr.Name == columnName)
                               )
                       ),
                    new DefaultTypeMap(typeof(T))
                })
        {
            //
        }
    }

    1

    Bu nispeten eski bir iplik olduğunu biliyorum, ama orada ne yaptım atmak düşündüm.

    Nitelik haritalamanın global olarak çalışmasını istedim. Özellik adıyla (varsayılan olarak da bilinir) veya class özelliğindeki bir sütun özelliğiyle eşleşirsiniz. Ayrıca, eşleştirdiğim her sınıf için bunu ayarlamak istemedim. Bu nedenle, uygulama başlangıcında çağırdığım bir DapperStart sınıfı oluşturdum:

    public static class DapperStart
    {
        public static void Bootstrap()
        {
            Dapper.SqlMapper.TypeMapProvider = type =>
            {
                return new CustomPropertyTypeMap(typeof(CreateChatRequestResponse),
                    (t, columnName) => t.GetProperties().FirstOrDefault(prop =>
                        {
                            return prop.Name == columnName || prop.GetCustomAttributes(false).OfType<ColumnAttribute>()
                                       .Any(attr => attr.Name == columnName);
                        }
                    ));
            };
        }
    }

    Gayet basit. Henüz yazdığım gibi hangi sorunlarla karşılaşacağımdan emin değilim, ama işe yarıyor.


    CreateChatRequestResponse neye benziyor? Ayrıca, başlangıçta nasıl çağırıyorsunuz?
    Glen F.

    1
    @GlenF. mesele şu ki, CreateChatRequestResponse'nin neye benzediği önemli değil. herhangi bir POCO olabilir. bu başlangıçta çağrılır. Sadece StartUp.cs dosyalarınızda veya Global.asax'ınızda uygulama başlangıcınızda çağırabilirsiniz.
    Matt M

    Belki de tamamen yanılıyorum, ancak CreateChatRequestResponsebunun yerine Ttüm Varlık nesneleri boyunca bu yineleme nasıl yapılır. Yanlışım varsa lütfen düzelt.
    Fwd079

    0

    Kaleb'in çözmeye çalıştığı sorunun basit çözümü, yalnızca sütun özelliği yoksa özellik adını kabul etmektir:

    Dapper.SqlMapper.SetTypeMap(
        typeof(T),
        new Dapper.CustomPropertyTypeMap(
            typeof(T),
            (type, columnName) =>
                type.GetProperties().FirstOrDefault(prop =>
                    prop.GetCustomAttributes(false)
                        .OfType<ColumnAttribute>()
                        .Any(attr => attr.Name == columnName) || prop.Name == columnName)));
    
    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.