【手撸一个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; } } }