利用动态代理实现通用存储过程的调用
很久没有更新了,哈哈,最近太懒惰了,业余时间,写了一个关于存储过程的调用的东东,部分思想来自于Lostinet大大写的用 System.Reflection.Emit 自动实现调用存储过程的接口,他的实现是用Emit,我改用动态代理,其实内部都用到Emit
通常情况下我们利用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,
如果不清楚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>
/// 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
{
}

}
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];
}
通常情况下我们利用ADO.NET调用存储过程往往要写上好多代码,特别存储过程是参数很多的话很容易出错,而且很繁琐,看看下面这段调用存储过程的代码:

晕,居然这么多,不知道你觉得烦不烦,反正我是很讨厌,很反感了。
其实利用动态代理,可以解决很多问题,现在先假设我们调用的模式:
1.定义调用的接口,方法名对应到要调用的存储过程名,参数也与之对应(注:我用的实例数据库是NorthWind):









如果不清楚DynamicProxy请参见园子里的一些优秀的文章:DynamicProxy(动态代理)技术剖析(1) DynamicProxy(动态代理)技术剖析(2)






































































该类继承自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

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

3.需要给定义了存储过程方法的接口创建代理,使得拦截器去拦截其中的方法:


























4.如何调用呢??肯定有很多朋友都会问的。








这样就可以对ISporces中定义的方法映射到对应名称的存储过程上去,实现调用。
示例代码下载