C#中的简单数据访问
快速易用的数据访问类库。
引言
我在开发几个数据库驱动项目时实现了这个类库。其中一些项目使用了大量数据,因此需要实现一个快速高效且易于使用的数据访问层。我不会描述我为什么不使用类型化的DataSet、NHibernate、DLINQ或类似工具的原因(但如果您有兴趣,这里我描述了一些)。我想如果您正在阅读这篇文章,您有自己的理由。
相反,我将描述这个类库提供了什么
强类型 SQL 命令
我实现了两种 SQL 命令类型的支持:选择查询(EntityReaderCommand<TEntity>
)和脚本(EntityScriptCommand<TEntity>
)。
使用 EntityReaderCommand<TEntity>
,您可以通过执行指定的 SQL 查询从数据库读取实体。它的工作方式类似于 IDataReader
的实现,但是当 IDataReader
每次获取都返回 IDataRecord
时,EntityReaderCommand<TEntity>
返回 TEntity
类的一个实例。
此代码示例将从数据库表“Person”填充 List<Person>
var persons = new List<Person>();
var reader = new EntityReaderCommand<Person>(
args => persons.Add(args.Entity),
@"
SELECT
Id <Id>,
FirstName <FirstName>,
LastName <LastName>
FROM
Person");
reader.Execute();
此代码示例将从数据库读取 Rectangle
实例
var rectangle = new Rectangle();
new EntityReaderCommand<Rectangle>(
delegate(EntityReaderArguments<Rectangle> args)
{
rectangle = args.Entity;
args.Terminate = true;
},
@"
SELECT
1 <Location.X>,
2 <Location.Y>,
3 <Width>,
4 <Height>").Execute();
此代码示例做同样的事情,但使用 EntityScriptCommand<TEntity>
var command = new EntityScriptCommand<Rectangle>(
new Rectangle(),
@"
SET <Location.X> = 1
SET <Location.Y> = 2
SET <Width> = 3
SET <Height> = 4");
command.Execute();
var rectangle = command.Entity;
以下代码示例在数据库表“Person”中插入一个新的 Person
实例。请注意,insert.Execute()
后,Person
对象将有一个新的 Id
。
var person = new Person{
FirstName = "Boris",
LastName = "Nadezhdin"
};
var insert = new EntityScriptCommand<Person>(
person,
@"
SET <Id> = newid()
INSERT INTO
Person
(
Id,
FirstName,
LastName
)
VALUES
(
<Id>,
<FirstName>,
<LastName>
)");
insert.Execute();
因此,正如您从上面的示例中看到的那样,查询语法是 SQL 客户端原生的,唯一的区别是实体属性和列别名之间的映射。命令支持复合属性(如 Rectangle.Location
)。实体及其属性可以是引用类型或值类型。无需特殊的 XML 或基于属性的 O/R 映射,所有映射都直接在查询中指定。
实体及其属性的类型只有两个限制
- 类型应该有一个默认构造函数。
- 如果属性在命令中使用,它应该有一个公共的 getter 和 setter。
非连接实体
命令执行逻辑中没有为实体生成代理,因此如果您使用的是 EntityReaderCommand<TEntity>
,您将读取 TEntity
,而不是泛型祖先。
对实体和会话范围没有限制,您可以在一个会话中从数据库读取实体并在另一个会话中更新它。
没有实体缓存,因此您可以拥有同一个数据库实体的多个实例。
命令参数的简单语法
有时需要向命令传递一些额外的参数。例如,通过 Id
查找 Person
public Person FindOne(Guid id)
{
Person person = null;
var reader = new EntityReaderCommand<Person>(
delegate(EntityReaderArguments<Person> args)
{
person = args.Entity;
args.Terminate = true;
},
@"
SELECT
Id <Id>,
FirstName <FirstName>,
LastName <LastName>
FROM
Person
WHERE
Id = {0}", id);
reader.Execute();
return person;
}
如您从上面的示例中可以看到,语法类似于 String.Format(String, params Object[] args)
,但在这种情况下,id
不是被其字符串表示替换,而是作为参数传递给命令。
这里是另一个示例,您可以看到如何传递参数并在命令执行后取回其修改后的值
const int one = 1;
var command = new SimpleCommand(
"SET {0} = {0} + 1", one);
command.Execute();
Assert.AreEqual(one + 1, command.GetArgs()[0]);
透明的会话和事务管理
每个命令都需要一个打开的数据库会话;如果当前线程中没有打开的会话,命令会打开一个新会话并在执行后关闭它。
要手动打开新会话,您应该创建一个 SessionScope
的新实例;要关闭它,请在该 SessionScope
实例上调用 Dispose()
。因此,在 SessionScope
对象的范围内,所有命令都将使用由 SessionScope
对象创建的一个会话。
如果您正在处理 Web 应用程序并希望每个 Web 请求都有一个数据库会话,您可以在 BeginRequest
事件处理程序中创建一个 SessionScope
实例(并将其存储在 HttpContext
的项目中,例如),并在 EndRequest
事件处理程序中将其释放。
除了 SessionScope
,还有另一个命令执行范围,TransactionScope
。它用于事务管理。
要启动一个新事务,您应该创建一个 TransactionScope
的新实例。要结束它,请在该 TransactionScope
实例上调用 Dispose()
。因此,在 TransactionScope
对象的范围内,所有命令都将在由 TransactionScope
对象创建的事务中工作。事务默认标记为回滚。因此,要提交事务,您应该在该 TransactionScope
实例上调用 Commit()
。请注意,在 TransactionScope
被释放之前,不会执行实际提交。
命令范围的另一个有趣特性是它们是实例化它们所在线程的局部。因此,不同线程中的命令将始终在不同的数据库会话中工作。
原生数据库客户端抽象层
此类库中的对象不与任何具体的数据库客户端耦合。会话和事务管理、命令执行都只通过 IDatabaseProvider
接口与原生数据库客户端一起工作。所有特定于原生数据库客户端的方法都已提取到此接口中。目前,IDatabaseProvider
只有一个实现,用于 Microsoft SQL Server,但为 Oracle、PgSQL 或 MySQL 实现它非常简单。
具体的 IDatabaseProvider
实现和数据库连接字符串可以在应用程序配置文件中声明性地定义,或者通过 Scope.Configuration
以编程方式定义。默认使用应用程序配置文件,例如
<?xml version="1.0"?>
<configuration>
<configSections>
<section
name="dataAccess"
type="Yap.Data.Client.ConfigurationSectionHandler, Yap.Data.Client"/>
</configSections>
<dataAccess
providerType="Yap.Data.Client.Sql.SqlProvider, Yap.Data.Client.Sql"
providerAssembly="Yap.Data.Client.Sql.dll"
connectionString="Data Source=.\SQLEXPRESS; Database=YAP;Integrated Security=True;"/>
</configuration>
源代码
该解决方案是在 Visual Studio 2008 中创建的,包含三个项目
- Yap.Data.Client
- Yap.Data.Client.Sql
- Yap.Data.UnitTest
主要的��象模型定义在 Yap.Data.Client 中。Microsoft SQL Server 的 IDatabaseProvider
实现定义在 Yap.Data.Client.Sql 中。Yap.Data.UnitTest 包含 Yap.Data.Client 程序集的测试夹具。Yap.Data.UnitTest 使用 MSTest,但可以很容易地转换为 NUnit。
要运行单元测试,需要创建一个数据库,执行 SQL 文件夹中的 create-script.sql,并修改 app.config 文件中的连接字符串。
二进制文件
在您的项目中使用此库只需要三件事
- 按照上面的示例修改应用程序配置文件
- 在您的数据访问层项目中,添加对 Yap.Data.Client 的引用
- 将 Yap.Data.Client.Sql 复制到应用程序输出文件夹
历史
- 版本 0.0.0.1。