【手撸一个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 == )
{
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;
}
}
}
【手撸一个ORM】第七步、SqlDataReader转实体的更多相关文章
- 【手撸一个ORM】第一步、实体约定和描述
一.约定 数据实体必须实现 IEntity 接口,该接口定义了一个int类型的Id属性,既每个实体必须有一个名称为Id的自增主键. 若数据表的主键列名称不是Id,可以通过 [MyKey("主 ...
- 【手撸一个ORM】第九步、orm默认配置类 MyDbConfiguration,一次配置,简化实例化流程
这个实现比较简单,事实上可配置的项目很多,如有需要,请读者自行扩展 using System; namespace MyOrm { public class MyDbConfiguration { p ...
- 【手撸一个ORM】MyOrm的使用说明
[手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...
- 【手撸一个ORM】第六步、对象表达式解析和Select表达式解析
说明 一个Orm自然不仅仅包含条件表达式,还会有如下的场景: OrderBy(s => s.StudentName) Select<StudentDto>(s => new S ...
- 【手撸一个ORM】第四步、Expression(表达式目录树)扩展
到这里,Orm的基架已经搭起来了,接下来就是激动人心的部分,表达式目录树转Sql语句,SqlDataReader转数据实体等等,但是在这之前,我们需要扩展下表达式目录树的方法,以方便后面的相关操作. ...
- 【手撸一个ORM】第十步、数据操作工具类 MyDb
说明 其实就是数据库操作的一些封装,很久不用SqlCommand操作数据库了,看了点园子里的文章就直接上手写了,功能上没问题,但写法上是否完美高效无法保证,建议有需要的朋友自己重写,当然如果能把最佳实 ...
- 【手撸一个ORM】第三步、SQL语句构造器和SqlParameter封装
既然是数据库工具,自然少不了增删改查的sql语句,在这里将这些常用SQL拼接操作集成到 [SqlServerBuilder.cs] 当中,方便后面调用. 近几年在项目中一直使用Dapper操作数据库, ...
- 【手撸一个ORM】第五步、Expression(表达式目录树)转换为Where子句
说明 在SQL中,查询.修改比较常用到WHERE子句,在这里根据使用场景不同,定义了两个类,一个用于查询,一个用于修改(插入)操作.原因是: 查询操作支持一级导航属性查询,如student.Schoo ...
- 【手撸一个ORM】第八步、查询工具类
一.实体查询 using MyOrm.Commons; using MyOrm.DbParameters; using MyOrm.Expressions; using MyOrm.Mappers; ...
随机推荐
- 【LeetCode】012. Integer to Roman
Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 t ...
- 系列文章--突袭HTML5之Javascript
突袭HTML5之Javascript API扩展5 - 其他扩展 突袭HTML5之Javascript API扩展4 - 拖拽 突袭HTML5之Javascript API扩展3 - 本地存储 突袭H ...
- 洛谷【P2003】平板
我对状态空间的理解:https://www.cnblogs.com/AKMer/p/9622590.html 题目传送门:https://www.luogu.org/problemnew/show/P ...
- 如何在开启了log-bin的MySQL Server中创建FUNCTION
在MySQL主从复制机器的master的数据库中创建function,报出如下错误: Error Code: 1418. This function has none of DETERMINISTIC ...
- 【转】 Pro Android学习笔记(四二):Fragment(7):切换效果
目录(?)[-] 利用setTransition 利用setCustomAnimations 通过ObjectAnimator自定义动态效果 程序代码的编写 利用fragment transactio ...
- 【转】 Pro Android学习笔记(三九):Fragment(4):基础小例子-续
目录(?)[-] Step 3实现简介显示类DetailFragment 创建实例 编写所需的生命周期代码 Step 4实现showDetailint index如何管理fragment fragme ...
- 【转】Pro Android学习笔记(十八):用户界面和控制(6):Adapter和AdapterView
目录(?)[-] SimpleCursorAdapter 系统预置的layout ArrayAdapter 动态数据增插删排序 自定义TextView风格 其他Adapter AdapterView不 ...
- [51nod1181]质数中的质数(素数筛法)
解题关键: 注意下标 #include<bits/stdc++.h> #define maxn 10000002 using namespace std; typedef long lon ...
- 在重命名SqlServer数据库时,报5030错误的解决办法
数据库不能重名名5030的错误,其实很简单原因就是有应用程序正在占用这个连接,使用这样一行命令就可以查询出正在占用的连接 use master select spid from master.dbo. ...
- php学习笔记-php中把浮点数转化为整数
在php中有时候会遇到比如 14.6%3这种操作,php是会先把14.6转化为整数再做其它的操作,那么这个转化为整数的操作是floor(14.6)还是ceil(14.6)还是round(14.6)呢? ...