用 System.Reflection.Emit 來自動生成呼叫儲存過程的實現

iDotNetSpace發表於2008-01-22
/****************************************************************\
*
* 用 System.Reflection.Emit 來自動生成呼叫儲存過程的實現!
*
* By http://lostinet.com
*
* Copyrights : Not-Reversed
*
\****************************************************************/

//使用的例子
namespace Lostinet.Sample
{
using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;

//定義一個介面,用於定義儲存過程

interface INorthwindStoredProcedures
{
//定義儲存過程對應的方法

DataSet CustOrderHist(string CustomerID);

//如果儲存過程名字和方法名字不同,應該用SqlAccessAttribute來進行說明
[SqlAccess("Employee Sales By Country")]
DataTable EmployeeSalesByCountry(DateTime Beginning_Date,DateTime Ending_Date);

//...more...

//MORE Ideas..

//直接執行SQL語句?
//[SqlAccess(SqlAccessType.SqlQuery,"SELECT * FROM Employees WHERE EmployeeID=@EmpId")]
//DataTable SelectEmployee(int EmpId);
}

class ConsoleApplication
{
[STAThread]
static void Main(string[] args)
{
using(SqlConnection conn=new SqlConnection("server=(local);trusted_connection=true;database=northwind"))
{
//一句話就把實現建立了!
//需要傳如 SqlConnection 和 SqlTransaction
//SqlTransaction可以為null

//這個好就好在,只要能得到SqlConnection/SqlTransaction就能用這個方法了,所以相容 Lostinet.Data.SqlScope
INorthwindStoredProcedures nsp=(INorthwindStoredProcedures)
StoredProcedure.CreateStoredProcedureInterface(typeof(INorthwindStoredProcedures),conn,null);

//呼叫儲存過程並且顯示

ShowData("CustOrderHist ALFKI",nsp.CustOrderHist("ALFKI"));

ShowData("Employee Sales By Country",nsp.EmployeeSalesByCountry(new DateTime(1998,1,1),new DateTime(1999,1,1)));

}
}

static void ShowData(string title,object data)
{
Form. f=new Form();
f.Width=600;
f.Height=480;
f.Text=title;

DataGrid grid=new DataGrid();
grid.Dock=DockStyle.Fill;
grid.DataSource=data;

f.Controls.Add(grid);
f.ShowDialog();
}

}
}

#region //實現方法(不完整)
namespace Lostinet.Sample
{
using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using System.Data;
using System.Data.SqlClient;

//這個類作為實現的基類,
//目的是提供儲存 SqlConnection/SqlTransaction 和公用的一些方法
//這個類必須為public,否則無法繼承
//但開發者不會顯式訪問這個類
public class SPInterfaceBase : IDisposable
{
public SPInterfaceBase()
{
}

public void Dispose()
{
}

//CreateStoredProcedureInterface會把相關的值SqlConnection/SqlTransaction存到這裡
public SqlConnection connection;
public SqlTransaction transaction;

//建立一個SqlCommand
public SqlCommand CreateCommand(string spname)
{
SqlCommand cmd=new SqlCommand(spname,connection,transaction);
cmd.CommandType=CommandType.StoredProcedure;
//TODO:
//cmd.Parameters.Add("@ReturnValue",...
return cmd;
}

//由 Type 推算出 SqlDbType , 未完成
SqlDbType GetSqlDbType(Type type)
{
//TODO:switch(type)...

return SqlDbType.NVarChar;
}

//定義引數
public void DefineParameter(SqlCommand cmd,string name,Type type,ParameterDirection direction)
{
SqlParameter param=new SqlParameter("@"+name,GetSqlDbType(type));
param.Direction=direction;
cmd.Parameters.Add(param);
}

//在SqlCommand執行前設定引數值
public void SetParameter(SqlCommand cmd,string name,object value)
{
cmd.Parameters["@"+name].Value=(value==null?DBNull.Value:value);
}
//在SqlCommand執行後取得引數值
public object GetParameter(SqlCommand cmd,string name)
{
return cmd.Parameters["@"+name].Value;
}

//根據不同的返回值執行不同的操作

public SqlDataReader ExecuteDataReader(SqlCommand cmd)
{
return cmd.ExecuteReader();
}
public object ExecuteScalar(SqlCommand cmd)
{
return cmd.ExecuteScalar();
}
public void ExecuteNonQuery(SqlCommand cmd)
{
cmd.ExecuteNonQuery();
}
public DataSet ExecuteDataSet(SqlCommand cmd)
{
DataSet ds=new DataSet();
using(SqlDataAdapter sda=new SqlDataAdapter(cmd))
{
sda.Fill(ds);
}
return ds;
}
public DataTable ExecuteDataTable(SqlCommand cmd)
{
DataTable table=new DataTable();
using(SqlDataAdapter sda=new SqlDataAdapter(cmd))
{
sda.Fill(table);
}
return table;
}
public DataRow ExecuteDataRow(SqlCommand cmd)
{
DataTable table=ExecuteDataTable(cmd);
if(table.Rows.Count==0)
return null;
return table.Rows[0];
}
}


public class StoredProcedure
{
static public object CreateStoredProcedureInterface(Type interfaceType,SqlConnection connection,SqlTransaction transaction)
{
//檢查引數
if(interfaceType==null)throw(new ArgumentNullException("interfaceType"));
if(!interfaceType.IsInterface)
throw(new ArgumentException("argument is not interface","interfaceType"));
if(connection==null)throw(new ArgumentNullException("connection"));
if(transaction!=null)
{
if(transaction.Connection!=connection)
throw(new ArgumentException("transaction.Connection!=connection","transaction"));
}

//建立StoredProcedure

StoredProcedure spemit=new StoredProcedure();
spemit.interfaceType=interfaceType;
spemit.connection=connection;
spemit.transaction=transaction;

//建立
return spemit.CreateInstance();
}

//用於儲存已建立的型別
static Hashtable EmittedTypes=new Hashtable();

Type interfaceType;
SqlConnection connection;
SqlTransaction transaction;

private StoredProcedure()
{
}

object CreateInstance()
{
lock(interfaceType)
{
//如果沒有建立具體的實現,則建立它

if(emittedType==null)
{
emittedType=(Type)EmittedTypes[interfaceType];

if(emittedType==null)
{
CreateType();

//儲存已建立型別
EmittedTypes[interfaceType]=emittedType;
}
}
}

//建立具體的例項
SPInterfaceBase spi=(SPInterfaceBase)Activator.CreateInstance(emittedType);

//設定SqlConnection/SqlTransaction
spi.connection=connection;
spi.transaction=transaction;

return spi;
}

Type emittedType;

TypeBuilder typeBuilder;

//建立型別
void CreateType()
{
//建立 Assembly
//AssemblyBuilderAccess.Run-表示只用於執行,不在磁碟上儲存
AssemblyName an=new AssemblyName();
an.Name="Assembly."+interfaceType.FullName+".Implementation";
AssemblyBuilder asmBuilder=AppDomain.CurrentDomain.DefineDynamicAssembly(an,AssemblyBuilderAccess.Run);

//建立Module
ModuleBuilder mdlBuilder=asmBuilder.DefineDynamicModule("Module."+interfaceType.FullName+".Implementation");

//建立Type,該型別繼承 SPInterfaceBase
typeBuilder=mdlBuilder.DefineType(interfaceType.FullName+".Implementation",TypeAttributes.Class,typeof(SPInterfaceBase));

//實現所有的介面方法
EmitInterface(interfaceType);

//如果interfaceType是基於其他介面的
foreach(Type subinterface in interfaceType.GetInterfaces())
{
//IDisposable不需要實現,由SPInterfaceBase實現了
if(subinterface==typeof(IDisposable))
continue;

EmitInterface(subinterface);
}


emittedType=typeBuilder.CreateType();
}

void EmitInterface(Type type)
{
//實現介面
typeBuilder.AddInterfaceImplementation(type);

//列出介面的成員
foreach(MemberInfo member in type.GetMembers(BindingFlags.Instance|BindingFlags.Public))
{
//約定-成員必須是方法,不能有屬性啊,事件之類的
if(member.MemberType!=MemberTypes.Method)
throw(new Exception("Could Not Emit "+member.MemberType+" Automatically!"));

//取得介面中定義的方法
MethodInfo method=(MethodInfo)member;

//計算新方法的屬性,在原來方法的屬性上覆制過來,並且不是Public/Abstract,加上Private
MethodAttributes methodattrs=method.Attributes;
methodattrs&=~(MethodAttributes.Public|MethodAttributes.Abstract);
methodattrs|=MethodAttributes.Private;


ParameterInfo[] paramInfos=method.GetParameters();
int paramlength=paramInfos.Length;

//取得引數的型別陣列
Type[] paramTypes=new Type[paramlength];
for(int i=0;i{
paramTypes[i]=paramInfos[i].ParameterType;
}

//在typeBuilder上建立新方法,引數型別與返回型別都與介面上的方法一致
MethodBuilder mthBuilder=typeBuilder.DefineMethod(method.Name,methodattrs,method.CallingConvention,method.ReturnType,paramTypes);

//複製新方法上的引數的名字和屬性
for(int i=0;i{
ParameterInfo pi=paramInfos[i];
//對於Instance,引數position由1開始
mthBuilder.DefineParameter(i+1,pi.Attributes,pi.Name);
}

//指定新方法是實現介面的方法的。
typeBuilder.DefineMethodOverride(mthBuilder,method);

//在型別上定義一個欄位,這個欄位用於儲存被方法使用的SqlCommand
FieldBuilder field_cmd=typeBuilder.DefineField("_cmd_"+method.Name,typeof(SqlCommand),FieldAttributes.Private);

//ILGenerator 是用於生成實現程式碼的物件
ILGenerator ilg=mthBuilder.GetILGenerator();

//定義臨時變數
LocalBuilder local_res=ilg.DeclareLocal(typeof(object));

//定義一個用於跳轉的Label
Label label_cmd_ready=ilg.DefineLabel();

//this._cmd_MethodName
ilg.Emit(OpCodes.Ldarg_0); //this
ilg.Emit(OpCodes.Ldfld,field_cmd);//._cmd_MethodName

//if(this._cmd_MethodName!=null) 跳到 label_cmd_ready
ilg.Emit(OpCodes.Brtrue,label_cmd_ready);

//如果this._cmd_MethodName為null,則執行下面程式碼來建立SqlCommand



//this._cmd_MethodName=this.CreateCommand("MethodName");
ilg.Emit(OpCodes.Ldarg_0);

//this.CreateCommand
ilg.Emit(OpCodes.Ldarg_0);//引數0
ilg.Emit(OpCodes.Ldstr,SqlAccessAttribute.GetSPName(method));//引數1
//呼叫
ilg.Emit(OpCodes.Callvirt,typeof(SPInterfaceBase).GetMethod("CreateCommand",BindingFlags.Instance|BindingFlags.Public));

ilg.Emit(OpCodes.Stfld,field_cmd);// ._cmd_MethodName=

//this.DefineParameter(...)
if(paramlength!=0)
{
//取得DefineParameter的引用
MethodInfo method_DefineParameter=typeof(SPInterfaceBase).GetMethod("DefineParameter",BindingFlags.Instance|BindingFlags.Public);

for(int i=0;i{
//取得各引數
ParameterInfo pi=paramInfos[i];

//this.DefineParameter(this._cmd_MethodName,"ParameterName",typeof(ParameterType),ParameterDirection.Xxx);

//引數0 - this
ilg.Emit(OpCodes.Ldarg_0);

//引數1 - this._cmd_MethodName
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldfld,field_cmd);

//引數2 - "ParameterName"
ilg.Emit(OpCodes.Ldstr,pi.Name);

//引數3 - typeof(ParameterType)
ilg.Emit(OpCodes.Ldtoken,pi.ParameterType);

//引數4 - ParameterDirection.Xxx
if(pi.ParameterType.IsByRef)
{
ilg.Emit(OpCodes.Ldc_I4,(int)ParameterDirection.InputOutput);
}
else if(pi.IsOut)
{
ilg.Emit(OpCodes.Ldc_I4,(int)ParameterDirection.Output);
}
else
{
ilg.Emit(OpCodes.Ldc_I4,(int)ParameterDirection.Input);
}

//呼叫DefineParameter
ilg.Emit(OpCodes.Callvirt,method_DefineParameter);
}
}
//到這裡 _cmd_CommandName 已經 OK 了。

//設定label_cmd_ready就指這裡
ilg.MarkLabel(label_cmd_ready);

//cmd!=null now.

if(paramlength!=0)
{
//現在要把方法的引數的值設定到SqlParameter上

MethodInfo method_SetParameter=typeof(SPInterfaceBase).GetMethod("SetParameter",BindingFlags.Instance|BindingFlags.Public);

for(int i=0;i{
ParameterInfo pi=paramInfos[i];

//如果引數是 out 的,則不需要設定
if(!pi.ParameterType.IsByRef&&pi.IsOut)
continue;

//this.SetParameter(this._cmd_MethodName,"ParameterName",ParameterName);

ilg.Emit(OpCodes.Ldarg_0);

ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldfld,field_cmd);

ilg.Emit(OpCodes.Ldstr,pi.Name);

//取得引數值,如果引數為ValueType,則Box到Object
ilg.Emit(OpCodes.Ldarg,i+1);
if(pi.ParameterType.IsValueType)
ilg.Emit(OpCodes.Box,pi.ParameterType);

ilg.Emit(OpCodes.Callvirt,method_SetParameter);
}
}

//現在要執行儲存過程(執行SqlCommand)了

//這裡根據返回值型別判斷怎樣執行SqlCommand

Type returnType=method.ReturnType;

//如果是 void 的,則不需要返回值
bool nores=returnType==typeof(void);

MethodInfo method_Execute=null;

if(nores)
{
//不需要返回值
method_Execute=typeof(SPInterfaceBase).GetMethod("ExecuteNonQuery",BindingFlags.Instance|BindingFlags.Public);
}
else if(returnType==typeof(object))
{
//返回object
method_Execute=typeof(SPInterfaceBase).GetMethod("ExecuteScalar",BindingFlags.Instance|BindingFlags.Public);
}
else if(returnType==typeof(DataSet))
{
//返回DataSet
method_Execute=typeof(SPInterfaceBase).GetMethod("ExecuteDataSet",BindingFlags.Instance|BindingFlags.Public);
}
else if(returnType==typeof(DataTable))
{
//返回DataTable
method_Execute=typeof(SPInterfaceBase).GetMethod("ExecuteDataTable",BindingFlags.Instance|BindingFlags.Public);
}
else if(returnType==typeof(DataRow))
{
//返回DataRow
method_Execute=typeof(SPInterfaceBase).GetMethod("ExecuteDataRow",BindingFlags.Instance|BindingFlags.Public);
}
else
{
//返回其他型別
foreach(Type retInterface in returnType.GetInterfaces())
{
//如果是返回IDataReader
if(retInterface==typeof(IDataReader))
{
//只支援SqlDataReader
if(!returnType.IsAssignableFrom(typeof(SqlDataReader)))
throw(new Exception("SqlDataReader could not convert to "+returnType.FullName));

method_Execute=typeof(SPInterfaceBase).GetMethod("ExecuteDataReader",BindingFlags.Instance|BindingFlags.Public);
break;
}
}
}

//如果找不到適合的策略,
if(method_Execute==null)
{
//TODO:當然,這裡應該有返回Int32,String,...的,不過懶得再寫了。

//丟擲異常,提示不支援該返回型別,要作者改改:)
throw(new NotSupportedException("NotSupport ReturnType:"+returnType.FullName));
}

//this.ExecuteXXX(this._cmd_MethodName)
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldfld,field_cmd);
ilg.Emit(OpCodes.Callvirt,method_Execute);

//如果有返回值的,則是
//local_res=this.ExecuteXXX(this._cmd_MethodName)
if(!nores)
{
if(returnType.IsValueType)
ilg.Emit(OpCodes.Box,returnType);
ilg.Emit(OpCodes.Stloc,local_res);
}

if(paramlength!=0)
{
//這裡處理ref/out的引數
MethodInfo method_GetParameter=typeof(SPInterfaceBase).GetMethod("GetParameter",BindingFlags.Instance|BindingFlags.Public);

for(int i=0;i{
ParameterInfo pi=paramInfos[i];

//如果不是ref/out則跳過
if(!pi.ParameterType.IsByRef&&!pi.IsOut)
continue;

//ParameterName=this.GetParameter(this._cmd_Methodname,"ParameterName")
ilg.Emit(OpCodes.Ldarg_0);

ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldfld,field_cmd);

ilg.Emit(OpCodes.Ldstr,pi.Name);

ilg.Emit(OpCodes.Callvirt,method_GetParameter);

//如果型別是值型別,則需要 Unbox
if(pi.ParameterType.IsValueType)
ilg.Emit(OpCodes.Unbox,pi.ParameterType);

ilg.Emit(OpCodes.Starg,i+1);
}
}

//如果是 void , 則直接 return;
//否者是 return local_res , 如果返回值型別是ValueType,則需要Unbox
if(!nores)
{
ilg.Emit(OpCodes.Ldloc,local_res);
if(returnType.IsValueType)
ilg.Emit(OpCodes.Unbox,returnType);
}
ilg.Emit(OpCodes.Ret);

// //throw(new NotImplementedException());
// ilg.Emit(OpCodes.Newobj,typeof(NotImplementedException).GetConstructor(new Type[0]));
// ilg.Emit(OpCodes.Throw);

}
}
}

public enum SqlAccessType
{
StoredProcedure
//TODO:
//,SqlQuery
}

[AttributeUsage(AttributeTargets.Method)]
public class SqlAccessAttribute : Attribute
{
string _sp;

public SqlAccessAttribute(string spname)
{
_sp=spname;
}

public string StoreProcedure
{
get
{
return _sp;
}
}

static public string GetSPName(MethodInfo method)
{
if(method==null)throw(new ArgumentNullException("method"));

object[] attrs=method.GetCustomAttributes(typeof(SqlAccessAttribute),false);
if(attrs==null||attrs.Length==0)
return method.Name;

return ((SqlAccessAttribute)attrs[0]).StoreProcedure;
}

//TODO:
// public SqlAccessAttribute(SqlAccessType type,string text)
// {
//
// }
}

}
#endregion

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-156336/,如需轉載,請註明出處,否則將追究法律責任。

相關文章