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

数据访问层 (DAL) 和 SqlWrapper 库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (45投票s)

2005年5月27日

7分钟阅读

viewsIcon

443840

downloadIcon

5885

使用 SqlWrapper,您可以轻松地创建您的 DAL 类,只需编写最少的代码。

引言

您是否曾经需要在项目中创建数据访问层?很可能您已经创建了一个或多个类,其中包含调用存储过程或执行 SQL 表达式的多个方法。这是一项非常枯燥的工作,特别是当数据库包含十几个表以上时。最糟糕的是,这些方法几乎都遵循相同的步骤(创建命令对象、填充其属性、执行并返回结果),并且很少包含其他逻辑。您有两种选择:手动编写源代码或生成源代码。无论哪种方式,都会产生大量的源代码。

我之前尝试过这两种方法,并认为一定有更简单的方法。关于AutoSproc Tool的文章给了我一个想法,我开发了我的SqlWrapper库。

一个简单的例子:

让我们编写两个类来处理 NorthWind 数据库。一个类 Orders1 使用常规方法。

public class Orders1
{
    private SqlConnection m_connection = null;

    public SqlConnection Connection
    {
        get{return m_connection;}
        set{m_connection = value;}
    }

    public DataSet CustOrdersDetail(int OrderID)
    {
        SqlCommand cmd = new SqlCommand("CustOrdersDetail", m_connection);
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.Add("@OrderID", SqlDbType.Int);
        cmd.Parameters["@OrderID"].Value = OrderID;
        SqlDataAdapter da = new SqlDataAdapter(cmd);
        DataSet ds = new DataSet();
        da.Fill(ds);
        return ds;
    }

    public int CountByEmployee(int EmployeeID)
    {
        SqlCommand cmd = new SqlCommand(
            "select count(*) from Orders where EmployeeID=@EmployeeID", 
            m_connection);
        cmd.CommandType = CommandType.Text;
        cmd.Parameters.Add("@EmployeeID", SqlDbType.Int);
        cmd.Parameters["@EmployeeID"].Value = EmployeeID;
        int count = (int)cmd.ExecuteScalar();
        return count;
    }
    
}

另一个类 Orders2 使用 SqlWrapper

public abstract class Orders2 : SqlWrapperBase
{
    public abstract DataSet CustOrdersDetail(int OrderID);

    [SWCommand("select count(*) from Orders where EmployeeID=@EmployeeID")]
    public abstract int CountByEmployee(int EmployeeID);
}

现在让我们看看这些类是如何使用的。

SqlConnection cnn = new SqlConnection(
    ConfigurationSettings.AppSettings["ConnectionString"]);
cnn.Open();

// working with the ordinary class
Orders1 orders1 = new Orders1();
orders1.Connection = cnn;
DataSet ds1 = orders1.CustOrdersDetail(10248);
int count1 = orders1.CountByEmployee(6);

// working with the wrapped class
Orders2 orders2 = (Orders2)WrapFactory.Create(typeof(Orders2));
orders2.Connection = cnn;
DataSet ds2 = orders2.CustOrdersDetail(10248);
int count2 = orders2.CountByEmployee(6);

区别显而易见。Orders2 类包含的代码量少得多,但在使用上与 Orders1 类相同,只是创建方式不同。

工作原理

要创建包装器类,您必须让您的类继承自 SqlWrapperBase 类,并定义带有自定义属性的抽象方法,这些属性将描述您想要执行的操作以及如何获取结果。如果未指定方法属性,则方法名将用作存储过程名。您还可以定义任意数量的具体方法,以防需要简单的 SQL 表达式执行之外的其他逻辑。之后,您可以通过调用 WrapFactory.Create() 方法来创建类的对象。此方法使用 System.Reflection.Emit 命名空间中的类为抽象方法添加实现。例如,以下方法

[SWCommand("select count(*) from Orders where EmployeeID=@EmployeeID")]
public abstract int CountByEmployee(int EmployeeID);

将被实现为这样:

[SWCommand("select count(*) from Orders where EmployeeID=@EmployeeID")]
public int CountByEmployee(int EmployeeID)
{
    MethodInfo method = (MethodInfo)MethodBase.GetCurrentMethod();
    object[] values = new object[1];
    values[0] = EmployeeID;
    object obj = SWExecutor.ExecuteMethodAndGetResult(
            m_connection, 
            m_transaction, 
            method, 
            values, 
            m_autoCloseConnection);

    return (int)obj;
}

主要工作由 SWExecutor.ExecuteMethodAndGetResult() 方法完成,该方法创建 SqlCommand 对象并返回其执行结果。为此,变量 method 提供了以下信息:

  • 命令文本
  • 命令类型
  • 执行方法
  • 参数名称
  • 参数数据类型
  • 参数方向
  • 参数大小、精度和小数位数
  • 在返回标量值的是 nullDBNull 时,其行为。
  • 在必须将 DBNull 值传递给参数时,其行为。

其中一些信息取自方法签名,另一些信息取自可选方法和方法参数属性。

下图显示了 SqlWrapper 类是如何关联的。

SqlWrapperBase

SqlWrapperBase 类是所有包装器类的基类,并包含以下属性及其对应的字段:

  • Connection 属性(包装受保护的 m_connection 字段);
  • Transaction 属性(包装受保护的 m_transaction 字段);
  • AutoCloseConnection 属性(包装受保护的 m_autoCloseConnection 字段)。如果为 true,则每次执行命令后都会关闭连接对象。

您可以在包装器类的具体方法中使用受保护的字段。

SWCommandAttribute

这是一个可选的方法属性,包含以下属性:

  • CommandType 可以取以下值:SWCommandType.TextSWCommandType.StoredProcedureSWCommandType.InsertUpdate。前三个值对应于 System.Data.CommandType 枚举值。SWCommandType.InsertUpdate 将在后面描述。默认值为 SWCommandType.Text
  • CommandText 包含命令文本,其处理方式取决于 CommandType 属性的值。
  • ReturnIfNull 包含在命令执行结果为 nullDBNull 时返回的值。
  • MissingSchemaActionSqlDataAdapter.MissingSchemaAction 的值。默认值为 MissingSchemaAction.Add

CommandText 属性外,所有属性都是可选的。

重要提示:如果省略此属性,则 CommandText 属性将使用方法名,而 CommandType 属性等于 SWCommandType.StoredProcedure

SWParameterAttribute

这是一个可选的参数属性,包含以下属性:

  • Name 包含命令参数名。如果省略,则使用方法参数名。
  • SqlDbType 包含命令参数的值类型,并对应于 SqlParameter.SqlDbType 属性。
  • Size 包含命令参数大小,并对应于 SqlParameter.Size 属性。
  • Precision 包含命令参数精度,并对应于 SqlParameter.Presision 属性。
  • Scale 包含命令参数小数位数,并对应于 SqlParameter.Scale 属性。
  • TreatAsNull 包含将被解释为 DBNull 值的值。此属性对于标量类型参数很有用。
  • ParameterType 可以取以下值之一:SWParameterType.DefaultSWParameterType.SPReturnValueSWParameterType.KeySWParameterType.Identity。默认值为 SWParameterType.Default

所有属性都是可选的。

SWParameterType.SPReturnValue 值表示此方法参数包含存储过程的返回值。在这种情况下,必须通过引用传递此方法参数。

SWParameterType.KeySWParameterType.Identity 值与 SWCommandAttribute.CommandType 属性的 SWCommandType.InsertUpdate 值一起使用,其含义将在后面介绍。

InsertUpdate 命令

由于 SQL INSERTUPDATE 子句非常简单但使用非常频繁,因此我在 SqlWrapper 库中添加了以下命令和参数类型,以简化向表中插入或更新数据的方法的创建。

  • SWCommandType.InsertUpdate 表示必须创建一个特殊的 INSERT - UPDATE 表达式。SWCommandAttribute.CommandText 必须包含表名。
  • SWParameterType.Identity 表示此参数是标识表行的标识表列类型。必须通过引用传递方法参数。
  • SWParameterType.Key 表示此参数是标识数据行的键的一部分(非标识表列类型)。

以下是 INSERT - UPDATE 表达式的示例:

  1. 方法定义
    [SWCommand(SWCommandType.InsertUpdate, "Shippers")]
    public abstract void ShippersInsertUpdate
        (
        [SWParameter(SWParameterType.Identity)]ref int ShipperID,
        [SWParameter(40)]string CompanyName,
        [SWParameter(24)]string Phone
        );

    SQL 表达式

    if(@ShipperID is NULL) 
    begin  
        insert into [Shippers]([CompanyName], [Phone]) 
        values(@CompanyName, @Phone)  
        
        select @ShipperID = SCOPE_IDENTITY() 
    end 
    else 
    begin  
        update [Shippers] set 
        [CompanyName]=@CompanyName, 
        [Phone]=@Phone 
        where [ShipperID]=@ShipperID  
    end
  2. 方法定义
    [SWCommand(SWCommandType.InsertUpdate, "Order Details")]
    public abstract void OrderDetailsInsertUpdate
        (
        [SWParameter(SWParameterType.Key)]int OrderID,
        [SWParameter(SWParameterType.Key)]int ProductID,
        Decimal UnitPrice,
        Int16 Quantity,
        float Discount
        );

    SQL 表达式

    update [Order Details] set 
    [OrderID]=@OrderID, 
    [ProductID]=@ProductID, 
    [UnitPrice]=@UnitPrice, 
    [Quantity]=@Quantity, 
    [Discount]=@Discount 
    where [OrderID]=@OrderID and [ProductID]=@ProductID  
    
    if (@@rowcount = 0)  
        insert into [Order Details]([OrderID], [ProductID], [UnitPrice], 
        [Quantity], [Discount]) 
        values(@OrderID, @ProductID, @UnitPrice, @Quantity, @Discount)

如您所见,在第一个示例中,变量 @ShipperIDNULL 进行比较。默认情况下,如果相应的参数值小于或等于 0,则具有此值。如果您愿意,可以在 SWParameter.TreatAsNull 属性中设置任何其他值,该值将被视为 NULL

在您的应用程序中创建数据访问层

SqlWrapper 库包含一个名为 DataAccessLayerBase 的基类,可用于您的自定义数据访问层 (DAL) 类,只需最少的代码。您需要做的就是创建一个继承自 DataAccessLayerBase 类的类,并像这样声明您的包装器类类型的属性:

public YourWrapperClass YourPropertyName
{
    get
    {
      return (YourWrapperClass)GetWrapped();
    }
}

这就是全部。您还可以根据需要添加任何其他成员。这是一个可以创建的 DAL 类的示例(Orders2 是上面显示的类,UserClass1 是另一个包装器类)。

public class MyDAL : DataAccessLayerBase
{
    public UserClass1 UserClass1{get{return (UserClass1)GetWrapped();}}
    public Orders2 Orders2{get{return (Orders2)GetWrapped();}}
}

这是您的 DAL 类的类图。

以下是如何使用您的 DAL 类的示例。

MyDAL dal = new MyDAL();
dal.Init(cnn, true, true);
int c = dal.Orders2.CountByEmployee(6);
DataTable dt = dal.UserClass1.Method1();

在使用您的 DAL 类对象之前,必须调用从 DataAccessLayerBase 类继承的一个重载的 Init() 方法。

public void Init(SqlConnection connection, bool autoCloseConnection, 
          bool ownsConnection);
public void Init(string connectionString, bool autoCloseConnection);

这些方法非常重要,因为除了设置连接属性之外,它们还会调用私有方法 GenerateAllWrapped(),该方法会枚举 DAL 类的所有方法,创建包装器类的对象,并将它们保存在私有 Hashtable m_swTypes 中。

private void GenerateAllWrapped()
{
    MethodInfo[] mis = this.GetType().GetMethods();
    for(int i = 0; i < mis.Length; ++i)
    {
        Type type = mis[i].ReturnType;
        if(type.GetInterface(typeof(ISqlWrapperBase).FullName) == 
                 typeof(ISqlWrapperBase))
        {
            if(mis[i].Name.StartsWith("get_"))
            {
                if(!m_swTypes.ContainsKey(mis[i].Name))
                {
                    ISqlWrapperBase sw = WrapFactory.Create(type);
                    m_swTypes[mis[i].Name] = sw;
                }
            }
        }
    }
}

您声明的属性会调用受保护方法 GetWrapped(),该方法查找调用方法的名称,并从 m_swTypes 返回相应的对象。

protected ISqlWrapperBase GetWrapped()
{
    MethodInfo mi = (MethodInfo)(new StackTrace().GetFrame(1).GetMethod());
    ISqlWrapperBase res = (ISqlWrapperBase)m_swTypes[mi.Name];
    if(res == null)
    {
        throw new SqlWrapperException("The object is not initialized.");
    }
    return res;
}

DataAccessLayerBase 类中的三个方法简化了事务处理。

  • BeginTransaction()BeginTransaction(IsolationLevel iso) 打开一个新的事务。
  • RollbackTransaction() 回滚已打开的事务。
  • CommitTransaction() 提交已打开的事务。

包装器类的 ConnectionTransactionAutoCloseTransaction 属性会在其中任何一个发生更改时自动更新。

此外,DataAccessLayerBase 类还有几个方法(ExecuteDataSet()ExecuteDataTable()ExecuteScalar()ExecuteNonQuery()),有助于执行用户 SQL 表达式,可用于即席查询。

其他语言

我已在 VB.NET 中测试了 SqlWrapper,我猜它可以在 .NET 平台上的任何语言中使用。在 VB.NET 中,Orders2 类可以这样编写:

Public MustInherit Class Orders2
    Inherits SqlWrapperBase

    Public MustOverride Function _
           CustOrdersDetail(ByVal OrderID As Integer) As DataSet

    <SWCommand("select count(*) from Orders where EmployeeID=@EmployeeID")> _
    Public MustOverride Function _
           CountByEmployee(ByVal EmployeeID As Integer) As Integer

End Class

MyDAL 可以这样编写:

Public Class MyDAL
    Inherits DataAccessLayerBase

    Public ReadOnly Property UserClass1() As UserClass1
    Get
        Return GetWrapped()
    End Get
    End Property

    Public ReadOnly Property Orders2() As Orders2
    Get
        Return GetWrapped()
    End Get
    End Property

End Class

计划要做的事情

SqlWrapper 库正在不断发展。目前,它包含了我在日常工作中遇到的大部分 .NET 和 MS SQL Server 2000 的需求,并且在不久的将来,我计划添加 Oracle 和 OLEDB 数据提供程序。

历史

  • 2005-05-27 - 初始发布。
  • 2005-06-09 - 添加了 VB.NET 示例。

参考

© . All rights reserved.