【手撸一个ORM】第七步、SqlDataReader转实体

说明

使用Expression(表达式目录树)转Entity的文章在园子里有很多,思路也大致也一样,我在前面有篇文章对解决思路有些说明,有兴趣的小伙伴可以看下 (传送门),刚接触表达式目录树时写的,不太严谨,但思路上应该不会有误导群众的嫌疑,具体实现代码还是以本篇的为准。

关于缓存和缺陷

实体查询,如:db.Query<Student>().Include(s => s.School).ToList();这种形式,因其SQL语句由代码拼接生成,所以比较固定,因此在这里对齐进行了缓存,既将表达式目录树生成的委托保存在一个字典中,第一次生成后,后面的操作就可以直接从字典中拿来用,其效率提升还是蛮明显的。这里因为我这个方法是之前写好的,在生成缓存key的时候使用 实体名+导航属性(多个导航属性,先按名称排序,然后拼接)的方式来生成,其实更普遍的做法是使用SQL语句作为缓存的key。

按需查询(Select),在这个代码里没有进行缓存,因为之前考虑到 Select(s => new {...}) 这种生成匿名类的查询,查询的字段不固定,那肯定就无法进行缓存,但是到最后也没能实现,下面的SqlDataReaderMapper.cs中有不少无用的代码,其实就是对生成匿名类对象和dynamic对象的尝试,然而,并没有成功。那么,在现阶段,这里的Func也还是可以缓存的,因为没有了匿名类的不确定性,所以生成的SQL语句时固定的,那生成的委托自然也就可以缓存了。只是在我的代码中没有实现,请自行解决吧。

不得不说无法支持 Select(s => new {})确实是个挺遗憾的地方,如果需要按需加载,必须定义一个实体承载查询结果,如 StudentDto 之类的,增加工作量不说,灵活性也欠缺了一些。

用于保存导航属性信息的工具类

using System;

namespace MyOrm.Mappers
{
    public class IncludePropertySdrMap
    {
        public Type Type { get; set; }

        public string PropertyName { get; set; }

        public int Index { get; set; }
    }
}

用于实体查询的转换类

using MyOrm.Reflections;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;

namespace MyOrm.Mappers
{
    public class SqlDataReaderConverter<T> where T : class, new()
    {
        private static readonly Dictionary<string, Func<SqlDataReader, T>> Dict
            = new Dictionary<string, Func<SqlDataReader, T>>();

        public MyEntity Master { get; set; }

        public List<string> Includes { get; set; }

        public string Key { get; set; }

        public SqlDataReaderConverter(string[] props = null)
        {
            Master = MyEntityContainer.Get(typeof(T));

            if (props == null || props.Length == 0)
            {
                Includes = new List<string>();
                Key = typeof(T).Name;
            }
            else
            {
                Includes = props.ToList();
                Key = typeof(T).Name + "-" + string.Join("-", props.OrderBy(p => p).Distinct());
            }
        }


        #region 反射
        public T ConvertToEntity(SqlDataReader sdr)
        {
            var entity = new T();

            foreach (var property in Master.Properties)
            {
                property.PropertyInfo.SetValue(entity, sdr[property.Name]);
            }

            foreach (var include in Includes)
            {
                var prop = Master.Properties.Single(p => p.Name == include);
                if (prop != null)
                {
                    var subType = prop.PropertyInfo.PropertyType;
                    var subEntityInfo = MyEntityContainer.Get(subType);
                    var subEntity = Activator.CreateInstance(subType);

                    foreach (var subProperty in subEntityInfo.Properties)
                    {
                        if (subProperty.IsMap)
                        {
                            subProperty.PropertyInfo.SetValue(subEntity, sdr[$"{include}_{subProperty.Name}"]);
                        }
                    }

                    prop.PropertyInfo.SetValue(entity, subEntity);
                }
            }

            return entity;
        }
        #endregion

        #region 表达式目录树
        public Func<SqlDataReader, T> GetFunc(SqlDataReader sdr)
        {
            if (!Dict.TryGetValue(Key, out var func))
            {
                var sdrParameter = Expression.Parameter(typeof(SqlDataReader), "sdr");
                var memberBindings = new List<MemberBinding>();
                var subMemberMaps = new Dictionary<string, List<IncludePropertySdrMap>>();
                foreach (var include in Includes)
                {
                    subMemberMaps.Add(include, new List<IncludePropertySdrMap>());
                }

                for (var i = 0; i < sdr.FieldCount; i++)
                {
                    var fieldName = sdr.GetName(i);
                    var fieldNames = fieldName.Split('_');

                    if (fieldNames.Length == 1)
                    {
                        var property = Master.Properties.Single(p => p.Name == fieldName);
                        if (property != null)
                        {
                            var methodName = GetSdrMethodName(property.PropertyInfo.PropertyType);
                            var methodCall = Expression.Call(sdrParameter,
                                typeof(SqlDataReader).GetMethod(methodName) ?? throw new InvalidOperationException(),
                                Expression.Constant(i));

                            Expression setValueExpression;
                            if (property.PropertyInfo.PropertyType.IsGenericType &&
                                property.PropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                            {
                                setValueExpression = Expression.Convert(methodCall, property.PropertyInfo.PropertyType);
                            }
                            else
                            {
                                setValueExpression = methodCall;
                            }

                            //memberBindings.Add(Expression.Bind(property.PropertyInfo, methodCall));
                            memberBindings.Add(
                                Expression.Bind(
                                    property.PropertyInfo,
                                    Expression.Condition(
                                        Expression.TypeIs(
                                            Expression.Call(
                                                sdrParameter,
                                                typeof(SqlDataReader).GetMethod("get_Item", new[] {typeof(int)}) ??
                                                throw new InvalidOperationException(),
                                                Expression.Constant(i)),
                                            typeof(DBNull)
                                        ),
                                        Expression.Default(property.PropertyInfo.PropertyType),
                                        setValueExpression
                                    )
                                )
                            );
                        }
                    }
                    else
                    {
                        if (subMemberMaps.TryGetValue(fieldNames[0], out var list))
                        {
                            list.Add(new IncludePropertySdrMap { PropertyName = fieldNames[1], Index = i });
                        }
                    }
                }

                foreach (var include in subMemberMaps)
                {
                    var prop = Master.Properties.Single(p => p.Name == include.Key);
                    if (prop != null)
                    {
                        var subEntityInfo = MyEntityContainer.Get(prop.PropertyInfo.PropertyType);
                        var subBindingList = new List<MemberBinding>();
                        foreach (var subProperty in subEntityInfo.Properties)
                        {
                            if (subProperty.IsMap)
                            {
                                var mapper = include.Value.SingleOrDefault(v => v.PropertyName == subProperty.Name);
                                if (mapper != null)
                                {
                                    var methodName = GetSdrMethodName(subProperty.PropertyInfo.PropertyType);
                                    var methodCall = Expression.Call(
                                        sdrParameter,
                                        typeof(SqlDataReader).GetMethod(methodName) ??
                                        throw new InvalidOperationException(),
                                        Expression.Constant(mapper.Index));

                                    Expression setValueExpression;
                                    if (subProperty.PropertyInfo.PropertyType.IsGenericType &&
                                        subProperty.PropertyInfo.PropertyType.GetGenericTypeDefinition() ==
                                        typeof(Nullable<>))
                                    {
                                        setValueExpression = Expression.Convert(methodCall,
                                            subProperty.PropertyInfo.PropertyType);
                                    }
                                    else
                                    {
                                        setValueExpression = methodCall;
                                    }

                                    subBindingList.Add(
                                        Expression.Bind(
                                            subProperty.PropertyInfo,
                                            Expression.Condition(
                                                Expression.TypeIs(
                                                    Expression.Call(
                                                        sdrParameter,
                                                        typeof(SqlDataReader).GetMethod("get_Item",
                                                            new[] {typeof(int)}) ??
                                                        throw new InvalidOperationException(),
                                                        Expression.Constant(mapper.Index)),
                                                    typeof(DBNull)
                                                ),
                                                Expression.Default(subProperty.PropertyInfo.PropertyType),
                                                setValueExpression
                                            )
                                        )
                                    );
                                }
                            }

                            var subInitExpression = Expression.MemberInit(
                                Expression.New(prop.PropertyInfo.PropertyType),
                                subBindingList);
                            memberBindings.Add(Expression.Bind(prop.PropertyInfo, subInitExpression));
                        }
                    }
                }

                var initExpression = Expression.MemberInit(Expression.New(typeof(T)), memberBindings);
                func = Expression.Lambda<Func<SqlDataReader, T>>(initExpression, sdrParameter).Compile();
                Dict.Add(Key, func);
            }
            else
            {
                //Console.WriteLine("应用了缓存");
            }
            return func;
        }

        public T ConvertToEntity2(SqlDataReader sdr)
        {
            if (sdr.HasRows)
            {
                var func = GetFunc(sdr);
                if (sdr.Read())
                {
                    return func.Invoke(sdr);
                }
            }
            return default(T);
        }

        public List<T> ConvertToEntityList(SqlDataReader sdr)
        {
            var result = new List<T>();
            if (!sdr.HasRows)
            {
                return result;
            }

            var func = GetFunc(sdr);
            do
            {
                while (sdr.Read())
                {
                    result.Add(func(sdr));
                }
            } while (sdr.NextResult());
            

            return result;
        }

        public List<T> ConvertToEntityList2(SqlDataReader sdr)
        {
            var result = new List<T>();
            while (sdr.Read())
            {
                result.Add(ConvertToEntity2(sdr));
            }
            return result;
        }

        /// <summary>
        /// 获取SqlDataReader转实体属性时调用的方法名
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private string GetSdrMethodName(Type type)
        {
            var realType = GetRealType(type);
            string methodName;

            if (realType == typeof(string))
            {
                methodName = "GetString";
            }
            else if (realType == typeof(int))
            {
                methodName = "GetInt32";
            }
            else if (realType == typeof(DateTime))
            {
                methodName = "GetDateTime";
            }
            else if (realType == typeof(decimal))
            {
                methodName = "GetDecimal";
            }
            else if (realType == typeof(Guid))
            {
                methodName = "GetGuid";
            }
            else if (realType == typeof(bool))
            {
                methodName = "GetBoolean";
            }
            else
            {
                throw new ArgumentException($"不受支持的类型:{type.FullName}");
            }

            return methodName;
        }

        private static Type GetRealType(Type type)
        {
            var realType = type.IsGenericType &&
                           type.GetGenericTypeDefinition() == typeof(Nullable<>)
                ? type.GetGenericArguments()[0]
                : type;

            return realType;
        }
        #endregion
    }
}

用于按需查询的转换类

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using MyOrm.Expressions;
using MyOrm.Reflections;

namespace MyOrm.Mappers
{
    public class SqlDataReaderMapper
    {
        public Func<SqlDataReader, TTarget> ResolveClass<TTarget>(SqlDataReader sdr)
        {
            if (!sdr.HasRows) return null;

            var sdrParameter = Expression.Parameter(typeof(SqlDataReader), "sdr");
            var memberBindings = new List<MemberBinding>();
            var subMemberMaps = new Dictionary<string, List<IncludePropertySdrMap>>();

            var masterEntity = MyEntityContainer.Get(typeof(TTarget));


            for (var i = 0; i < sdr.FieldCount; i++)
            {
                var fieldName = sdr.GetName(i);
                var fieldNames = fieldName.Split("__");

                if (fieldNames.Length == 1)
                {
                    var property = masterEntity.Properties.Single(p => p.Name == fieldName);
                    if (property != null)
                    {
                        var methodName = GetSdrMethodName(property.PropertyInfo.PropertyType);
                        var methodCall = Expression.Call(sdrParameter,
                            typeof(SqlDataReader).GetMethod(methodName) ?? throw new InvalidOperationException(),
                            Expression.Constant(i));

                        Expression setValueExpression;
                        if (property.PropertyInfo.PropertyType.IsGenericType &&
                            property.PropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                        {
                            setValueExpression = Expression.Convert(methodCall, property.PropertyInfo.PropertyType);
                        }
                        else
                        {
                            setValueExpression = methodCall;
                        }

                        //memberBindings.Add(Expression.Bind(property.PropertyInfo, methodCall));
                        memberBindings.Add(
                            Expression.Bind(
                                property.PropertyInfo,
                                Expression.Condition(
                                    Expression.TypeIs(
                                        Expression.Call(
                                            sdrParameter,
                                            typeof(SqlDataReader).GetMethod("get_Item", new[] { typeof(int) }) ??
                                            throw new InvalidOperationException(),
                                            Expression.Constant(i)),
                                        typeof(DBNull)
                                    ),
                                    Expression.Default(property.PropertyInfo.PropertyType),
                                    setValueExpression
                                )
                            )
                        );
                    }
                }
                else
                {
                    if (subMemberMaps.TryGetValue(fieldNames[0], out var list))
                    {
                        list.Add(new IncludePropertySdrMap { PropertyName = fieldNames[1], Index = i });
                    }
                }
            }

            foreach (var include in subMemberMaps)
            {
                var prop = masterEntity.Properties.Single(p => p.Name == include.Key);
                if (prop != null)
                {
                    var subEntityInfo = MyEntityContainer.Get(prop.PropertyInfo.PropertyType);
                    var subBindingList = new List<MemberBinding>();
                    foreach (var subProperty in subEntityInfo.Properties)
                    {
                        if (subProperty.IsMap)
                        {
                            var mapper = include.Value.SingleOrDefault(v => v.PropertyName == subProperty.Name);
                            if (mapper != null)
                            {
                                var methodName = GetSdrMethodName(subProperty.PropertyInfo.PropertyType);
                                var methodCall = Expression.Call(
                                    sdrParameter,
                                    typeof(SqlDataReader).GetMethod(methodName) ??
                                    throw new InvalidOperationException(),
                                    Expression.Constant(mapper.Index));

                                Expression setValueExpression;
                                if (subProperty.PropertyInfo.PropertyType.IsGenericType &&
                                    subProperty.PropertyInfo.PropertyType.GetGenericTypeDefinition() ==
                                    typeof(Nullable<>))
                                {
                                    setValueExpression = Expression.Convert(methodCall,
                                        subProperty.PropertyInfo.PropertyType);
                                }
                                else
                                {
                                    setValueExpression = methodCall;
                                }

                                subBindingList.Add(
                                    Expression.Bind(
                                        subProperty.PropertyInfo,
                                        Expression.Condition(
                                            Expression.TypeIs(
                                                Expression.Call(
                                                    sdrParameter,
                                                    typeof(SqlDataReader).GetMethod("get_Item",
                                                        new[] { typeof(int) }) ??
                                                    throw new InvalidOperationException(),
                                                    Expression.Constant(mapper.Index)),
                                                typeof(DBNull)
                                            ),
                                            Expression.Default(subProperty.PropertyInfo.PropertyType),
                                            setValueExpression
                                        )
                                    )
                                );
                            }
                        }

                        var subInitExpression = Expression.MemberInit(
                            Expression.New(prop.PropertyInfo.PropertyType),
                            subBindingList);
                        memberBindings.Add(Expression.Bind(prop.PropertyInfo, subInitExpression));
                    }
                }
            }

            var initExpression = Expression.MemberInit(Expression.New(typeof(TTarget)), memberBindings);
            return Expression.Lambda<Func<SqlDataReader, TTarget>>(initExpression, sdrParameter).Compile();
        }

        public Func<SqlDataReader, TTarget> ResolveConstant<TTarget>(SqlDataReader sdr, string fieldName = "")
        {
            var type = typeof(TTarget);
            var sdrParameter = Expression.Parameter(typeof(SqlDataReader), "sdr");
            MethodCallExpression callExpression;
            if (string.IsNullOrWhiteSpace(fieldName))
            {
                var methodName = GetSdrMethodName(type);
                callExpression = Expression.Call(sdrParameter, typeof(SqlDataReader).GetMethod(methodName),
                    Expression.Constant(0));
                return Expression.Lambda<Func<SqlDataReader, TTarget>>(callExpression, sdrParameter).Compile();
            }
            else
            {
                callExpression = Expression.Call(sdrParameter,
                    typeof(SqlDataReader).GetMethod("get_item", new[] {typeof(string)}),
                    Expression.Constant(fieldName));
                var convertExpression = Expression.Convert(callExpression, type);
                return Expression.Lambda<Func<SqlDataReader, TTarget>>(convertExpression, sdrParameter).Compile();
            }
        }

        public Func<SqlDataReader, dynamic> Resolve2(SqlDataReader sdr)
        {
            var sdrParameter = Expression.Parameter(typeof(SqlDataReader), "sdr");
            var newExpression = Expression.New(typeof(System.Dynamic.ExpandoObject));
            var convertExpression = Expression.Convert(newExpression, typeof(IDictionary<string, object>));

            var memberBindings = new List<MemberBinding>();
            for(var i = 0; i < sdr.FieldCount; i++)
            {
                var nameExpression = Expression.Call(sdrParameter, typeof(SqlDataReader).GetMethod("GetName"), Expression.Constant(i));
                //var itemExpression = Expression.Call(
                //            sdrParameter,
                //            typeof(SqlDataReader).GetMethod("get_Item",
                //                new[] { typeof(int) }) ??
                //            throw new InvalidOperationException(),
                //            Expression.Constant(i));
                //var type = Expression.Constant(Expression.Call(sdrParameter, typeof(SqlDataReader).GetMethod("GetFieldType", new[] { typeof(int) }), Expression.Constant(i)));
                var valueExpression = Expression.Call(sdrParameter, typeof(SqlDataReader).GetMethod("GetValue", new[] { typeof(int) }), Expression.Constant(i));

                //var callExpression = Expression.Call(
                //    convertExpression,
                //    typeof(IDictionary<string, object>).GetMethod("Add"),
                //    nameExpression, valueExpression);

                Expression.Call(newExpression,
                    typeof(System.Dynamic.ExpandoObject).GetMethod("TryAdd", new[] { typeof(string), typeof(object) }),
                    nameExpression,
                    valueExpression);
            }
            var initExpression = Expression.MemberInit(newExpression);
            var lambda = Expression.Lambda<Func<SqlDataReader, dynamic>>(initExpression, sdrParameter);
            return lambda.Compile();
        }

        public List<T> ConvertToList<T>(SqlDataReader sdr)
        {
            var result = new List<T>();
            if (!sdr.HasRows)
            {
                return result;
            }

            var func = typeof(T).IsClass && typeof(T) != typeof(string) ? ResolveClass<T>(sdr) : ResolveConstant<T>(sdr);

            if (func == null)
            {
                return result;
            }

            while (sdr.Read())
            {
                result.Add(func.Invoke(sdr));
            }

            return result;
        }

        public T ConvertToEntity<T>(SqlDataReader sdr)
        {
            if (!sdr.HasRows)
            {
                return default(T);
            }

            var func = typeof(T).IsClass ? ResolveClass<T>(sdr) : ResolveConstant<T>(sdr);

            if (func == null)
            {
                return default(T);
            }

            if (sdr.Read())
            {
                return func.Invoke(sdr);
            }

            return default(T);
        }

        public List<dynamic> ConvertToList(SqlDataReader sdr)
        {
            var result = new List<dynamic>();
            if (!sdr.HasRows)
            {
                return result;
            }

            var func = Resolve2(sdr);

            if (func == null)
            {
                return result;
            }

            while (sdr.Read())
            {
                result.Add(func.Invoke(sdr));
            }

            return result;
        }

        public dynamic ConvertToEntity(SqlDataReader sdr)
        {
            if (!sdr.HasRows)
            {
                return null;
            }

            var func = Resolve2(sdr);

            if (func != null && sdr.Read())
            {
                return func.Invoke(sdr);
            }

            return null;
        }

        private string GetSdrMethodName(Type type)
        {
            var realType = GetRealType(type);
            string methodName;

            if (realType == typeof(string))
            {
                methodName = "GetString";
            }
            else if (realType == typeof(int))
            {
                methodName = "GetInt32";
            }
            else if (realType == typeof(DateTime))
            {
                methodName = "GetDateTime";
            }
            else if (realType == typeof(decimal))
            {
                methodName = "GetDecimal";
            }
            else if (realType == typeof(Guid))
            {
                methodName = "GetGuid";
            }
            else if (realType == typeof(bool))
            {
                methodName = "GetBoolean";
            }
            else
            {
                throw new ArgumentException($"不受支持的类型:{type.FullName}");
            }

            return methodName;
        }

        public Type ConvertSdrFieldToType(SqlDataReader sdr)
        {
            return null;
        }

        private static Type GetRealType(Type type)
        {
            var realType = type.IsGenericType &&
                           type.GetGenericTypeDefinition() == typeof(Nullable<>)
                ? type.GetGenericArguments()[0]
                : type;

            return realType;
        }
    }
}

 

posted @ 2019-04-06 23:48  没追求的码农  阅读(769)  评论(1编辑  收藏  举报