我是Dapper微型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)与对象属性的列名?
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))
})
{
}
}
这意味着我们现在可以使用属性轻松支持需要map的类型:
Dapper.SqlMapper.SetTypeMap(
typeof(MyModel),
new ColumnAttributeTypeMapper<MyModel>());
下面是完整源代码的要点。
取自Dapper测试,目前在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);
Helper类从Description属性中获取名称(我个人使用类似@kalebs的Column示例)
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; }
}
这是在逃避其他答案。这只是我对管理查询字符串的一个想法。
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;
}