Вручную сопоставьте имена столбцов со свойствами класса

Я новичок в Dapper микро ОРМ. Пока я могу использовать его для простых вещей, связанных с 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) имена столбцов со свойствами объекта?

Ответ 1

Это отлично работает:

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

Ответ 2

Теперь 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);
    }
}

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

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

Здесь Gist для полного исходного кода.

Ответ 3

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

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

Ответ 4

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

Это класс для обработки отображений. Словарь будет работать, если вы сопоставили все столбцы, но этот класс позволяет указать только различия. Кроме того, он включает в себя обратные карты, поэтому вы можете получить поле из столбца и столбца из поля, что может быть полезно при выполнении таких операций, как создание операторов 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])));

Ответ 5

Я делаю следующее, используя динамические и 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;
    }

Ответ 6

Взято из 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; }
}

Ответ 7

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

Ответ 8

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

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

Ответ 9

Прежде чем открыть соединение с вашей базой данных, выполните этот фрагмент кода для каждого из ваших классов 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();
}

Ответ 10

Если вы используете .NET 4.5.1 или более высокую версию Dapper.FluentColumnMapping для отображения стиля LINQ. Он позволяет полностью отделить отображение db от вашей модели (нет необходимости в аннотации)

Ответ 11

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

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

Ответ 12

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

Добавить новый класс атрибутов столбца:
  [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property]

  public class ColumnAttribute : Attribute
  {

    public string Name { get; set; }

    public ColumnAttribute(string name)
    {
      this.Name = name;
    }
  }

Искать эту строку:
map = new DefaultTypeMap(type);

и прокомментируйте это.

Вместо этого напишите:
        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);
        });

Ответ 13

Решение Kaleb Pederson работало для меня. Я обновил ColumnAttributeTypeMapper, чтобы разрешить настраиваемый атрибут (имевший требование для двух разных сопоставлений на одном объекте домена) и обновленные свойства, чтобы позволить частным сеттерам в случаях, когда необходимо было создать поле и типы были разными.

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

Ответ 14

Я знаю, что это относительно старая тема, но я подумал, что я добавлю то, что я сделал там.

Я хотел, чтобы отображение атрибутов работало глобально. Либо вы соответствуете имени свойства (по умолчанию), либо вы соответствуете атрибуту столбца в свойстве класса. Я также не хотел устанавливать это для каждого класса, который я отображал. Таким образом, я создал класс DapperStart, который я вызываю при запуске приложения:

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

Довольно просто Не уверен, с какими проблемами я столкнусь, поскольку я только что написал это, но это работает.

Ответ 15

Простое решение проблемы, которую пытается решить Kaleb, - это просто принять имя свойства, если атрибут столбца не существует:

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