使用类属性手动映射列名称

时间:2012-01-17 22:35:21

标签: dapper

我是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)具有对象属性的列名吗?

17 个答案:

答案 0 :(得分:173)

Dapper现在支持自定义列到属性映射器。它通过ITypeMap接口完成。 Dapper提供了CustomPropertyTypeMap类,可以完成大部分工作。例如:

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

这是Gist to the full source code

答案 1 :(得分:62)

这很好用:

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没有允许你指定Column Attribute的功能,我不反对添加对它的支持,只要我们不引入依赖。

答案 2 :(得分:58)

有一段时间,以下情况应该有效:

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

答案 3 :(得分:26)

这是一个简单的解决方案,不需要允许您将基础设施代码保留在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])));

答案 4 :(得分:19)

我使用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;
    }

答案 5 :(得分:11)

取自目前在Dapper 1.42上的Dapper Tests

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

Helper类从Description属性中获取名称(我个人使用了像@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; }
}

答案 6 :(得分:11)

实现此目的的一种简单方法是在查询中的列上使用别名。如果您的数据库列为PERSON_ID并且您的对象的属性为ID,那么您只需在查询中执行select PERSON_ID as Id ...,Dapper就会按预期选择它。

答案 7 :(得分:10)

与映射混淆是边缘移动到真正的ORM土地。而不是与它斗争并使Dapper保持其真正的简单(快速)形式,只需稍微修改您的SQL:

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

答案 8 :(得分:7)

在打开与数据库的连接之前,请为每个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();
}

答案 9 :(得分:3)

如果您使用.NET 4.5.1或更高版本的checkout Dapper.FluentColumnMapping来映射LINQ样式。它允许您将db映射与模型完全分离(无需注释)

答案 10 :(得分:3)

这是其他答案的后盾。这只是我管理查询字符串的一个想法。

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

答案 11 :(得分:1)

Kaleb Pederson的解决方案对我有用。我更新了ColumnAttributeTypeMapper以允许自定义属性(在同一域对象上需要两个不同的映射)和更新的属性,以便在需要派生字段和类型不同的情况下允许私有setter。

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

答案 12 :(得分:1)

对于所有使用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 :(得分:0)

    我知道这是一个相对较旧的线程,但是我想我会把在那里做的事情扔掉。

    我希望属性映射能够在全局范围内工作。您可以匹配属性名称(也称为默认值),也可以匹配类属性上的列属性。我也不想为我要映射的每个类都进行设置。这样,我创建了一个在应用启动时调用的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);
                        }
                    ));
            };
        }
    }
    

    非常简单。不知道我刚刚写这篇文章时会遇到什么问题,但是它可以工作。

    答案 14 :(得分:0)

    Kaleb试图解决的问题的简单解决方案是,如果column属性不存在,则接受属性名称:

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

    答案 15 :(得分:0)

    请注意,Dapper对象映射不区分大小写,因此可以这样命名属性:

    public class Person 
    {
        public int Person_Id { get; set; }
        public string First_Name { get; set; }
        public string Last_Name { get; set; }
    }
    

    或者保留Person类并使用PersonMap:

      public class PersonMap 
            {
                public int Person_Id { get; set; }
                public string First_Name { get; set; }
                public string Last_Name { get; set; }
                public Person Map(){
                  return new Person{
                    PersonId = Person_Id,
                    FirstName = First_Name,
                    LastName = Last_Name
                   }               
                }
            }
    

    然后,在查询结果中:

    var person = conn.Query<PersonMap>(sql).Select(x=>x.Map()).ToList();
    

    答案 16 :(得分:0)

    更简单的方法(与@Matt M的答案相同,但已更正并向默认地图添加了后备功能)

    // override TypeMapProvider to return custom map for every requested type
    Dapper.SqlMapper.TypeMapProvider = type =>
       {
           // create fallback default type map
           var fallback = new DefaultTypeMap(type);
           return new CustomPropertyTypeMap(type, (t, column) =>
           {
               var property = t.GetProperties().FirstOrDefault(prop =>
                   prop.GetCustomAttributes(typeof(ColumnAttribute))
                       .Cast<ColumnAttribute>()
                       .Any(attr => attr.Name == column));
    
               // if no property matched - fall back to default type map
               if (property == null)
               {
                   property = fallback.GetMember(column)?.Property;
               }
    
               return property;
           });
       };