爲Dapper編寫一個相似於EF的Map配置類

引言

最近在用Dapper處理Sqlite。映射模型的時候不喜歡用Attribute配置,但願用相似EF的Map來配置,因此粗略的實現了一個。git

實現

首先是主體的配置輔助類型:express

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

這裏是其中引用的一個輔助方法的實現api

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

而後是一些鏈式調用的擴展支持app

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

能夠使用如下配置代碼進行配置:this

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

結束。spa

相關文章
相關標籤/搜索