数据访问层 (DAL) 和 SqlWrapper 库






4.74/5 (45投票s)
2005年5月27日
7分钟阅读

443840

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
提供了以下信息:
- 命令文本
- 命令类型
- 执行方法
- 参数名称
- 参数数据类型
- 参数方向
- 参数大小、精度和小数位数
- 在返回标量值的是
null
或DBNull
时,其行为。 - 在必须将
DBNull
值传递给参数时,其行为。
其中一些信息取自方法签名,另一些信息取自可选方法和方法参数属性。
下图显示了 SqlWrapper
类是如何关联的。
SqlWrapperBase
SqlWrapperBase
类是所有包装器类的基类,并包含以下属性及其对应的字段:
Connection
属性(包装受保护的m_connection
字段);Transaction
属性(包装受保护的m_transaction
字段);AutoCloseConnection
属性(包装受保护的m_autoCloseConnection
字段)。如果为true
,则每次执行命令后都会关闭连接对象。
您可以在包装器类的具体方法中使用受保护的字段。
SWCommandAttribute
这是一个可选的方法属性,包含以下属性:
CommandType
可以取以下值:SWCommandType.Text
、SWCommandType.StoredProcedure
和SWCommandType.InsertUpdate
。前三个值对应于System.Data.CommandType
枚举值。SWCommandType.InsertUpdate
将在后面描述。默认值为SWCommandType.Text
。CommandText
包含命令文本,其处理方式取决于CommandType
属性的值。ReturnIfNull
包含在命令执行结果为null
或DBNull
时返回的值。MissingSchemaAction
是SqlDataAdapter.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.Default
、SWParameterType.SPReturnValue
、SWParameterType.Key
和SWParameterType.Identity
。默认值为SWParameterType.Default
。
所有属性都是可选的。
SWParameterType.SPReturnValue
值表示此方法参数包含存储过程的返回值。在这种情况下,必须通过引用传递此方法参数。
SWParameterType.Key
和 SWParameterType.Identity
值与 SWCommandAttribute.CommandType
属性的 SWCommandType.InsertUpdate
值一起使用,其含义将在后面介绍。
InsertUpdate 命令
由于 SQL INSERT
和 UPDATE
子句非常简单但使用非常频繁,因此我在 SqlWrapper
库中添加了以下命令和参数类型,以简化向表中插入或更新数据的方法的创建。
SWCommandType.InsertUpdate
表示必须创建一个特殊的INSERT
-UPDATE
表达式。SWCommandAttribute.CommandText
必须包含表名。SWParameterType.Identity
表示此参数是标识表行的标识表列类型。必须通过引用传递方法参数。SWParameterType.Key
表示此参数是标识数据行的键的一部分(非标识表列类型)。
以下是 INSERT
- UPDATE
表达式的示例:
- 方法定义
[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
- 方法定义
[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)
如您所见,在第一个示例中,变量 @ShipperID
与 NULL
进行比较。默认情况下,如果相应的参数值小于或等于 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()
提交已打开的事务。
包装器类的 Connection
、Transaction
或 AutoCloseTransaction
属性会在其中任何一个发生更改时自动更新。
此外,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 示例。