Вручную сопоставьте имена столбцов со свойствами класса - PullRequest
151 голосов
/ 18 января 2012

Я новичок в Dapper micro ORM.Пока я могу использовать его для простых вещей, связанных с ORM, но не могу сопоставить имена столбцов базы данных со свойствами класса.

Например, у меня есть следующая таблица базы данных:

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

и у меня есть класс Person:

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

Обратите внимание, что имена моих столбцов в таблице отличаются от имени свойства класса, которому я пытаюсь сопоставить данные, которые яполученный из результата запроса.

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

Приведенный выше код не будет работать, поскольку имена столбцов не соответствуют свойствам объекта (Person).В этом сценарии, что я могу сделать в Dapper, чтобы вручную сопоставить (например, person_id => PersonId) имена столбцов со свойствами объекта?

Ответы [ 15 ]

177 голосов
/ 27 сентября 2012

Dapper теперь поддерживает пользовательские столбцы для сопоставления свойств.Это происходит через интерфейс ITypeMap .Класс CustomPropertyTypeMap предоставляется Dapper, который может выполнять большую часть этой работы.Например:

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

И модель:

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

Важно отметить, что реализация CustomPropertyTypeMap требует, чтобы атрибут существовал и соответствовал одному из имен столбцов илисвойство не будет отображено. Класс DefaultTypeMap предоставляет стандартную функциональность и может использоваться для изменения этого поведения:

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

И с этим на месте он становитсялегко создать пользовательский сопоставитель типов, который будет автоматически использовать атрибуты, если они присутствуют, но в противном случае вернется к стандартному поведению:

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))
            })
    {
    }
}

Это означает, что теперь мы можем легко поддерживать типы, для которых требуется сопоставление, используя атрибуты:

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

Вот Суть к полному исходному коду .

62 голосов
/ 18 января 2012

Это прекрасно работает:

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 нет средства, позволяющего указать Атрибут столбца , я не против добавления поддержки для него, при условии, что мы не вмешиваемсязависимость.

59 голосов
/ 30 декабря 2015

В течение некоторого времени должно работать следующее:

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
27 голосов
/ 19 сентября 2014

Вот простое решение, которое не требует атрибутов, позволяющих вам исключить код инфраструктуры из ваших POCO.

Это класс для работы с отображениями. Словарь будет работать, если вы отобразите все столбцы, но этот класс позволяет вам указать только различия. Кроме того, он включает в себя обратные карты, поэтому вы можете получить поле из столбца и столбец из поля, что может быть полезно при выполнении таких задач, как генерация операторов SQL.

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 и сообщите Dapper использовать сопоставление.

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])));
21 голосов
/ 25 января 2017

Я делаю следующее, используя динамический и LINQ:

    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 голосов
/ 18 января 2016

Взято из Dapper Tests , которое в настоящее время находится на Dapper 1.42.

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

Вспомогательный класс для получения имени из атрибута Description (лично я использовал Column, как пример @kalebs)

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

Класс

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

   [Description("A")]
   public string B { get; set; }
}
11 голосов
/ 12 мая 2015

Простой способ добиться этого - просто использовать псевдонимы столбцов в вашем запросе.Если столбец вашей базы данных равен PERSON_ID, а свойство вашего объекта равно ID, вы можете просто сделать select PERSON_ID as Id ... в своем запросе, и Dapper подберет его, как ожидается.

10 голосов
/ 04 августа 2015

Мессинг с картографией - это пограничный переход в реальную землю ORM.Вместо того, чтобы бороться с этим и поддерживать Dapper в его истинно простой (быстрой) форме, просто слегка измените ваш SQL следующим образом:

var sql = @"select top 1 person_id as PersonId,FirstName,LastName from Person";
7 голосов
/ 02 августа 2017

Прежде чем открыть соединение с вашей базой данных, выполните этот фрагмент кода для каждого из ваших классов poco:

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

Затем добавьте аннотации данных в ваши классы poco следующим образом:

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

После этого все готово. Просто сделайте запрос, что-то вроде:

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();
}
3 голосов
/ 17 января 2017

Это свиное отступление от других ответов. Это всего лишь мысль о том, как управлять строками запроса.

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

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