zoukankan      html  css  js  c++  java
  • 【手撸一个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;
            }
        }
    }
  • 相关阅读:
    SpringMVC 注解大全
    Maven实战--- dependencies与dependencyManagement的区别
    SpringMVC5.2.0 使用到 WebDataBinderFactory.createDataBinder 方法的类
    Spring DataBinder
    mysql 查询主键外键
    objectMapper.canSerialize SpringMVC实体转JSON字符串
    整合SSM时启动Tomcat报nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/exc/InvalidDefinitionException
    synchronizeOnSession
    init-method afterPropertiesSet BeanPostProcessor 执行顺序
    SpringMVC中重定向参数的使用以及原理解析 RedirectAttributes
  • 原文地址:https://www.cnblogs.com/diwu0510/p/10663477.html
Copyright © 2011-2022 走看看