使用 .NET 接口简化存储过程执行





4.00/5 (3投票s)
使用 .NET 接口简化存储过程执行,无需处理 ADO.NET 类
引言
本文将展示如何在不处理任何 ADO.NET 对象的情况下访问数据库,只需使用强类型接口即可。 假设我们需要调用存储过程,如果只使用纯 ADO.NET,就必须处理 Connection
, Command
和 Parameter
对象,并编写大量冗余代码。 如果调用存储过程仅仅是一个简单的函数调用,那会怎么样呢? 确实,有一些框架简化了这项任务,我并不打算超越这些框架。 例如,LINQ to SQL 允许您在设计器中拖放存储过程,然后会自动为您生成函数调用。 但是有多少开发人员已经切换到使用最新版本的 .NET Framework? 这就是为什么我制作的代码完全兼容 .NET 2.0 及以上版本的原因。
但我认为我将要向您展示的内容更深入一些。 您需要做的编码大部分是创建简单的接口。 框架将构建类来即时(缓存)实现这些接口,并为您完成处理 ADO.NET 的繁重工作。
背景
假设我们有一个简单的存储过程。
CREATE PROCEDURE ReadData
@ProductId int
AS
BEGIN
SELECT ProductID, ProductName, CreateDate, ModifyDate
FROM Product
WHERE ProductId = @ProductId
END
为了使用 ADO.NET 对象执行此过程,我们必须创建命令对象,将参数添加到此对象,获取读取器或 DataSet
,然后我们才能(例如)将其绑定到网格。 如果我们只需要一个类似这样的方法来调用呢?
IEnumerable<IProduct> products = ReadData(55);
其中 IProduct
接口只定义了几个属性,如下所示
public interface IProduct
{
int ProductId {get;}
string ProductName {get;set;}
DateTime CreateDate {get;}
ModifyDate {get;set;}
}
然后,您可以将结果绑定到(例如)DataGrid
dataGrid.DataSource = new List<IProduct>(ReadData(55));
我认为它看起来非常简单。
Using the Code
这个简单的框架允许您通过调用方法来执行存储过程。 您将能够将结果作为可枚举的集合获取,将参数传递给存储过程,就像您将参数传递给方法调用一样,从存储过程获取输出参数,以及从存储过程获取返回值。 存储过程的用法有很多种,我将尝试概述其中的一些,而本文的代码在测试项目中提供了更多示例。
让我们从数据库中的存储过程列表开始。
--Procedure #1
--This procedure will just inserting data into some table
CREATE PROCEDURE InsertData
@ProductId int
, @ProductName nvarchar(255)
AS
--.... procedure code here
--
--Procedure #2
--This procedure returns product name by its id using output parameter
CREATE PROCEDURE GetProductName1
@ProductId int
, @ProductName nvarchar(255) OUTPUT
AS
SELECT @ProductName = ProductName from Product WHERE ProductId = @ProductId
--
--Procedure #3
--This procedure returns product name as scalar value
CREATE PROCEDURE GetProductName2
@ProductId int
AS
SELECT ProductName FROM Product WHERE ProductId = @ProductId
--
--Procedure #4
--This procedure returns list of products
CREATE PROCEDURE GetProducts
AS
SELECT ProductId, ProductName FROM Product
首先,我们需要做的是定义一个接口,该接口定义带有参数和返回类型的方法。
[ConnectionName(Name="TestConnection")]
public interface IMyProcs
{
void InsertData(int productId, string productName);
void GetProductName1(int productId, out string productName);
string GetProductName2(int productId);
IEnumerable<IProduct> GetProducts();
}
为简单起见,我将方法和参数命名为与存储过程名称和存储过程参数相同。 如您所见,还有另一个接口 IProduct
。 这是
public interface IProduct
{
int ProductId {get;}
string ProductName {get;set;}
}
一旦您有了接口,您就不必担心这些接口的实现。 框架会为您完成它。 请注意,在 IProduct
接口中,我只用 getter 标记了一个属性。 无论如何,框架都能够设置此属性的值,但您有权在代码中锁定对该属性 setter 的访问,该代码正在使用此接口。
现在让我们看看如何执行这些存储过程。 执行使用 DatabaseContext<T>
类完成,其中 T
是具有方法的接口之一。 在我们的例子中,它是 IMyProcs
接口。 这是一个执行代码片段
string productName = null;
using(DatabaseContext<IMyProcs> context = new DatabaseContext<IMyProcs>())
{
context.Execute.InsertData(1234, "Some product #1 name here");
context.Execute.GetProductName1(1234, out productName);
Console.WriteLine(productName);
context.Execute.InsertData(2345, "Some product #2 name here");
productName = context.Execute.GetProductName2(2);
IEnumerable<IProduct> products = context.Execute.GetProducts();
foreach(IProduct product in products)
{
Console.WriteLine(product.ProductName);
}
}
在这里,我在单个连接下执行了 4 个存储过程,该连接由 ConnectionName
属性定义。 IMyProcs
接口的 ConnectionName
属性指示 DatabaseContext
从配置文件加载连接字符串,使用属性中指定的名称。 此属性是可选的,您可以像这样将连接名称传递给 DatabaseContext
构造函数
new DatabaseContext("TestConnection")
或者使用现有的连接,如下所示
using(SqlConnection conn = new SqlConnection(...))
{
conn.Open();
using(DatabaseContext<IMyProcs> context = new DatabaseContext<IMyProcs>(conn))
{ ....
从这个简单的例子中,您可以看到您可以执行存储过程,从中获取输出参数以及标量值和结果集。
如果您的存储过程返回多个结果集,您可以使用类 ResultSet<T>
。
using(DatabaseContext<IMyProcs> context = new DatabaseContext<IMyProcs>())
{
ResultSet<IMyFirstResult> results = context.Execute.GetMoreResults();
foreach(IMyFirstResult result in results)
{
....
}
ResultSet<MyNextResult> nextResults = results.Next<MyNextResult>();
foreach(MyNextResult result in nextResults)
{
....
}
如您所见,我使用了类而不是接口:MyNextResult
。 您不仅可以将接口用于 IEnumerable<T>
和 ResultSet<T>
泛型类型,还可以使用类。 类的一个要求是:具有默认构造函数,并且所有属性都应具有 setter。
您可以在作为 ZIP 文件一部分的 nUnit 项目中找到更多示例。 欢迎提出任何意见。