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

我是Dapper Micro ORM的新手。 到目前为止,我能够使用它简单的ORM相关的东西,但我无法映射数据库列名称的类属性。 例如:

我有如下的数据库表:

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

我有class级叫人

 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 )的列名与对象的属性?

任何线索或帮助将不胜感激。

这工作正常:

 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属性的function ,我不反对增加对它的支持,只要我们不引入依赖关系。

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类提供了标准的function,可以用来改变这种行为:

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

并且,就可以很容易地创build一个自定义types映射器,如果它们存在,将会自动使用这些属性,否则将回退到标准行为:

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

这意味着我们现在可以使用属性轻松地支持需要映射的types:

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

下面是完整的源代码的要点 。

有一段时间,以下应该工作:

 Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; 

这是一个简单的解决scheme,不需要属性允许您将基础结构代码保留在您的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]))); 

我使用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而您的对象的特性是ID您可以在查询中select PERSON_ID as Id ... ,Dapper将按预期方式select它。

混乱与映射是边界移动到真正的ORM土地。 而不是与它作斗争,并保持Dapper的真正简单(快速)的forms,只是稍微修改你的SQL像这样:

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

采取目前在Dapper 1.42上的Dappertesting 。

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

如果您正在使用.NET 4.5.1或更高版本检出Dapper.FluentColumnMapping映射LINQ样式。 它可以让你完全分离你的模型的数据库映射(不需要注释)

对于所有使用Dapper 1.12的人来说,下面是你需要做的事情:

  • 添加一个新的列属性类:
  •   [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property] public class ColumnAttribute : Attribute { public string Name { get; set; } public ColumnAttribute(string name) { this.Name = name; } } 
  • search这一行:
  •  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); }); 

    Kaleb Pederson的解决scheme为我工作。 我更新了ColumnAttributeTypeMapper以允许自定义属性(需要在同一个域对象上有两个不同映射)和更新的属性,以便在需要派生字段且types不同的情况下允许私有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)) }) { // } } 

    这是其他答案的小猪支持。 这只是我pipe理查询string的一个想法。

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

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