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

DatabaseAccessor

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (22投票s)

2007年8月13日

CPOL

4分钟阅读

viewsIcon

121880

downloadIcon

964

一个封装了基本数据库访问功能的简单基类。

引言

本文介绍了我的DatabaseAccessor类,它隐藏了使用基本ADO.NET类的一些细节。

背景

使用基本ADO.NET类(ConnectionsCommandsDataReadersDataAdapters)相当容易,但也相当重复。此外,许多初学者没有学会使用事务和参数。

当我刚开始学习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提供了对ConnectionOpenClose方法以及状态的访问。

db.Open() ;
db.Close() ;
bool b = db.IsClosed ;

设置命令

可以使用SetCommand方法设置CommandTextParametersCommandTypeCommandType默认为System.Data.CommandType.Text。Parameters集合会填充CommandText之后的任何值。Parameters将被命名为Param0ParamnSqlServerAccessorCreateParameter实现会添加@

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 ) ;

可以通过DatabaseAccessorCommandTimeout属性设置CommandCommandTimeout

db.CommandTimeout = 30 ;

可以通过DatabaseAccessor的Parameters属性访问Command的Parameters。

db.Parameters [ "@Param0" ].Value = 42 ;

执行命令

DatabaseAccessor支持底层Command的三种执行方法,并且还会检查Connection的状态,以便自动打开和关闭Connection,这与DataAdapterFill方法类似。此外,还可以传递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的首选方法是使用DatabaseAccessorCloseReader方法。

db.CloseReader() ;

ExecuteScalar

DatabaseAccessorExecuteScalar方法是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简化了使用事务的令人困惑的细节。默认的IsolationLevelSystem.Data.IsolationLevel.ReadCommitted

try
{
    db.BeginTransaction() ;
    ...
    db.CommitTransaction() ;
}
catch ( System.Exception err )
{
    db.RollbackTransaction() ;
    
    throw ( err ) ;
}

DatabaseAccessor还有一个TransactionInProgess属性和一个IsolationLevel属性。

异常

DatabaseAccessor会将遇到的任何异常封装在ConnectionExceptionOperationFailedException中,具体取决于错误的上下文。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的测试
© . All rights reserved.