为Dapper编写一个类似于EF的Map配置类

时间:2023-03-09 02:02:02
为Dapper编写一个类似于EF的Map配置类

引言

最近在用Dapper处理Sqlite。映射模型的时候不喜欢用Attribute配置,希望用类似EF的Map来配置,所以粗略的实现了一个。

实现

首先是主体的配置辅助类型:

using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using Dapper;
using DRapid.Utility.Linq.Expressions; namespace DRapid.Utility.Dapper.Map
{
public class InstanceMapper<T> : InstanceMapper
{
protected InstanceMapper()
: base(typeof(T))
{ } public static InstanceMapper<T> Config()
{
return new InstanceMapper<T>();
} public void MapColumn(string columnName, Expression<Func<T, object>> propertySelector)
{
var propertyName = ExpressionHelper.ReadMemberName(propertySelector);
MapColumn(columnName, propertyName);
}
} public class InstanceMapper : SqlMapper.ITypeMap
{
protected InstanceMapper(Type type)
{
_map = new CustomPropertyTypeMap(type, GetProperty);
_mapDic = new ConcurrentDictionary<string, PropertyInfo>();
_instanceType = type;
} private CustomPropertyTypeMap _map;
private Type _instanceType;
private ConcurrentDictionary<string, PropertyInfo> _mapDic; public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
return _map.FindConstructor(names, types);
} public ConstructorInfo FindExplicitConstructor()
{
return _map.FindExplicitConstructor();
} public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
return _map.GetConstructorParameter(constructor, columnName);
} public SqlMapper.IMemberMap GetMember(string columnName)
{
return _map.GetMember(columnName);
} public void MapColumn(string columnName, string propertyName)
{
_mapDic.AddOrUpdate(columnName,
key => _instanceType.GetProperty(propertyName),
(key, pro) => _instanceType.GetProperty(propertyName));
} private PropertyInfo GetProperty(Type type, string columnName)
{
PropertyInfo propertyInfo;
var result = _mapDic.TryGetValue(columnName, out propertyInfo);
return result ? propertyInfo : null;
} public void Apply()
{
SqlMapper.SetTypeMap(_instanceType, this);
} public static InstanceMapper Config(Type type)
{
return new InstanceMapper(type);
}
}
}

这里是其中引用的一个辅助方法的实现

        public static string ReadMemberName<T>(Expression<Func<T, object>> expression)
{
var body = expression.Body;
/*这里需要考虑转型表达式*/
if (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression) body).Operand;
Trace.Assert(body.NodeType == ExpressionType.MemberAccess, "表达式必须是成员访问或者是带转型的成员访问");
var accessMember = (MemberExpression) body;
return accessMember.Member.Name;
}

然后是一些链式调用的扩展支持

using System;
using System.Linq.Expressions; namespace DRapid.Utility.Dapper.Map
{
public static class InstanceMapperExtension
{
public static InstanceMapper<T> Use<T>(this InstanceMapper<T> mapper, string columnName, string propertyName)
{
mapper.MapColumn(columnName, propertyName);
return mapper;
} public static InstanceMapper<T> Use<T>(this InstanceMapper<T> mapper, string columnName,
Expression<Func<T, object>> propertySelector)
{
mapper.MapColumn(columnName, propertySelector);
return mapper;
}
}
}

调用

由于有静态访问入口,所以配置一般分布在各个类的静态构造函数中,从而防止重复配置。

所以,对于一个dto类型:

        public class PointInfo
{
public byte[] Detail { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public int Count { get; set; }
}

可以使用以下配置代码进行配置:

        InstanceMapper<PointInfo>.Config()
.Use("STATLG", s => s.Longitude)
.Use("STATLA", s => s.Latitude)
.Use("FREQUERYCOUNT", s => s.Count)
.Use("FREQDB", s => s.Detail)
.Apply();

结束。