BootFX 的对象关系映射功能入门





2.00/5 (1投票)
本文旨在向那些希望使用 BootFX(一个开源 .NET 应用程序框架)的读者介绍该框架。
引言
BootFX 是一个自 2000 年开始开发的开源 .NET 应用程序框架。它源于我和 Karli Watson 为 Wrox Press 撰写的书籍《.NET Enterprise Development》的支持工作。它已被用于商业产品,如 Topaz Filer,Web 服务,如 Twitter Ad Box 和 .NET 247,以及大量的私营和公共部门组织。它同样适用于开发 Web 和桌面应用程序。
BootFX 主要是一个对象关系映射数据库层,尽管随着时间的推移,也添加了用户界面组件和管理及运行应用程序的功能。本文的目的是引导读者完成构建框架库、生成实体层以及创建和运行示例应用程序的过程。
BootFX 系统有许多模块,我们在这里使用的是 BootFX Common 库。
构建
BootFX 的下载托管在 Google Code 上。请访问 http://www.bootfx.com/,将跳转到 Google Code 中的相应位置。然后,下载最新的源代码包。
源代码包是 Visual Studio .NET 2005 包。如果您在 Visual Studio 2008 中打开它,会提示您进行转换。该包直接从我们的构建系统中创建,因此会提示您有关源代码管理的信息。您可以在提示时永久删除所有源代码绑定。
该项目配置为仅当机器上安装了一个名为 BootFX 的密钥容器时才进行构建。.NET 2.0 中已弃用密钥容器,但个人认为这是使用 Visual Studio 密钥文件的最简单方法。(您会发现在生成设置中已禁用项目相关的警告。)因此,您有两种选择:您可以创建一个密钥文件并将其安装到容器中,或者您可以更改项目设置以使用其他方法。(例如:请参阅 http://blogs.msdn.com/akukreja/archive/2008/05/02/use-key-name-or-containers-sign-net-assembly.aspx。)
要创建和安装密钥文件,请打开 Visual Studio Tools 命令提示符并执行
sn -k BootFX.snk
然后
sn -i BootFX.snk BootFX
为了方便处理多个使用 BootFX 的项目,解决方案配置为将所有二进制文件复制到一个名为 *c:\Sluice\BootFX\Common\bin* 的文件夹中。这是通过一个名为 *BootFX.Common.Sluice.exe* 的可执行文件完成的。解决方案配置为首先构建此项目,并手动创建并将其复制到 *c:\Sluice*。然后,其他项目中的生成事件会引用该可执行文件来复制生成输出。
“sluice”实用程序可以用于任何输出程序集,只要其名称形式为 *<company>.<project>[<otherParts>]<.dll|.exe>*。
解决方案现在应该可以编译并创建 BootFX 程序集。
创建示例项目
可以从 Google Code 网站下载 SQL Server 2005 示例数据库。请访问 http://www.bootfx.com/ 并查找下载中的 *BootFX.Sample-Database.zip*。
使用 DBUtil
DBUtil 是生成 BootFX 所需实体的实用程序。它连接到 SQL Server 数据库(MySQL 可通过可选数据库支持),读取元数据,并使用 .NET 的 CodeDom 库发出类。您可以多次将 DBUtil 连接到数据库并生成类,以便在开发过程中更改数据库架构。
DBUtil 的工作方式是为每个表生成两个类:其中一个类(指定为“基”类)在每次要求 DBUtil 生成类时都会重新生成。另一个类仅在第一次生成时生成。这允许您在生成的代码中添加自定义项,而不会被后续的生成操作覆盖。此外,基类继承自 BootFX 的 Entity
类。此图说明了这一点。
DBUtil 维护一个项目文件,这是一个简单的 XML 文件。将项目文件包含在应用程序的源树中至关重要;否则,将无法与项目团队共享自定义项。
DBUtil 启动时,会提示您创建或打开一个项目。
选择 *创建新项目*。DBUtil 启动时,从菜单中选择 *项目* - *连接到数据库*。您将看到一个允许您连接到数据库的对话框。输入您希望使用的数据库的凭据。
DBUtil 然后会显示数据库中的表列表。
默认情况下,表设置为不生成(因此每个表旁边都有一个红色的 X)。您可以通过在树中选择表并在属性面板中将“生成项目”更改为“True”来打开和关闭生成。
您会注意到树中为每个表提供了两个名称,例如,“Company”和“Companies”。BootFX 允许您为表和列名提供别名,以便如果数据库中的命名非常晦涩,您可以将其映射到代码中更易于使用的名称。默认情况下,DBUtil 会尝试创建英文复数名称的单数形式。
启用 Companies 和 Projects 表的生成。
DBUtil 生成的方法和属性
DBUtil 在其默认操作中生成一组属性和方法。此外,它还会使用属性将整个元数据集合嵌入到代码中。(这意味着原始数据库架构可供正在运行的 BootFX 应用程序使用。未来的文章将详细介绍利用此功能的 BootFX 功能。)
每个类都以属性开头,这些属性告诉 BootFX 运行时它是一个实体类。实体默认也标记为可序列化。在此示例中,还提供了默认排序规范。
[Serializable()]
[Entity(typeof(ProjectCollection), "Projects")]
[SortSpecification(new string[] {
"Name"}, new BootFX.Common.Data.SortDirection[] {
BootFX.Common.Data.SortDirection.Ascending})]
public class Project : ProjectBase
{
在基类中,提供了与数据库列一对一映射的属性,例如:
[EntityField("Name", System.Data.DbType.String,
BootFX.Common.Entities.EntityFieldFlags.Common, 64)]
public string Name
{
get
{
return ((string)(this["Name"]));
}
set
{
this["Name"] = value;
}
}
BootFX 中的 Entity
类负责在内存中存储值,这是通过使用两个数组实现的:一个用于存储值,另一个用于存储描述数据状态的标志。
此外,当 BootFX 检测到数据库中的外键关系时,它将提供属性和方法来帮助导航层次结构。例如,Project
上的此属性提供了对父 Company
实例的访问(创建的实体已缓存)。
[EntityLinkToParent("Company",
"FK_Projects_Companies", typeof(Company),
new string[] { "CompanyId"})]
public Company Company
{
get
{
return ((Company)(this.GetParent("Company")));
}
set
{
this.SetParent("Company", value);
}
}
除了属性,还会生成方法。这些通常是返回与数据库中的特定值匹配的实体的静态方法。默认情况下,您会获得一个方法,用于从已知 ID 返回单个实例,或返回所有实体。
public static Project GetById(int projectId)
{
return BootFX.Common.Generated.Project.GetById(projectId,
BootFX.Common.Data.SqlOperator.EqualTo);
}
public static Project GetById(int projectId,
BootFX.Common.Data.SqlOperator projectIdOperator)
{
BootFX.Common.Data.SqlFilter filter =
new BootFX.Common.Data.SqlFilter(typeof(Project));
filter.Constraints.Add("ProjectId", projectIdOperator, projectId);
return ((Project)(filter.ExecuteEntity()));
}
...以及
public static ProjectCollection GetAll()
{
BootFX.Common.Data.SqlFilter filter =
BootFX.Common.Data.SqlFilter.CreateGetAllFilter(typeof(Project));
return ((ProjectCollection)(filter.ExecuteEntityCollection()));
}
SqlFilter
类将在稍后解释。
您还获得每个列正好一个方法。
public static ProjectCollection GetByCompanyId(int companyId)
{
return BootFX.Common.Generated.Project.GetByCompanyId(
companyId, BootFX.Common.Data.SqlOperator.EqualTo);
}
public static ProjectCollection GetByCompanyId(int companyId,
BootFX.Common.Data.SqlOperator companyIdOperator)
{
BootFX.Common.Data.SqlFilter filter =
new BootFX.Common.Data.SqlFilter(typeof(Project));
filter.Constraints.Add("CompanyId",
companyIdOperator, companyId);
return ((ProjectCollection)(filter.ExecuteEntityCollection()));
}
DBUtil 还会检测索引。然后,它会以两种模式之一生成方法。如果它检测到索引设置为唯一,它将创建一个返回单个对象而不是集合的方法,例如:
public static Project GetByJobNumber(int jobNumber)
{
return BootFX.Common.Generated.Project.GetByJobNumber(
jobNumber, BootFX.Common.OnNotFound.ReturnNull);
}
public static Project GetByJobNumber(int jobNumber,
BootFX.Common.OnNotFound onNotFound)
{
BootFX.Common.Data.SqlFilter filter =
new BootFX.Common.Data.SqlFilter(typeof(Project));
filter.Constraints.Add("JobNumber",
BootFX.Common.Data.SqlOperator.EqualTo, jobNumber);
Project results = ((Project)(filter.ExecuteEntity()));
return results;
}
如果索引包含多个列,它将创建一个接受相同数量参数的方法。这有两个优点:首先,它使得通过添加索引来向模型添加方法变得非常容易。其次,由于更容易使用索引,它鼓励开发人员记住在数据库中添加索引。
DBUtil 关于代码生成的最后一个技巧是能够将列映射到代码中的枚举。例如,如果数据库中有一个状态列,存储为整数,您希望将其映射到一个名为,例如,CacheItemStatus
的枚举,则在 DBUtil 中设置该值。
...并且将生成匹配的代码(这会影响所有相关的属性和方法参数)。
[EntityField("Status", System.Data.DbType.Int32,
BootFX.Common.Entities.EntityFieldFlags.Common)]
[DatabaseDefault(
BootFX.Common.Data.Schema.SqlDatabaseDefaultType.Primitive, 0)]
public CacheItemStatus Status
{
get
{
return ((CacheItemStatus)(this["Status"]));
}
set
{
this["Status"] = value;
}
}
将文件添加到您的项目
现在您已经了解了 DBUtil 中的代码生成工作原理,让我们来看看代码生成是如何工作的。
DBUtil 目前(尚未)与 Visual Studio 集成,这意味着您需要协同操作这两个应用程序。(尽管我们多年前曾计划将 DBUtil 作为插件提供,但这里描述的工作方法效果足够好。)
对于示例项目,我创建了一个空的 Visual Studio 解决方案,其中包含两个项目:一个类库和一个控制台应用程序。类库用于所有与 UI 无关的工作。控制台应用程序的目的是稍后运行 BootFX 代码。在类库项目中,我创建了一个名为 *Entities* 的文件夹。
从菜单中选择 *项目* - *设置*。这将显示选项对话框。将命名空间更改为 BootFX.Sample,并将“输出实体”设置为该文件夹值,使其成为 Visual Studio 项目的 Entities 文件夹的实际文件夹路径。
其他项可以保持不变。单击“确定”,然后从菜单中选择 *项目* - *生成代码*。回到 Visual Studio,您会发现这些项现在可用了。您现在可以将它们包含在项目中。
*!Base* 文件夹包含基类。(有时能够重新生成所有实体基类会很有帮助,并且将它们这样自包含可以使从源代码管理中签出它们变得容易得多。)
另一个要求是将 BootFX.Common.dll 程序集引用添加到两个项目中,例如:
启动运行时并使用实体
现在我们准备好开始使用我们的实体了。
在控制台应用程序中,创建以下类。我们稍后将填充 DoRun 的操作。
namespace BootFX.Sample
{
class Program
{
static void Main(string[] args)
{
try
{
DoRun();
}
catch (Exception ex)
{
Console.WriteLine("------------------------------");
Console.WriteLine(ex);
}
finally
{
if (System.Diagnostics.Debugger.IsAttached)
Console.ReadLine();
}
}
}
}
此外,将 BootFX.Common
和 BootFX.Common.Data
命名空间添加到类顶部的 using
语句中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BootFX.Common;
using BootFX.Common.Data;
BootFX 需要通过一个名为 Runtime.Start
的方法来初始化其内部状态。其次,我们需要告诉 BootFX 在哪里可以找到我们正在使用的数据库。将这些调用添加到 DoRun
方法中。
private static void DoRun()
{
// start the runtime...
Runtime.Start("BootFX", "Sample",
"Console",
typeof(Project).Assembly.GetName().Version);
// define the default database connection...
Database.SetDefaultDatabase(typeof(SqlServerConnection),
"data source=(local);initial catalog=sample;" +
"integrated security=sspi");
现在我们准备开始使用实体了。最简单的调用是获取所有公司并将名称显示在屏幕上。可以使用此代码完成:
// walk the companies...
foreach (Company company in Company.GetAll())
Console.WriteLine(company.Name);
运行应用程序会得到这个结果:
这可以这样增强:
// walk the companies...
foreach (Company company in Company.GetAll())
{
Console.WriteLine(company.Name);
// walk the projects...
foreach (Project project in company.GetProjectItems())
Console.WriteLine("\t{0} --> {1}",
project.JobNumber, project.Name);
}
输出现在如下:
更改数据
此时,您应该能够看到读取实体数据的简单性。更改数据库中的数据也非常直接。
Entity
基类能够跟踪对列映射属性值所做的更改,并将这些值修补回数据库。
要更新数据,只需设置属性值并调用 SaveChanges
方法。例如,此代码会循环遍历每个项目并设置 SharePointUrl
属性。
// walk...
foreach (Project project in Project.GetAll())
{
// set the sharepoint URL...
project.SharePointUrl = string.Format(
"http://myserver/projects/{0}/", project.JobNumber);
// save changes to the project...
project.SaveChanges();
}
BootFX 能够通过将开放事务附加到执行线程的方法来处理数据库事务。每当在数据库连接上创建新命令时,如果事务已附加到线程,则命令的 Transaction
属性将被设置。
如果我们采用上面的代码,可以通过添加设置、提交或回滚事务的代码来将其包装在线程绑定的事务中,如下所示:
// create a connection and bind it to the thread...
Database.BeginTransaction();
try
{
// walk...
foreach (Project project in Project.GetAll())
{
// set the sharepoint URL...
project.SharePointUrl = string.Format(
"http://myserver/projects/{0}/",
project.JobNumber);
// save changes to the project...
project.SaveChanges();
}
// commit and then unbind the transaction...
Database.Commit();
}
catch (Exception ex)
{
// rollback (then unbind) and throw...
Database.Rollback(ex);
throw new InvalidOperationException(
"The update operation failed.", ex);
}
对于插入操作,实体会跟踪 ID 值是否已设置。如果尚未设置,并且实体被标记为具有自动增量列,BootFX 将发出插入操作。
为了完成可用的 CRUD 操作,要删除一个实体,请调用 MarkForDeletion
方法,然后调用 SaveChanges
。
创建 SQL Filter 并处理基于集合的数据
为了结束讨论,值得研究一下 SqlFilter
类和 SqlStatement
类。
SqlFilter
用于从数据库加载一组实体。原则是您只提供一种实体类型来处理,然后将数据从表中的所有数据限制为您想要的子集。您可以深入研究生成的代码来查找 SqlFilter
的示例,但我们现在来看一个。
如果您只使用 Constraints
属性并提供简单的列匹配表达式,您可以确保语句能够正常工作,而与连接的数据库类型无关(例如,相同的代码适用于 SQL Server 或 MySQL)。但是,有一个选项可以为表创建“自由约束”。这允许您创建写入最终“where”子句的表达式,但会失去 SQL 变体不可知的功能。
这是一个快速示例:
private static CompanyCollection GetMagicCompanies(string projectSubset)
{
SqlFilter filter = Company.CreateFilter();
// constraint by companies that start with 'E'...
filter.Constraints.Add("name",
SqlOperator.StartsWith, "e");
// the constrain by passing in the value of the project subset...
filter.Constraints.AddFreeConstraint("companyid in (select" +
" companyid from projects where name like @subset)");
filter.ExtraParameters.Add("subset", DbType.String,
"%" + projectSubset + "%");
// run...
return (CompanyCollection)filter.ExecuteEntityCollection();
}
在底层,SqlFilter
类扩展了 SqlStatementCreator
。此类用于动态发出 SqlStatement
。SqlStatement
实例是 BootFX 中所有数据库活动的基础。它们基本上封装并生成特定的 .NET 类型,例如 System.Data.SqlClient.SqlCommand
。如果您将以下代码添加到 GetMagicCompanies
,您可以看到语句生成的效果:
// output the statement...
Console.WriteLine("---------------------------------");
Console.WriteLine(filter.GetStatement().CommandText);
Console.WriteLine("---------------------------------");
...例如:
从示例中可以看到,BootFX 正正确地管理着将参数修补到语句中、添加 order by
语句以及为相关的 SQL 变体(在此例中为 SQL Server)格式化列和表名。
使用 IntelliSense 检查 Database
类上可用的静态方法。您会注意到,这些方法允许选择标量值、将基于集合的表选择为 DataTable
或 DataSet
实例,或执行非查询语句。
结论
在本文中,我们了解了如何使用 BootFX 应用程序框架中强大的对象关系映射功能。我们研究了如何构建库、将程序集包含在自己的代码中、启动运行时以及在数据库中选择和更改数据。