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

结束。

posted @ 2016-06-03 14:06  LibraJM  阅读(1329)  评论(1编辑  收藏  举报