一个具有ORM功能的小型ADO.NET库






4.76/5 (29投票s)
基本的 CRUD 方法以及其他一些有趣的功能
目的
这并非一个成熟的ORM解决方案,也未声称是。其目的是将一个对象映射到一张表/视图,并允许用户执行基本的CRUD(插入
、更新
、删除
、选择
)任务。
概述
这个想法很简单。创建一个类来表示数据库表或视图中的一条记录。然后将该类及其成员映射到表中。请注意,类成员必须在类的范围内可见才能被映射。如果父类映射了私有
字段,则在处理子类时将不会使用它。
映射
映射可以通过两种方式之一完成:使用属性或实现一个静态
方法。这些是源代码中提供的两种方法。通过实现Sqless.Mapping.ITableDefinitionBuilder
接口并将该类的一个实例添加到Sqless.Mapping.TableDefinitionFactory.Builders
列表中,可以轻松定义其他映射方法。您还可以重新排列此列表中的项,以指定检查类映射的顺序。(注意:访问此列表不是线程安全的。)
要使用属性将类映射到数据库表,该类需要用TableAttribute
修饰,用户可以在其中指定以下内容:
Name
- 映射表的名称Schema
- 映射表所属的Schema名称Sequence
- 与映射表关联的Sequence对象(这仅对支持Sequence的数据库(如Oracle)有意义)
要将类成员映射到表列,类成员必须用FieldAttribute
修饰,用户可以在其中指定以下内容:
Name
- 数据库列的名称Flags
- 字段标志,指定字段的访问方式和时间
FieldFlags enum
具有以下值:
Read
- 允许从数据库列读取字段值并写入.NET对象。Write
- 允许从.NET对象读取字段值并保存到数据库列。ReadWrite
- 允许字段值在两个方向上传递。等同于FieldFlags.Read | FieldFlags.Write
。如果没有指定其他标志,这应该被视为默认值。Key
- 指定此字段是表主键的一部分。Auto
- 指定此字段的值由数据库自动生成。
指定FieldFlags
时,可以使用按位或(bitwise OR)组合标志。
或者,可以在类中实现一个名为DefineTable
的静态
方法。此方法应返回一个Sqless.Mapping.TableDefinition
对象,该对象描述包含类的映射。TableDefinition
对象实现了Builder模式,因此可以链式调用方法,并且它们提供了一种定义与上述属性相同数据元素的方式。这种方法的优点是避免了发现属性映射所需的System.Reflection
调用,因此它应该更快。缺点是需要更多的代码,并且字段映射不会被子类继承。
我们稍后将看到两种映射方法的示例。
API 概述
主要对象是IDatabase
对象。它是数据库连接的包装器。它提供了一种处理事务、执行原始SQL语句和访问ITable
对象的方式。ITable
对象提供了ORM功能,即处理.NET对象和数据库表之间的映射。每个ITable
可以创建IQuery
对象,这些对象提供了一种针对该表运行带有where
和order by
子句的select
(和delete
)语句的方式。所有与底层数据库的通信都由IStatement
对象处理。IStatement
是数据库命令对象的包装器。您可以使用IStatement
执行原始SQL语句(不是很安全)或参数化SQL语句,参数化由IStatement
对象处理。简单的.NET格式化(例如WHERE id = {0}
)用于指定SQL字符串中的参数占位符。执行使用IStatement
的查询会返回IQueryResult
对象。IQueryResult
提供了方便的方法来迭代结果集。
示例映射
考虑SQL Server中的以下表
create table dbo.People
(
Id int not null identity,
Fname varchar(20),
Lname varchar(20),
Dob datetime,
constraint PK_People primary key (Id)
)
创建一个类来表示此表中的记录,并使用属性进行映射
[Table("People", "dbo")]
public class Person
{
private int? id;
private string fname;
private string lname;
private DateTime? dob;
[Field("Id", FieldFlags.Read|FieldFlags.Key|FieldFlags.Auto)]
public int? ID
{
get { return id; }
set { id = value; }
}
[Field("Fname")]
public string FirstName
{
get { return fname; }
set { fname = value; }
}
[Field("Lname")]
public string LastName
{
get { return lname; }
set { lname = value; }
}
[Field("Dob")]
public DateTime? BirthDate
{
get { return dob; }
set { dob = value; }
}
}
注意ID
字段的标志。数据库中的ID
字段是标识字段和主键。因此,标志包含FieldFlags.Key
和FieldFlags.Auto
。标记为Auto
的字段将在插入记录对象后填充生成的值。FieldFlags.Read
允许此字段只能从数据库读取,但不能写入。这很有用,因为标识字段不应被更新。也可以使任何其他字段(不仅是标识字段)只读或只写。
以下是如何使用静态
方法映射同一类
// Now we don't specify any attributes.
public class Person
{
/* Field declarations */
/* Property declarations */
public static TableDefinition DefineTable()
{
return new TableDefinition("People").Schema("dbo")
.Field("Id").MapTo("ID").ReadOnly().Key().Auto().Add()
.Field("Fname").MapTo("FirstName").Add()
.Field("Lname").MapTo("LastName").Add()
.Field("Dob").MapTo("BirthDate").Add();
}
}
对象触发器
触发器是对象方法,在对象参与数据库操作之前或之后被调用。触发器名称定义了它们何时被调用。就像所有其他类成员一样,触发器方法必须在相关类的范围内可见。也就是说,在父类中定义的私有
触发器方法在处理子类时不会被调用。触发器方法的签名是EventHandler(object sender, EventArgs args)
。第一个参数(sender
)是触发触发器的IDatabase
对象。第二个是空的EventArgs
对象。
以下是所有可能的触发器列表:
// Called before insert operation.
void BeforeInsert(object sender, EventArgs args);
// Called after insert operation.
void AfterInsert(object sender, EventArgs args);
void BeforeUpdate(object sender, EventArgs args);
void AfterUpdate(object sender, EventArgs args);
// Not called if deleting using a query.
void BeforeDelete(object sender, EventArgs args);
void AfterDelete(object sender, EventArgs args);
// No corresponding BeforeSelect.
void AfterSelect(object sender, EventArgs args);
假设我们需要修改Person
类,以便如果BirthDate
未设置,则默认为DateTime.Today
。我们可以向类添加以下方法:
public class Person
{
/* Declarations are not shown. */
protected void BeforeInsert(object sender, EventArgs args)
{
EnsureDob();
}
protected void BeforeUpdate(object sender, EventArgs args)
{
EnsureDob();
}
private void EnsureDob()
{
if (!dob.HasValue)
dob = DateTime.Today;
}
}
触发器可以自由加载和保存其他对象。假设我们定义了Customer
和Purchase
类并正确映射到相应的表。我们还希望每个Customer
始终拥有其Purchase
s列表。我们可以使用AfterSelect
触发器来实现这一点。
[Table("Customers")]
public class Customer
{
private int customerID;
private IList purchases;
protected void AfterSelect(object sender, EventArgs args)
{
purchases = (sender as IDatabase).Table(typeof(Purchase))
.Query().Eq("CustomerID", this.customerID)
.OrderBy("PurchaseDate", false)
.Select();
}
}
查询对象
IQuery
对象允许用户为Select
、Find
和Delete
操作指定搜索条件(WHERE
和ORDER BY
子句)。方法名称应该很容易理解其功能。一个例子应该使其变得微不足道。IQuery
对象实现了构建器模式,因此可以链式调用其方法。
ITable table = db.Table(typeof(Person));
// WHERE Id = 1 and Fname = 'John'
table.Query().Eq("Id", 1).And().Eq("Fname", "John");
// WHERE Fname like 'M%' and Lname = 'Smith'
table.Query().Like("Fname", "M%").And().Eq("Lname", "Smith");
// WHERE (Fname = 'John' and Lname = 'Doe') or (Fname = 'Jane' and Lname = 'Smith')
table.Query().Sub().Eq("Fname", "John").And().Eq("Lname", "Doe").EndSub()
.Or().Sub().Eq("Fname", "Jane").And().Eq("Lname", "Smith").EndSub();
// WHERE Id IN (1,2,3)
table.Query().In("Id", new int[] { 1,2,3 });
// WHERE Lname = 'Smith' ORDER BY Fname ASC
table.Query().Eq("Lname", "Smith").Order("Fname", true);
// Using a template query object, the following will select all rows
// WHERE FirstName = 'John' AND LastName = 'Smith'
// All non-null fields are used in the query.
Person p = new Person();
p.FirstName = "John";
p.LastName = "Smith";
IList johnSmiths = database.Table(p.GetType()).Query(p).Select();
执行原始SQL语句
您可以执行任何非查询SQL语句,包括insert、update和delete
。当使用IStatement
执行SQL查询时,将返回IQueryResult
对象。从此,您可以将结果集读入RowSet
对象,或提供自己的回调,这些回调将在迭代结果时被调用。
string sql = "select Id, Dob, Fname, Lname from dbo.People";
RowSet rs = database.Prepare(sql).ExecQuery().ToRowSet();
while (rs.Next()) {
Console.WriteLine("Id = {0}, Dob = {1}, Fname = {2}, Lname = {3}",
rs.Get(0), rs.Get("Dob"), rs[2], rs["Lname"] );
}
// Get a list of objects
IList people = database.Prepare(sql).ExecQuery()
.ToList(new ToListCallback(delegate(IRow row) {
MyObject p = new MyObject();
p.ID = row[0];
p.Date = row[1];
p.Text = row[2] + row[3];
return p;
}));
// Output names
database.Prepare(sql).ExecQuery()
.ForEach(new ForEachCallback(delegate(IRow row) {
Console.WriteLine( (string)row[2] + " " + (string)row[3] );
}));
空值和DBNull值
null
和DBNull
之间的转换是自动处理的。无论何时需要将null
值插入数据库字段,都应传递.NET null
值或将映射的对象字段设置为null
。从数据库中选择的任何DBNull
值在分配给对象字段或添加到RowSet
对象之前都会转换为.NET null
。支持Nullable
类型。当null
s传递给Eq
和Ne
方法时,IQuery
对象也能正确处理它们。
跟踪事件
IDatabase
对象会触发Trace
事件。在对数据库执行任何命令之前,会触发Trace
事件。此事件通常用于将生成的SQL语句和参数值写入文件或控制台。更常见的选项是配置应用程序跟踪并写入Trace
对象。这对于调试目的很有用。
更多示例
SqlConnection conn = new SqlConnection("my_connection_string");
IDatabase database = new Sqless.SqlServer.SqlDatabase(conn);
// How many Johns do we have
int count = database.Table(typeof(Person))
.Query().Eq("FirstName", "John")
.Count();
// Get Person with id = 5
Person p = (Person) database.Table(typeof(Person))
.Query().Eq("Id", 5).Find();
// Execute stored procedure SelectCustomer
string sql = "exec SelectCustomer {0}";
RowSet rs = database.Prepare(sql).ExecQuery(5).ToRowSet();
// exec InsertCustomer @p0, @p1, @p2
string sql = "exec InsertCustomer {0}, {1}, {2}";
int rowcount = 0;
using (IStatement stmt = database.Prepare(sql))
for (int i = 0; i < 10; ++i)
rowcount += stmt.ExecNonQuery(i, "John", "Doe");
历史
- 2010年4月5日:首次发布
- 2010年9月19日:更新
- 2011年11月30日:更新
- 2011年12月9日:更新