65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3投票s)

2010年8月17日

CPOL

3分钟阅读

viewsIcon

29387

downloadIcon

308

使用 .NET 接口简化存储过程执行,无需处理 ADO.NET 类

引言

本文将展示如何在不处理任何 ADO.NET 对象的情况下访问数据库,只需使用强类型接口即可。 假设我们需要调用存储过程,如果只使用纯 ADO.NET,就必须处理 Connection, CommandParameter 对象,并编写大量冗余代码。 如果调用存储过程仅仅是一个简单的函数调用,那会怎么样呢? 确实,有一些框架简化了这项任务,我并不打算超越这些框架。 例如,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 项目中找到更多示例。 欢迎提出任何意见。

© . All rights reserved.