DatabaseAccessor






4.73/5 (22投票s)
一个封装了基本数据库访问功能的简单基类。
引言
本文介绍了我的DatabaseAccessor
类,它隐藏了使用基本ADO.NET类的一些细节。
背景
使用基本ADO.NET类(Connections
、Commands
、DataReaders
、DataAdapters
)相当容易,但也相当重复。此外,许多初学者没有学会使用事务和参数。
当我刚开始学习ADO.NET时,我很快就决定需要在数据访问层和基本类之间添加一个层。本文介绍了我在多年来开发的两种实现中较为轻量级的实现,并且目前正在使用。此实现目前仅支持SQL Server数据库引擎。
Using the Code
ZIP文件中包含的文件
- DatabaseAccessor.cs -- 定义了用于派生Accessor的抽象基类
- DatabaseException.cs -- 定义了一些简单的异常
- SqlServerAccessor.cs -- 一个派生的DatabaseAccessor,它使用System.Data.SqlClient中的类
- SqlServerErrorInfo.cs -- 这有助于确定发生了什么类型的错误
- DbSamp.cs -- 示例程序
连接到数据库
通过不要求应用程序知道完整的ConnectionString
,可以简化创建数据库Connection
的过程。例如,SqlServerAccessor
的一个构造函数只需要服务器和数据库的名称。此外,该构造函数不仅创建Connection
,还创建Command
,从而节省了开发者的工作。
PIEBALD.Database.SqlServerAccessor db =
new PIEBALD.Database.SqlServerAccessor ( "localhost" , "Northwind" ) )
PIEBALD.Database.SqlServerAccessor db =
new PIEBALD.Database.SqlServerAccessor ( ConnectionString ) )
使用连接
DatabaseAccessor
提供了对Connection
的Open
和Close
方法以及状态的访问。
db.Open() ;
db.Close() ;
bool b = db.IsClosed ;
设置命令
可以使用SetCommand
方法设置CommandText
、Parameters
和CommandType
。CommandType
默认为System.Data.CommandType.Text
。Parameters集合会填充CommandText
之后的任何值。Parameters将被命名为Param0
到Paramn
;SqlServerAccessor
的CreateParameter
实现会添加@
。
db.SetCommand ( "SELECT * FROM Employees" ) ;
db.SetCommand ( "SELECT * FROM Employees WHERE EmployeeID=@Param0" , 42 ) ;
db.SetCommand ( "SELECT * FROM Employees WHERE EmployeeID=@Param0" ,
System.Data.CommandType.Text , 42 ) ;
可以通过DatabaseAccessor
的CommandTimeout
属性设置Command
的CommandTimeout
。
db.CommandTimeout = 30 ;
可以通过DatabaseAccessor
的Parameters属性访问Command
的Parameters。
db.Parameters [ "@Param0" ].Value = 42 ;
执行命令
DatabaseAccessor
支持底层Command
的三种执行方法,并且还会检查Connection
的状态,以便自动打开和关闭Connection
,这与DataAdapter
的Fill
方法类似。此外,还可以传递CommandText
和参数值,从而节省了开发人员调用SetCommand
。
ExecuteNonQuery
int i = db.ExecuteNonQuery() ; // If SetCommand has been used
int i = db.ExecuteNonQuery (
"UPDATE Employees SET ReportsTo=42 WHERE ReportsTo!=42" ) ;
int i = db.ExecuteNonQuery (
"UPDATE Employees SET ReportsTo=@Param0 WHERE ReportsTo!=@Param0", 42);
ExecuteReader
System.Data.IDataReader r = db.ExecuteReader() ;
//If SetCommand has been used
System.Data.IDataReader r = db.ExecuteReader ( "SELECT * FROM Employees" ) ;
System.Data.IDataReader r = db.ExecuteReader (
"SELECT * FROM Employees WHERE EmployeeID!=42" ) ;
System.Data.IDataReader r = db.ExecuteReader (
"SELECT * FROM Employees WHERE EmployeeID!=@Param0" , 42 ) ;
DatabaseAccessor
还通过其DataReader
属性提供对DataReader
的访问。
while ( db.DataReader.Read ) { ... }
关闭DataReader
的首选方法是使用DatabaseAccessor
的CloseReader
方法。
db.CloseReader() ;
ExecuteScalar
DatabaseAccessor
的ExecuteScalar
方法是Generic
的,从而节省了开发人员声明一个对象来接收值、执行、测试null、然后执行转换和/或分配某个默认值的工作。第一个参数是在结果为null时使用的值。
int i = db.ExecuteScalar<int> ( -1 ) ; // If SetCommand has been used
int i = db.ExecuteScalar<int> ( -1 ,
"SELECT ReportsTo FROM Employees WHERE EmployeeID=42" ) ;
int i = db.ExecuteScalar<int> ( -1 ,
"SELECT ReportsTo FROM Employees WHERE EmployeeID=@Param0" , 42 ) ;
通过将类型指定为object
并将默认结果指定为null
,可以请求ExecuteScalar
的正常操作。
object o = db.ExecuteScalar<object> ( null ) ; // If SetCommand has been used
object o = db.ExecuteScalar<object> ( null ,
"SELECT ReportsTo FROM Employees WHERE EmployeeID=42" ) ;
object o = db.ExecuteScalar<object> ( null ,
"SELECT ReportsTo FROM Employees WHERE EmployeeID=@Param0" , 42 ) ;
ExecuteDataTable
DatabaseAccessor
还简化了填充DataTable的常见任务。
System.Data.DataTable d = db.ExecuteDataTable() ;
//If SetCommand has been used
System.Data.DataTable d = db.ExecuteDataTable (
"SELECT * FROM Employees" ) ;
System.Data.DataTable d = db.ExecuteDataTable (
"SELECT * FROM Employees WHERE EmployeeID!=42" ) ;
System.Data.DataTable d = db.ExecuteDataTable (
"SELECT * FROM Employees WHERE EmployeeID!=@Param0" , 42" ) ;
检索数据库模式的基本信息也已内置。
System.Data.DataTable d = db.ListTables() ;
System.Data.DataTable d = db.ListColumns ( "Employees" ) ;
事务
DatabaseAccessor
简化了使用事务的令人困惑的细节。默认的IsolationLevel
是System.Data.IsolationLevel.ReadCommitted
。
try
{
db.BeginTransaction() ;
...
db.CommitTransaction() ;
}
catch ( System.Exception err )
{
db.RollbackTransaction() ;
throw ( err ) ;
}
DatabaseAccessor
还有一个TransactionInProgess
属性和一个IsolationLevel
属性。
异常
DatabaseAccessor
会将遇到的任何异常封装在ConnectionException
或OperationFailedException
中,具体取决于错误的上下文。SqlServerAccessor
能够确定底层问题。
派生新的DatabaseAccessors
DatabaseAccessor
类是抽象的;派生类必须实现以下方法
CreateParameter ( string Name , object Value )
ExecuteDataTable ( System.Data.DataTable DataTable )
ListTables()
派生类还必须实现至少一个构造函数,该构造函数实例化一个Connection
并将其传递给基构造函数。派生类还可以根据需要重写其他方法(SqlServerAccessor
实现了自己的Parameters属性和WrapDataExecption
方法)。SqlServerAccessor
可以作为派生其他DatabaseAccessor
的模板。
编写数据访问层
在数据库和应用程序之间应该至少还有一个抽象层:数据访问层。数据访问层使应用程序不必了解正在使用的实际数据库实现。然后,应用程序可以使用概念术语而不是SQL语句,例如“GetEmployeeList
”而不是“SELECT * FROM Employees
”。这样的数据访问层实现可以通过以下方式使用DatabaseAccessor
:派生自它
public class NorthwindAccessor : PIEBALD.Database.SqlServerAccessor
{
public NorthwindAccessor() : base ( "localhost" , "Northwind" ) {}
...
}
或者声明一个字段来保存对DatabaseAccessor
的引用
public class NorthwindAccessor
{
private PIEBALD.Database.SqlServerAccessor db =
new PIEBALD.Database.SqlServerAccessor ( "localhost" , "Northwind" );
...
}
这两种技术之间的主要区别在于,前者允许应用程序执行即席SQL语句,这很方便,但可能会违反数据访问层的概念。后者不允许访问数据库,因此更可取。第二种技术的少数几种变体是
public class NorthwindAccessor
{
private PIEBALD.Database.SqlServerAccessor db ;
public NorthwindAccessor()
{
this.db = new PIEBALD.Database.SqlServerAccessor ( "localhost" ,
"Northwind" ) ;
return ;
}
...
}
public class NorthwindAccessor
{
private PIEBALD.Database.SqlServerAccesor db ;
private NorthwindAccessor()
{
this.db = new PIEBALD.Database.SqlServerAccessor ( "localhost" ,
"Northwind" ) ;
return ;
}
public static NorthwindAccessor
Connect()
{
return ( new NorthwindAccessor() ) ;
}
...
}
然后只需向类添加方法来实现必要数据库请求。这些方法仅用于说明目的;我对它们是否是“好”实现不做任何声明。请注意,我还添加了IDisposable
的实现,并将该类声明为sealed。
public sealed class NorthwindAccessor : System.IDisposable
{
private PIEBALD.Database.SqlServerAccessor db ;
private NorthwindAccessor()
{
this.db = new PIEBALD.Database.SqlServerAccessor
(
"localhost"
,
"Northwind"
) ;
return ;
}
public static NorthwindAccessor
Connect()
{
return ( new NorthwindAccessor() ) ;
}
public void Dispose()
{
db.Dispose() ;
return ;
}
public System.Data.DataTable GetEmployeeList()
{
return ( db.ExecuteDataTable
(
@"
SELECT *
FROM Employees
"
) ) ;
}
public System.Data.DataRow GetEmployee ( int EmployeeID )
{
return ( db.ExecuteDataTable
(
@"
SELECT *
FROM Employees
WHERE EmployeeID=@Param0
"
,
EmployeeID
).Rows [ 0 ] ) ;
}
public System.DateTime GetEmployeeHireDate ( int EmployeeID )
{
return ( db.ExecuteScalar<System.DateTime>
(
System.DateTime.MaxValue
,
@"
SELECT HireDate
FROM Employees
WHERE EmployeeID=@Param0
"
,
EmployeeID
) ) ;
}
}
此示例来自包含的DbSamp.cs文件。
历史
- 2007-08-07首次提交
- 2007-08-21在ExecuteScalar中添加了对System.DBNull.Value的测试