来一点反射,再来一点Emit —— 极度简化Entity!
在前一篇文章《没有ORM或代码生成数据就不能持久化了? - 用范型技术代替代码生成!》中,Teddy尝试运用泛型极大简化了一个轻量级持久化框架对代码生成的依赖,并且为了保证性能,整个持久化组件没有使用反射。在本文中,Teddy将在保证性能的基础上,加一点反射和加一点Emit,从而进一步简化Entity的定义和使用,当然也就进一步降低了组件对传统代码生成的依赖。读者可以对比前文阅读本文,看看改进的效果。 内容绝对精彩,不容错过哟!
来一点反射
在之前方案的Entity定义中,每个具体的Entity类必须从基类Entity<EntityType>继承,但是Entity<EntityType>只是提供了一组公共方法,并不涉及Entity的构造过程。我们还是把之前的Entity示例代码再列一下:using System;
namespace Ilungasoft.Helper.TestApp.DomainObject
{
public class About : Ilungasoft.Helper.Data.Entity<About>
{
public About()
{
keyValues.Add("ID", DefaultValue<int>());
keyValues.Add("Title", DefaultValue<string>());
keyValues.Add("Content", DefaultValue<string>());
keyValues.Add("Deletable", DefaultValue<bool>());
keyValues.Add("Order", DefaultValue<int>());
}
public int ID
{
get { return (int)keyValues["ID"]; }
set { keyValues["ID"] = value; }
}
public string Title
{
get { return (string)keyValues["Title"]; }
set { keyValues["Title"] = value; }
}
public string Content
{
get { return (string)keyValues["Content"]; }
set { keyValues["Content"] = value; }
}
public bool Deletable
{
get { return (bool)keyValues["Deletable"]; }
set { keyValues["Deletable"] = value; }
}
public int Order
{
get { return (int)keyValues["Order"]; }
set { keyValues["Order"] = value; }
}
}
}
namespace Ilungasoft.Helper.TestApp.DomainObject
{
public class About : Ilungasoft.Helper.Data.Entity<About>
{
public About()
{
keyValues.Add("ID", DefaultValue<int>());
keyValues.Add("Title", DefaultValue<string>());
keyValues.Add("Content", DefaultValue<string>());
keyValues.Add("Deletable", DefaultValue<bool>());
keyValues.Add("Order", DefaultValue<int>());
}
public int ID
{
get { return (int)keyValues["ID"]; }
set { keyValues["ID"] = value; }
}
public string Title
{
get { return (string)keyValues["Title"]; }
set { keyValues["Title"] = value; }
}
public string Content
{
get { return (string)keyValues["Content"]; }
set { keyValues["Content"] = value; }
}
public bool Deletable
{
get { return (bool)keyValues["Deletable"]; }
set { keyValues["Deletable"] = value; }
}
public int Order
{
get { return (int)keyValues["Order"]; }
set { keyValues["Order"] = value; }
}
}
}
以上代码有什么不爽的地方呢?首先就是About()这个构造函数。为了避免反射,所以通过在构造函数中显式指定属性名称和类型。但是,很显然,在下面的属性定义中,已经指定过属性及其类型了。指定了两遍,说明存在冗余,也就有必要改进。那就让我们在基类Entity<EntityType>中通过反射来获取属性信息好了。这样一来,About这个类可以被简化为:
using System;
namespace Ilungasoft.Helper.TestApp.DomainObject
{
public class About : Ilungasoft.Helper.Data.Entity<About>
{
public About() : base()
{
}
public int ID
{
get { return (int)keyValues["ID"]; }
set { keyValues["ID"] = value; }
}
public string Title
{
get { return (string)keyValues["Title"]; }
set { keyValues["Title"] = value; }
}
public string Content
{
get { return (string)keyValues["Content"]; }
set { keyValues["Content"] = value; }
}
public bool Deletable
{
get { return (bool)keyValues["Deletable"]; }
set { keyValues["Deletable"] = value; }
}
public int Order
{
get { return (int)keyValues["Order"]; }
set { keyValues["Order"] = value; }
}
}
}
namespace Ilungasoft.Helper.TestApp.DomainObject
{
public class About : Ilungasoft.Helper.Data.Entity<About>
{
public About() : base()
{
}
public int ID
{
get { return (int)keyValues["ID"]; }
set { keyValues["ID"] = value; }
}
public string Title
{
get { return (string)keyValues["Title"]; }
set { keyValues["Title"] = value; }
}
public string Content
{
get { return (string)keyValues["Content"]; }
set { keyValues["Content"] = value; }
}
public bool Deletable
{
get { return (bool)keyValues["Deletable"]; }
set { keyValues["Deletable"] = value; }
}
public int Order
{
get { return (int)keyValues["Order"]; }
set { keyValues["Order"] = value; }
}
}
}
注意“: base()”,构造函数调用基类构造函数来构造。那么,基类Entity<EntityType>会有怎样的改动呢?
public class Entity<EntityType> : IEntity
where EntityType : Entity<EntityType>, new()
{
protected static Dictionary<string, object> keyValuesTemplate = null;
/// <summary>
/// Initializes a new instance of the <see cref="T:Entity<EntityType>"/> class.
/// </summary>
public Entity()
{
if (keyValuesTemplate == null)
{
keyValuesTemplate = new Dictionary<string, object>();
PropertyInfo[] pis = typeof(EntityType).GetProperties();
foreach (PropertyInfo pi in pis)
{
keyValuesTemplate.Add(pi.Name, this.GetType().GetMethod("DefaultValue", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(pi.PropertyType).Invoke(this, null));
}
}
keyValues = new Dictionary<string, object>(keyValuesTemplate);
}
protected Dictionary<string, object> keyValues;
/// <summary>
/// Defaults the value.
/// </summary>
/// <returns></returns>
protected object DefaultValue<MemberType>()
{
if (typeof(MemberType) == typeof(string))
{
return string.Empty;
}
return default(MemberType);
}
//省略其他代码。。。
}
where EntityType : Entity<EntityType>, new()
{
protected static Dictionary<string, object> keyValuesTemplate = null;
/// <summary>
/// Initializes a new instance of the <see cref="T:Entity<EntityType>"/> class.
/// </summary>
public Entity()
{
if (keyValuesTemplate == null)
{
keyValuesTemplate = new Dictionary<string, object>();
PropertyInfo[] pis = typeof(EntityType).GetProperties();
foreach (PropertyInfo pi in pis)
{
keyValuesTemplate.Add(pi.Name, this.GetType().GetMethod("DefaultValue", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(pi.PropertyType).Invoke(this, null));
}
}
keyValues = new Dictionary<string, object>(keyValuesTemplate);
}
protected Dictionary<string, object> keyValues;
/// <summary>
/// Defaults the value.
/// </summary>
/// <returns></returns>
protected object DefaultValue<MemberType>()
{
if (typeof(MemberType) == typeof(string))
{
return string.Empty;
}
return default(MemberType);
}
//省略其他代码。。。
}
上面的代码还是挺有意思的,让我来给您解释一二。
首先,基类构造函数只在第一次构造About时通过反射构造keyValues对象的模版,之后的实例化只是简单的Copy改模版从而将反射对性能的影响降至最低。
接着,注意DefaultValue<MemberType>()这个函数,该函数用于指定属性的缺省值,是利用了C#2.0新关键字default来实现的。default关键字真的极大方便了我们设置缺省值,原来一般只能自己写NullObject,现在只需default(Type)就行。只不过,default关键字是通过一定的泛型机制实现的,也就是说它的参数Type必须是一个类型声明符,而不是一个Type类型的实例。这会有什么问题呢?原来在具体的Entity类的构造函数中显式指定的话没问题,因为可以直接指定类型声明符,如:“DefaultValue<int>()”,但是,如果改成通过利用反射来实现该怎么办呢?注意下面这条语句:
this.GetType().GetMethod("DefaultValue", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(pi.PropertyType).Invoke(this, null)
这条语句实际上就相当于“DefaultValue<int>()”调用,只是,“DefaultValue<int>()”中类型int必须显式指定,必须传类型声明符,而反射类型PropertyInfo.PropertyType却只是一个Type实例对象。所以,此时,就必须通过反射调用DefaultValue<MemberType>()方法了。(这可不可以作为一个在反射中调用default关键字的典型范例呢?^-^)。这里指定参数“BindingFlags.Instance | BindingFlags.NonPublic”是因为DefaultValue方法是protected的,必须指定该参数才能通过GetMethod()获得引用。如果该方法定义成public的,则不需指定该参数。
再来一点Emit
上面,我们通过很少的反射,把构造函数中的代码冗余给去除了。那么,让我们继续看看去除构造函数中的代码冗余后的Entity类的定义还有什么不爽的地方呢?不爽的地方还不少:首先是keyValues这个类暴露了太多基类的实现细节,一个突兀的keyValues,让Entity类乍一眼看去不太容易让人明白;其次,每个属性都要用这种枯燥、类似的方式定义get,set,并从keyValues中读写属性值,怎么看怎么重复,怎么看怎么来的写这样类似的代码,是可以用工具来生成这个实体类,但是会让我更不爽,只生成这点代码,却要我承受代码生成带来的维护成本,实在是不值,太不值了。让我们再瞟一眼本段的小标题“再来一点Emit”,没错,我要让Emit出马了。先来点美丽的远景好了,我希望利用Emit改造过后,我可以这样来定义一个Entity类:
using System;
namespace Ilungasoft.Helper.TestApp.DomainObject2
{
public interface About: Ilungasoft.Helper.Data.IEntity
{
int ID { get; set; }
string Title { get; set; }
string Content { get; set; }
bool Deletable { get; set; }
int Order { get; set; }
}
}
namespace Ilungasoft.Helper.TestApp.DomainObject2
{
public interface About: Ilungasoft.Helper.Data.IEntity
{
int ID { get; set; }
string Title { get; set; }
string Content { get; set; }
bool Deletable { get; set; }
int Order { get; set; }
}
}
天那!有人要叫吗?^-^这不是一个接口吗?这这……真的可以只定义到这个程度吗,连public限定符都能剩了?这就是一个实体类?——为什么不可以呢?;-)
下面就来看看,要把实体类的定义从之前的样子整成这个样子需要做些什么吧。
当然,我们的主角自然是Emit。和keyValues模版的运用思路一样,因为Emit会带来一定的性能损失,为了将损失降至最低,我们只在第一次构造某个Entity时Emit出一个运行时的具体的Entity类型来,并缓存起来。当然我们这个动态Entity具体类型的作用要相当于之前的Entity版本。
这一次的改动,动作幅度可能要稍稍大那么一点点。
我们先定义一个接口IEntity,这个接口一是为了区别Entity类型,另一个重要作用是声明具体的Entity类需要的公共方法。
using System;
using System.Collections.Generic;
namespace Ilungasoft.Helper.Data
{
public interface IEntity
{
void SetPropertyValue(string key, object val);
string[] GetMemberNames(params string[] exceptMembers);
object[] GetMemberValues(params string[] exceptMembers);
}
}
using System.Collections.Generic;
namespace Ilungasoft.Helper.Data
{
public interface IEntity
{
void SetPropertyValue(string key, object val);
string[] GetMemberNames(params string[] exceptMembers);
object[] GetMemberValues(params string[] exceptMembers);
}
}
然后,因为现在实体类只需定义接口了,自然没办法new一个实例了,我们还必须给实体类定义一个EntityFactory。重头戏来了,没错,我们这一章中的重头戏就在我们的EntityFactory。EntityFactory要利用Emit为我们动态的生成我们需要的实体类的具体类。因为这个类是整个Emit方案的精华,所以虽然代码有点长,还是在这里列一下。另外,Extract一个BaseEntityFactory基类用于存放所有动态Entity具体类所在的AssemblyBuilder实例,也就是我们所有动态Entity类的宿主。
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
namespace Ilungasoft.Helper.Data
{
public abstract class BaseEntityFactory
{
protected static AssemblyBuilder assBuilder = null;
protected static ModuleBuilder modBuilder = null;
protected const string DYNAMIC_ENTITY_NAMESPACE = "Ilungasoft.Helper.Data.DynamicEntity";
}
}
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
namespace Ilungasoft.Helper.Data
{
public abstract class BaseEntityFactory
{
protected static AssemblyBuilder assBuilder = null;
protected static ModuleBuilder modBuilder = null;
protected const string DYNAMIC_ENTITY_NAMESPACE = "Ilungasoft.Helper.Data.DynamicEntity";
}
}
using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
namespace Ilungasoft.Helper.Data
{
public sealed class EntityFactory<IEntityType> : BaseEntityFactory
where IEntityType : IEntity
{
private static TypeBuilder typeBuilder = null;
private EntityFactory()
{
}
static EntityFactory()
{
//create dynamic IEntity Assembly & Type through Emit
if (assBuilder == null)
{
AssemblyName assName = new AssemblyName();
assName.Name = DYNAMIC_ENTITY_NAMESPACE;
assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);
}
if (modBuilder == null)
{
modBuilder = assBuilder.DefineDynamicModule(DYNAMIC_ENTITY_NAMESPACE);
}
if (typeBuilder == null)
{
typeBuilder = modBuilder.DefineType(DYNAMIC_ENTITY_NAMESPACE + "." + typeof(IEntityType).FullName, TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(typeof(IEntityType));
Type keyValuesType = typeof(Dictionary<string, object>);
Type baseType = typeof(Entity<IEntityType>);
typeBuilder.SetParent(baseType);
//define SetPropertyValue(string key, object val)
MethodInfo mi = typeof(IEntity).GetMethod("SetPropertyValue");
ParameterInfo[] paramInfos = mi.GetParameters();
int paramlength = paramInfos.Length;
Type[] paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder setPropValMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
setPropValMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(setPropValMethodBuilder, mi);
ILGenerator setPropValMethodIL = setPropValMethodBuilder.GetILGenerator();
setPropValMethodIL.Emit(OpCodes.Ldarg_0);
setPropValMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
setPropValMethodIL.Emit(OpCodes.Ldarg_1);
setPropValMethodIL.Emit(OpCodes.Ldarg_2);
setPropValMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropValMethodIL.Emit(OpCodes.Ret);
//define GetMemberNames(params string[] exceptMembers)
mi = typeof(IEntity).GetMethod("GetMemberNames");
paramInfos = mi.GetParameters();
paramlength = paramInfos.Length;
paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder getMemberNamesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
getMemberNamesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(getMemberNamesMethodBuilder, mi);
ILGenerator getMemberNamesMethodIL = getMemberNamesMethodBuilder.GetILGenerator();
getMemberNamesMethodIL.Emit(OpCodes.Ldarg_1);
getMemberNamesMethodIL.Emit(OpCodes.Call, baseType.GetMethod("GetMemberNames", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public));
getMemberNamesMethodIL.Emit(OpCodes.Ret);
//define GetMemberValues(params string[] exceptMembers)
mi = typeof(IEntity).GetMethod("GetMemberValues");
paramInfos = mi.GetParameters();
paramlength = paramInfos.Length;
paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder getMemberValuesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
getMemberValuesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(getMemberValuesMethodBuilder, mi);
ILGenerator getMemberValuesMethodIL = getMemberValuesMethodBuilder.GetILGenerator();
getMemberValuesMethodIL.Emit(OpCodes.Ldarg_0);
getMemberValuesMethodIL.Emit(OpCodes.Ldarg_1);
getMemberValuesMethodIL.Emit(OpCodes.Call, baseType.GetMethod("GetMemberValues"));
getMemberValuesMethodIL.Emit(OpCodes.Ret);
//define default constructor
ConstructorBuilder consBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
ILGenerator ctorIL = consBuilder.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, baseType.GetConstructor(new Type[0]));
ctorIL.Emit(OpCodes.Ret);
//define properties
PropertyInfo[] pis = typeof(IEntityType).GetProperties();
foreach (PropertyInfo pi in pis)
{
PropertyBuilder propBuilder = typeBuilder.DefineProperty(pi.Name, System.Reflection.PropertyAttributes.HasDefault, pi.PropertyType, null);
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
if (pi.CanRead)
{
//define getMethod
MethodBuilder getPropMethodBuilder = typeBuilder.DefineMethod("get_" + pi.Name, pi.GetGetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, pi.PropertyType, Type.EmptyTypes);
typeBuilder.DefineMethodOverride(getPropMethodBuilder, pi.GetGetMethod());
ILGenerator getPropMethodIL = getPropMethodBuilder.GetILGenerator();
getPropMethodIL.Emit(OpCodes.Ldarg_0);
getPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
getPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
getPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("get_Item"));
if (pi.PropertyType.IsValueType)
{
getPropMethodIL.Emit(OpCodes.Unbox_Any, pi.PropertyType);
}
else
{
getPropMethodIL.Emit(OpCodes.Castclass, pi.PropertyType);
}
getPropMethodIL.Emit(OpCodes.Ret);
propBuilder.SetGetMethod(getPropMethodBuilder);
}
if (pi.CanWrite)
{
//define setMethod
MethodBuilder setPropMethodBuilder = typeBuilder.DefineMethod("set_" + pi.PropertyType, pi.GetSetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, null, new Type[] { pi.PropertyType });
typeBuilder.DefineMethodOverride(setPropMethodBuilder, pi.GetSetMethod());
ILGenerator setPropMethodIL = setPropMethodBuilder.GetILGenerator();
setPropMethodIL.Emit(OpCodes.Ldarg_0);
setPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
setPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
setPropMethodIL.Emit(OpCodes.Ldarg_1);
if (pi.PropertyType.IsValueType)
{
setPropMethodIL.Emit(OpCodes.Box, pi.PropertyType);
}
setPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropMethodIL.Emit(OpCodes.Ret);
propBuilder.SetSetMethod(setPropMethodBuilder);
}
}
}
}
public static IEntityType CreateObject()
{
return (IEntityType)Activator.CreateInstance(typeBuilder.CreateType());
}
public static IEntityType CreateObject(DataRow row)
{
return Entity<IEntityType>.CreateObject(row);
}
public static IEntityType CreateObject(IDataReader reader)
{
return Entity<IEntityType>.CreateObject(reader);
}
public static IEntityType[] CreateObjectList(DataTable table)
{
return Entity<IEntityType>.CreateObjectList(table);
}
public static IEntityType[] CreateObjectList(IDataReader reader)
{
return Entity<IEntityType>.CreateObjectList(reader);
}
}
}
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
namespace Ilungasoft.Helper.Data
{
public sealed class EntityFactory<IEntityType> : BaseEntityFactory
where IEntityType : IEntity
{
private static TypeBuilder typeBuilder = null;
private EntityFactory()
{
}
static EntityFactory()
{
//create dynamic IEntity Assembly & Type through Emit
if (assBuilder == null)
{
AssemblyName assName = new AssemblyName();
assName.Name = DYNAMIC_ENTITY_NAMESPACE;
assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);
}
if (modBuilder == null)
{
modBuilder = assBuilder.DefineDynamicModule(DYNAMIC_ENTITY_NAMESPACE);
}
if (typeBuilder == null)
{
typeBuilder = modBuilder.DefineType(DYNAMIC_ENTITY_NAMESPACE + "." + typeof(IEntityType).FullName, TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(typeof(IEntityType));
Type keyValuesType = typeof(Dictionary<string, object>);
Type baseType = typeof(Entity<IEntityType>);
typeBuilder.SetParent(baseType);
//define SetPropertyValue(string key, object val)
MethodInfo mi = typeof(IEntity).GetMethod("SetPropertyValue");
ParameterInfo[] paramInfos = mi.GetParameters();
int paramlength = paramInfos.Length;
Type[] paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder setPropValMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
setPropValMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(setPropValMethodBuilder, mi);
ILGenerator setPropValMethodIL = setPropValMethodBuilder.GetILGenerator();
setPropValMethodIL.Emit(OpCodes.Ldarg_0);
setPropValMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
setPropValMethodIL.Emit(OpCodes.Ldarg_1);
setPropValMethodIL.Emit(OpCodes.Ldarg_2);
setPropValMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropValMethodIL.Emit(OpCodes.Ret);
//define GetMemberNames(params string[] exceptMembers)
mi = typeof(IEntity).GetMethod("GetMemberNames");
paramInfos = mi.GetParameters();
paramlength = paramInfos.Length;
paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder getMemberNamesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
getMemberNamesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(getMemberNamesMethodBuilder, mi);
ILGenerator getMemberNamesMethodIL = getMemberNamesMethodBuilder.GetILGenerator();
getMemberNamesMethodIL.Emit(OpCodes.Ldarg_1);
getMemberNamesMethodIL.Emit(OpCodes.Call, baseType.GetMethod("GetMemberNames", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public));
getMemberNamesMethodIL.Emit(OpCodes.Ret);
//define GetMemberValues(params string[] exceptMembers)
mi = typeof(IEntity).GetMethod("GetMemberValues");
paramInfos = mi.GetParameters();
paramlength = paramInfos.Length;
paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder getMemberValuesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
getMemberValuesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(getMemberValuesMethodBuilder, mi);
ILGenerator getMemberValuesMethodIL = getMemberValuesMethodBuilder.GetILGenerator();
getMemberValuesMethodIL.Emit(OpCodes.Ldarg_0);
getMemberValuesMethodIL.Emit(OpCodes.Ldarg_1);
getMemberValuesMethodIL.Emit(OpCodes.Call, baseType.GetMethod("GetMemberValues"));
getMemberValuesMethodIL.Emit(OpCodes.Ret);
//define default constructor
ConstructorBuilder consBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
ILGenerator ctorIL = consBuilder.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, baseType.GetConstructor(new Type[0]));
ctorIL.Emit(OpCodes.Ret);
//define properties
PropertyInfo[] pis = typeof(IEntityType).GetProperties();
foreach (PropertyInfo pi in pis)
{
PropertyBuilder propBuilder = typeBuilder.DefineProperty(pi.Name, System.Reflection.PropertyAttributes.HasDefault, pi.PropertyType, null);
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
if (pi.CanRead)
{
//define getMethod
MethodBuilder getPropMethodBuilder = typeBuilder.DefineMethod("get_" + pi.Name, pi.GetGetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, pi.PropertyType, Type.EmptyTypes);
typeBuilder.DefineMethodOverride(getPropMethodBuilder, pi.GetGetMethod());
ILGenerator getPropMethodIL = getPropMethodBuilder.GetILGenerator();
getPropMethodIL.Emit(OpCodes.Ldarg_0);
getPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
getPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
getPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("get_Item"));
if (pi.PropertyType.IsValueType)
{
getPropMethodIL.Emit(OpCodes.Unbox_Any, pi.PropertyType);
}
else
{
getPropMethodIL.Emit(OpCodes.Castclass, pi.PropertyType);
}
getPropMethodIL.Emit(OpCodes.Ret);
propBuilder.SetGetMethod(getPropMethodBuilder);
}
if (pi.CanWrite)
{
//define setMethod
MethodBuilder setPropMethodBuilder = typeBuilder.DefineMethod("set_" + pi.PropertyType, pi.GetSetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, null, new Type[] { pi.PropertyType });
typeBuilder.DefineMethodOverride(setPropMethodBuilder, pi.GetSetMethod());
ILGenerator setPropMethodIL = setPropMethodBuilder.GetILGenerator();
setPropMethodIL.Emit(OpCodes.Ldarg_0);
setPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
setPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
setPropMethodIL.Emit(OpCodes.Ldarg_1);
if (pi.PropertyType.IsValueType)
{
setPropMethodIL.Emit(OpCodes.Box, pi.PropertyType);
}
setPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropMethodIL.Emit(OpCodes.Ret);
propBuilder.SetSetMethod(setPropMethodBuilder);
}
}
}
}
public static IEntityType CreateObject()
{
return (IEntityType)Activator.CreateInstance(typeBuilder.CreateType());
}
public static IEntityType CreateObject(DataRow row)
{
return Entity<IEntityType>.CreateObject(row);
}
public static IEntityType CreateObject(IDataReader reader)
{
return Entity<IEntityType>.CreateObject(reader);
}
public static IEntityType[] CreateObjectList(DataTable table)
{
return Entity<IEntityType>.CreateObjectList(table);
}
public static IEntityType[] CreateObjectList(IDataReader reader)
{
return Entity<IEntityType>.CreateObjectList(reader);
}
}
}
好了,来看看测试代码:
SimpleDataAccess<About>.Insert(Entity<About>.GetMemberNames("ID"), EntityFactory<About>.CreateObject().GetMemberValues("ID")); //插入一条About记录,要插入About实例除了ID字段之外的所有字段值
SimpleDataAccess<About>.Delete(new Condition("ID = @id", 100)); //删除id = 100的纪录
About[] abouts = SimpleDataAccess<About>.Select(null, null); //获取所有记录,按照默认的排序
SimpleDataAccess<About>.Update(new string[] { "Title", "Content" }, new object[] { "title", "content" }, new Condition("ID = @id", 13)); //更新id=13的记录的两个字段
object[] ids = SimpleDataAccess<About>.SelectSingleColumn("ID", null); //获取某一个字段的全部记录
About about = SimpleDataAccess<About>.SelectTopOne(new Condition("ID", "id", OP.Equals, 13)); //获取ID==13的第一条记录
About[] abouts2 = SimpleDataAccess<About>.SelectPage("ID", null, null, 3, 2); //获取默认排序的所有记录的每页3条记录的第3页
About[] abouts3 = SimpleDataAccess<About>.SelectPage("ID", new Condition("ID > @id", 13), new OrderList("ID", true), 3, 1); //获取ID>13,按ID倒序排序的所有记录的每页3条的第1页
about.GetMemberNames();
about.GetMemberValues("ID");
MessageBox.Show("ok");
SimpleDataAccess<About>.Delete(new Condition("ID = @id", 100)); //删除id = 100的纪录
About[] abouts = SimpleDataAccess<About>.Select(null, null); //获取所有记录,按照默认的排序
SimpleDataAccess<About>.Update(new string[] { "Title", "Content" }, new object[] { "title", "content" }, new Condition("ID = @id", 13)); //更新id=13的记录的两个字段
object[] ids = SimpleDataAccess<About>.SelectSingleColumn("ID", null); //获取某一个字段的全部记录
About about = SimpleDataAccess<About>.SelectTopOne(new Condition("ID", "id", OP.Equals, 13)); //获取ID==13的第一条记录
About[] abouts2 = SimpleDataAccess<About>.SelectPage("ID", null, null, 3, 2); //获取默认排序的所有记录的每页3条记录的第3页
About[] abouts3 = SimpleDataAccess<About>.SelectPage("ID", new Condition("ID > @id", 13), new OrderList("ID", true), 3, 1); //获取ID>13,按ID倒序排序的所有记录的每页3条的第1页
about.GetMemberNames();
about.GetMemberValues("ID");
MessageBox.Show("ok");
测试代码和原来的代码只有细微区别,只是原来的实体类是具体类可以new,现在凡是涉及构造实体类的操作都集中到EntityFactory中了。
在结束本文之前,再来举个调用SimpleDbHelper类的例子好了,我们定义另一个实体类SimpleAbout,这个类只是About的摘要,定义起来真的很简单的哟:
using System;
using System.Collections.Generic;
using System.Text;
namespace Ilungasoft.Helper.TestApp.DomainObject2
{
public interface SimpleAbout : Ilungasoft.Helper.Data.IEntity
{
string Title { get; }
string Content { get; set; }
}
}
using System.Collections.Generic;
using System.Text;
namespace Ilungasoft.Helper.TestApp.DomainObject2
{
public interface SimpleAbout : Ilungasoft.Helper.Data.IEntity
{
string Title { get; }
string Content { get; set; }
}
}
前面忘了说了,定义实体类的时,如果希望属性只读或只写也是很容易的,像上面这样只包含get或set就行了。来看看我们的调用SimpleDbHelper的示例:
IDataReader reader = SimpleDbHelper.SelectReadOnly(DatabaseFactory.CreateDatabase(), //获取默认数据库,一般为app.config/web.config中指定的第一个数据库
"About", new string[] { "Title", "Content" }, "ID > @ID", new object[] { 5 }, "ID DESC");
SimpleAbout[] simpleAbouts = EntityFactory<SimpleAbout>.CreateObjectList(reader);
"About", new string[] { "Title", "Content" }, "ID > @ID", new object[] { 5 }, "ID DESC");
SimpleAbout[] simpleAbouts = EntityFactory<SimpleAbout>.CreateObjectList(reader);
调用SimpleDbHelper的方法,我们可以非常方便灵活的查询或修改数据,返回IDataReader或者DataSet,然后可以通过EntityFactory<>的CreateObject和CreateObjectList方法将数据绑定到需要的实体类。是不是很灵活很奇妙呢?^-^
看到这些,你真的觉得在这里代码生成对定义实体类,持久化非要不可吗?在.Net2.0的思路下,太多旧思想也许都要与时俱进了。
下载范例程序(很抱歉,暂时不能公开持久化组件的源码,有需要的话就先反编译吧,不过在这次的示例代码中,我包含了本持久化组件的一个Release编译版本,您完全可以用来写些测试代码玩玩,有任何问题欢迎与Teddy联系,我会持续思考改进,说不定哪天就开源了,如果真的有很多人需要的话~~)
01/09 更新
本文续篇:用自定义KeyValueCollection类代替Dictionary/Hastable,改善简化后的Entity性能