通常情况下我们利用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);
}如果不清楚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;
}
}
该类继承自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
{
}
}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];
}这样就可以对ISporces中定义的方法映射到对应名称的存储过程上去,实现调用。
示例代码下载