ORM 框架更好的加载器






3.50/5 (4投票s)
使用Reflection.Emit生成加载器类,以实现更快的操作

引言
现在似乎有很多ORM框架可用。 我研究了其中的几个,主要是开源的。 我注意到它们中的大多数都严重依赖反射来在运行时从数据源加载属性值。
在“正常”类型的应用程序中,当处理许多需要从运行到运行持续存在的简单类型的对象时,ORM框架可以极大地提高生产力。 但是,在我的业务领域(金融服务)中,实体的数量及其复杂的相互关系使得加载和保存对象的过程很慢。
我不禁想知道为什么没有一个ORM框架(据我所知)使用Reflection.Emit
在运行时生成“加载器”和“保存器”类,从而消除对反射的需求。
开始使用ORMReflectionEmit
现在我不想再编写另一个ORM框架! 因此,我编写了一个原型来测试使用Reflection.Emit
在运行时为给定类型生成“加载器”类的想法。 ORM框架可以在其类创建和加载部分中使用此“加载器”。
我以前考虑在.NET 1.1中执行此操作,但是所有的装箱,拆箱和类型转换都使我感到头晕,但是现在我们在.NET 2.0中具有泛型,该解决方案变得更加容易,更加优雅并且运行更快。
我首先设计了一个加载器类,如下面的代码所示
class PortfolioDataLoader
{
public PortfolioDataLoader()
{
}
public void LoadData(Portfolio instance, IDataRecord record)
{
instance.Id = record.GetInt32(0);
instance.Code = record.GetString(1);
instance.Description = record.GetString(2);
instance.CurrentHolding = record.GetDecimal(3);
instance.DateOpened = record.GetDateTime(4);
}
}
为了进行泛型的加载器类重构,我创建了IObjectDataLoader<T>
接口并在我的加载器类上实现了它。 重写如下
public interface IObjectDataLoader<T>
where T: new()
{
void LoadData(T instance, IDataRecord record);
}
class PortfolioDataLoader: IObjectDataLoader<Portfolio>
{
public PortfolioDataLoader()
{
}
public void LoadData(Portfolio instance, IDataRecord record)
{
instance.Id = record.GetInt32(0);
instance.Code = record.GetString(1);
instance.Description = record.GetString(2);
instance.CurrentHolding = record.GetDecimal(3);
instance.DateOpened = record.GetDateTime(4);
}
}
现在要做的就是在运行时使用Reflection.Emit
生成此类。 本质上,对于每种给定类型,该类中唯一不同的部分是LoadData
方法的实现。 最终,我想使用如下面的代码段所示的加载器
// generate the loader for Portfolio
IObjectDataLoader<Portfolio> loader =
DataLoader.GenerateObjectDataLoader<Portfolio>(mappings);
// storage
List<Portfolio> list = new List<Portfolio>();
using(SqlDataReader reader = cmd.ExecuteReader(...))
{
while(reader.Read())
{
list.Add( DataLoader.CreateInstance<Portfolio>(loader, reader) );
}
}
出于此原型的目的,我做了一些假设
- 数据选择并通过
SqlDataReader
提供的数据字段顺序是不可变的,因此可以使用字段索引而不是名称来调用每种数据类型的相应reader.GetXXXX(int)
方法。 这进一步加快了加载过程,因为避免了查找每个给定字段名称的索引。 - 不希望出现
null
字段值。 还有很多工作要做才能做对,但是可以做到。 IDataRecord
接口的GetXXXX(int)
方法使用XXXX
是其返回类型的数据类型名称的模式命名。- 我创建了一个高度简化的映射类,以描述类属性与用于从数据读取器中检索数据的索引之间的映射。
此外,该原型不包含完整性检查或错误处理。
我必须承认我有点“作弊”,因为Reflection.Emit
文档有些稀缺,所以我手工编写了一些示例实现,并使用“ildasm.exe”工具(随.NET SDK一起提供)对它们进行了反汇编,以查看生成了什么IL。 然后,我在代码生成代码中向后工作以生成相同的IL。 这是一种学习方式,不是吗? :-)
DataLoader.GenerateObjectDataLoader<T>(ORMapping[])
方法根据提供的映射为给定类型T
生成加载器类。 最值得注意的是,生成LoadData
方法实现的代码如下
foreach (ORMapping mapping in mappings)
{
// get the property on T
PropertyInfo propertyInfo = type.GetProperty
(mapping.PropertyName, BindingFlags.Instance | BindingFlags.Public);
// get the data type of this property
string dataTypeName = propertyInfo.PropertyType.Name;
// luckily, most of the methods on IDataRecord following
// the GetDataTypeName(int) name pattern
// so we can concat the type name and get the corresponding
// method for that data type :-)
MethodInfo dataRecordMethod = typeof(IDataRecord).GetMethod
("Get" + dataTypeName, new Type[] { typeof(int) });
ilGen.Emit(OpCodes.Ldarg_1); // instance
ilGen.Emit(OpCodes.Ldarg_2); // record
ilGen.Emit(OpCodes.Ldc_I4, mapping.FieldIndex); // int
ilGen.Emit(OpCodes.Callvirt, dataRecordMethod); // GetDataTypeName(int)
ilGen.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod()); // T.set_XXXX(xxxx)
ilGen.Emit(OpCodes.Nop);
}
结论
可以做到! 现在,挑战在于采用现有的ORM框架并替换加载器位,哦,还要编写保存器位。
历史
- 2006年2月10日:首次发布