利用动态代理实现通用存储过程的调用
很久没有更新了,哈哈,最近太懒惰了,业余时间,写了一个关于存储过程的调用的东东,部分思想来自于Lostinet大大写的用 System.Reflection.Emit 自动实现调用存储过程的接口,他的实现是用Emit,我改用动态代理,其实内部都用到Emit
通常情况下我们利用ADO.NET调用存储过程往往要写上好多代码,特别存储过程是参数很多的话很容易出错,而且很繁琐,看看下面这段调用存储过程的代码:
晕,居然这么多,不知道你觉得烦不烦,反正我是很讨厌,很反感了。
其实利用动态代理,可以解决很多问题,现在先假设我们调用的模式:
1.定义调用的接口,方法名对应到要调用的存储过程名,参数也与之对应(注:我用的实例数据库是NorthWind):
如果不清楚DynamicProxy请参见园子里的一些优秀的文章:DynamicProxy(动态代理)技术剖析(1) DynamicProxy(动态代理)技术剖析(2)
通常情况下我们利用ADO.NET调用存储过程往往要写上好多代码,特别存储过程是参数很多的话很容易出错,而且很繁琐,看看下面这段调用存储过程的代码:
晕,居然这么多,不知道你觉得烦不烦,反正我是很讨厌,很反感了。
其实利用动态代理,可以解决很多问题,现在先假设我们调用的模式:
1.定义调用的接口,方法名对应到要调用的存储过程名,参数也与之对应(注:我用的实例数据库是NorthWind):
public interface ISproces
{
System.Data.DataSet CustOrderHist(string CustomerID);
DataSet CustOrdersDetail(int OrderID);
//如果储存过程名字和方法名字不同,应该用SpCustomNameAttribute来进行说明
[SpCustomNameAttribute("Employee Sales By Country")]
DataSet EmployeeSalesByCountry(DateTime Beginning_Date,DateTime Ending_Date);
}
2.利用Castle的DynamicProxy拦截对接口ISproces的调用,并写自己的拦截类SprocInterceptor,{
System.Data.DataSet CustOrderHist(string CustomerID);
DataSet CustOrdersDetail(int OrderID);
//如果储存过程名字和方法名字不同,应该用SpCustomNameAttribute来进行说明
[SpCustomNameAttribute("Employee Sales By Country")]
DataSet EmployeeSalesByCountry(DateTime Beginning_Date,DateTime Ending_Date);
}
如果不清楚DynamicProxy请参见园子里的一些优秀的文章:DynamicProxy(动态代理)技术剖析(1) DynamicProxy(动态代理)技术剖析(2)
/// <summary>
/// 该类负责拦截接口中方法的执行,并调用对应的存储过程
/// </summary>
public class SprocInterceptor:StandardInterceptor
{
public SqlConnection connection;
public SprocInterceptor()
{
}
public override object Intercept(IInvocation invocation, params object[] args)
{
MethodInfo method=invocation.Method;
connection.Open();
string methodName="";
object returnObj=null;
if (invocation.Method.IsDefined(typeof(SpCustomNameAttribute),true))
{
methodName =SpCustomNameAttribute.GetSPName(method);
}
else
{
methodName=method.Name;
}
SqlCommand command = new SqlCommand(methodName, connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
ParameterInfo[] paramInfos=method.GetParameters();
int paramlength=paramInfos.Length;
for(int i=0;i<paramlength;i++)
{
Type type=paramInfos[i].ParameterType;
SqlDbType sqlType=ConvertSqlType(type);
SqlParameter PageIndexParam = command.Parameters.Add("@"+paramInfos[i].Name, sqlType);
PageIndexParam.Value = args[i];
}
if(method.ReturnType==typeof(void))
{
// 执行
command.ExecuteNonQuery();
}
else if(method.ReturnType==typeof(DataSet))
{
// 取出数据集
SqlDataAdapter adapter = new SqlDataAdapter(command);
DataSet dataset = new DataSet();
adapter.Fill(dataset);
adapter.Dispose();
returnObj=dataset;
}
else
{
SqlParameter returnValueParam = command.Parameters.Add("@returnValueParam",ConvertSqlType(method.ReturnType));
returnValueParam.Direction = System.Data.ParameterDirection.ReturnValue;
// 执行
command.ExecuteNonQuery();
returnObj=Convert.ChangeType(returnValueParam.Value,method.ReturnType);
}
// 清除
command.Dispose();
connection.Close();
return returnObj;
}
}
/// 该类负责拦截接口中方法的执行,并调用对应的存储过程
/// </summary>
public class SprocInterceptor:StandardInterceptor
{
public SqlConnection connection;
public SprocInterceptor()
{
}
public override object Intercept(IInvocation invocation, params object[] args)
{
MethodInfo method=invocation.Method;
connection.Open();
string methodName="";
object returnObj=null;
if (invocation.Method.IsDefined(typeof(SpCustomNameAttribute),true))
{
methodName =SpCustomNameAttribute.GetSPName(method);
}
else
{
methodName=method.Name;
}
SqlCommand command = new SqlCommand(methodName, connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
ParameterInfo[] paramInfos=method.GetParameters();
int paramlength=paramInfos.Length;
for(int i=0;i<paramlength;i++)
{
Type type=paramInfos[i].ParameterType;
SqlDbType sqlType=ConvertSqlType(type);
SqlParameter PageIndexParam = command.Parameters.Add("@"+paramInfos[i].Name, sqlType);
PageIndexParam.Value = args[i];
}
if(method.ReturnType==typeof(void))
{
// 执行
command.ExecuteNonQuery();
}
else if(method.ReturnType==typeof(DataSet))
{
// 取出数据集
SqlDataAdapter adapter = new SqlDataAdapter(command);
DataSet dataset = new DataSet();
adapter.Fill(dataset);
adapter.Dispose();
returnObj=dataset;
}
else
{
SqlParameter returnValueParam = command.Parameters.Add("@returnValueParam",ConvertSqlType(method.ReturnType));
returnValueParam.Direction = System.Data.ParameterDirection.ReturnValue;
// 执行
command.ExecuteNonQuery();
returnObj=Convert.ChangeType(returnValueParam.Value,method.ReturnType);
}
// 清除
command.Dispose();
connection.Close();
return returnObj;
}
}
该类继承自StandardInterceptor,并重写了Intercept方法实现对调用的方法的拦截,invocation得到调用的方法名,返回值,参数名,参数的类型而params object[] args参数对应的数据,得到这些数据后,我们便可以很轻松的构造对存储过程的ADO.NET调用的代码了,同时区分处理返回值和void的情况。
值得注意的是:SqlParameter param = command.Parameters.Add("@"+paramInfos[i].Name, sqlType);这个sqlType是SQL server对应的数据类型的枚举,所以这里需要一个映射,使.net的Type转换到SqlDbType,很简单:
1
2 /// <summary>
3 /// 转化类型到SQL server对应的数据类型
4 /// </summary>
5 /// <param name="type"></param>
6 /// <returns></returns>
7 public static SqlDbType ConvertSqlType(Type type)
8 {
9 if (type.FullName.ToLower() == "system.int64")
10 {
11 return SqlDbType.BigInt;
12 }
13 else if (type.FullName.ToLower() == "system.boolean")
14 {
15 return SqlDbType.Bit;
16 }
17 else if (type.FullName.ToLower() == "system.datetime")
18 {
19 return SqlDbType.DateTime;
20 }
21 else if (type.FullName.ToLower() == "system.decimal")
22 {
23 return SqlDbType.Decimal;
24 }
25 else if (type.FullName.ToLower() == "system.double")
26 {
27 return SqlDbType.Float;
28 }
29 else if (type.FullName.ToLower() == "system.int32")
30 {
31 return SqlDbType.Int;
32 }
33 else if (type.FullName.ToLower() == "system.single")
34 {
35 return SqlDbType.Real;
36 }
37 else if (type.FullName.ToLower() == "system.int16")
38 {
39 return SqlDbType.SmallInt;
40 }
41 else if (type.FullName.ToLower() == "system.byte")
42 {
43 return SqlDbType.TinyInt;
44 }
45 else if (type.FullName.ToLower() == "system.guid")
46 {
47 return SqlDbType.UniqueIdentifier;
48 }
49 else if (type.FullName.ToLower() == "system.byte()")
50 {
51 return SqlDbType.VarBinary;
52 }
53 else if (type.FullName.ToLower() == "system.string")
54 {
55 return SqlDbType.VarChar;
56 }
57 else if (type.FullName.ToLower() == "system.object")
58 {
59 return SqlDbType.Variant;
60 }
61 else
62 {
63 throw new ArgumentOutOfRangeException();
64 }
65 }
2 /// <summary>
3 /// 转化类型到SQL server对应的数据类型
4 /// </summary>
5 /// <param name="type"></param>
6 /// <returns></returns>
7 public static SqlDbType ConvertSqlType(Type type)
8 {
9 if (type.FullName.ToLower() == "system.int64")
10 {
11 return SqlDbType.BigInt;
12 }
13 else if (type.FullName.ToLower() == "system.boolean")
14 {
15 return SqlDbType.Bit;
16 }
17 else if (type.FullName.ToLower() == "system.datetime")
18 {
19 return SqlDbType.DateTime;
20 }
21 else if (type.FullName.ToLower() == "system.decimal")
22 {
23 return SqlDbType.Decimal;
24 }
25 else if (type.FullName.ToLower() == "system.double")
26 {
27 return SqlDbType.Float;
28 }
29 else if (type.FullName.ToLower() == "system.int32")
30 {
31 return SqlDbType.Int;
32 }
33 else if (type.FullName.ToLower() == "system.single")
34 {
35 return SqlDbType.Real;
36 }
37 else if (type.FullName.ToLower() == "system.int16")
38 {
39 return SqlDbType.SmallInt;
40 }
41 else if (type.FullName.ToLower() == "system.byte")
42 {
43 return SqlDbType.TinyInt;
44 }
45 else if (type.FullName.ToLower() == "system.guid")
46 {
47 return SqlDbType.UniqueIdentifier;
48 }
49 else if (type.FullName.ToLower() == "system.byte()")
50 {
51 return SqlDbType.VarBinary;
52 }
53 else if (type.FullName.ToLower() == "system.string")
54 {
55 return SqlDbType.VarChar;
56 }
57 else if (type.FullName.ToLower() == "system.object")
58 {
59 return SqlDbType.Variant;
60 }
61 else
62 {
63 throw new ArgumentOutOfRangeException();
64 }
65 }
3.需要给定义了存储过程方法的接口创建代理,使得拦截器去拦截其中的方法:
/// <summary>
/// InvokeSP 的摘要说明。
/// </summary>
public class SpProxy
{
//public SqlConnection connection;
public SpProxy()
{
}
public static object CreatSpObject(Type inerfaceType,SqlConnection connection)
{
ProxyGenerator _generator = new ProxyGenerator();
GeneratorContext context = new GeneratorContext();
SprocInterceptor interceptor = new SprocInterceptor();
interceptor.connection=connection;
object proxy = _generator.CreateCustomProxy(inerfaceType, interceptor,new noUse(), context);
return proxy;
}
private class noUse
{
}
}
/// InvokeSP 的摘要说明。
/// </summary>
public class SpProxy
{
//public SqlConnection connection;
public SpProxy()
{
}
public static object CreatSpObject(Type inerfaceType,SqlConnection connection)
{
ProxyGenerator _generator = new ProxyGenerator();
GeneratorContext context = new GeneratorContext();
SprocInterceptor interceptor = new SprocInterceptor();
interceptor.connection=connection;
object proxy = _generator.CreateCustomProxy(inerfaceType, interceptor,new noUse(), context);
return proxy;
}
private class noUse
{
}
}
4.如何调用呢??肯定有很多朋友都会问的。
private void button2_Click(object sender, System.EventArgs e)
{
System.Data.SqlClient.SqlConnection connection=new System.Data.SqlClient.SqlConnection("Initial Catalog=Northwind;Data Source=(local);Packet Size=4096;user id=sa;password=sa");
object proxy=SpProxy.CreatSpObject(typeof(ISproces),connection);
ISproces sp=proxy as ISproces;
DataSet ds=sp.CustOrdersDetail(10249);
dataGrid1.DataSource=ds.Tables[0];
}
{
System.Data.SqlClient.SqlConnection connection=new System.Data.SqlClient.SqlConnection("Initial Catalog=Northwind;Data Source=(local);Packet Size=4096;user id=sa;password=sa");
object proxy=SpProxy.CreatSpObject(typeof(ISproces),connection);
ISproces sp=proxy as ISproces;
DataSet ds=sp.CustOrdersDetail(10249);
dataGrid1.DataSource=ds.Tables[0];
}
这样就可以对ISporces中定义的方法映射到对应名称的存储过程上去,实现调用。
示例代码下载