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

时间:2023-12-06 11:10:08

说明

使用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 == )
{
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 = ; i < sdr.FieldCount; i++)
{
var fieldName = sdr.GetName(i);
var fieldNames = fieldName.Split('_'); if (fieldNames.Length == )
{
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[], out var list))
{
list.Add(new IncludePropertySdrMap { PropertyName = fieldNames[], 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()[]
: 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 = ; i < sdr.FieldCount; i++)
{
var fieldName = sdr.GetName(i);
var fieldNames = fieldName.Split("__"); if (fieldNames.Length == )
{
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[], out var list))
{
list.Add(new IncludePropertySdrMap { PropertyName = fieldNames[], 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());
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 = ; 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()[]
: type; return realType;
}
}
}