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



Я новичок в 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;
}


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



любая подсказка или помощь будут высоко оценены.

484   13  

13 ответов:

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

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

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

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

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

здесь Gist to полный исходный код.

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

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

вот простое решение, которое не требует атрибутов, позволяющих сохранить код инфраструктуры из ваших 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])));

Я делаю следующее С помощью dynamic и 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;
    }

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

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

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

взято с Тесты Щеголеватый который в настоящее время находится на 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);

вспомогательный класс, чтобы получить имя от атрибута описания (я лично использовал столбец, например @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; }
}

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

Если вы используете .NET 4.5.1 или выше checkout щеголь.FluentColumnMapping для отображения стиля LINQ. Это позволяет полностью отделить отображение БД от вашей модели (нет необходимости в аннотациях)

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

человек.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;
}

для всех вас, кто использует 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);
            });
    

  • решение Калеба Педерсона сработало для меня. Я обновил 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))
                })
        {
            //
        }
    }
    

    Comments

      Ничего не найдено.