试验自定义生成提供程序






4.69/5 (17投票s)
一个简单的 OR 映射器,使用自定义构建提供程序构建,该提供程序从 XML 文件读取数据。
引言
最近,Microsoft 发布了 .NET 2.0 框架附带的内置提供程序的源代码。虽然我从未下载过它们,但这次发布引起了我的注意,因此我开始研究 ASP.NET 的这项新功能。我对构建提供程序的强大功能印象深刻(我不知道为什么它们直到现在才引起我的注意),所以我决定与您分享我的周末体验。
声明式类型创建
我从在 App_Code 中创建一个 XML 文件开始,尝试从中动态创建类型。这是文件的内容
<types>
<class name="User" namespace="Mapper.Core">
<property name="ID" type="System.Int32" />
<property name="FirstName" type="System.String" />
<property name="LastName" type="System.String" />
<property name="Username" type="System.String" />
<property name="Password" type="System.String" />
<property name="Email" type="System.String" />
</class>
</types>
在阅读了一些关于自定义构建提供程序的文章(其中一篇是 Javier Lozano 的精彩文章,可以在此处找到)以及其他关于 CodeDom 的文章后,我开始构建我的测试提供程序。
我创建了 EntityBuildProvider
类,它扩展了 System.Web.Compilation.BuildProvider
类。ASP.NET 引擎在开发和编译期间执行 GenerateCode
方法,在 Temporary ASP.NET Files 文件夹中为自定义提供程序生成代码,并将它们包含在 App_Code 程序集中。
public override void GenerateCode(AssemblyBuilder assemblyBuilder)
{
string fileName = base.VirtualPath;
CodeCompileUnit generatedUnit = GenerateUnit(fileName);
assemblyBuilder.AddCodeCompileUnit(this, generatedUnit);
}
GenerateUnit
方法读取 XML 文件内容,并使用 CodeDom 生成包含找到的属性的类。然后,我在 web.config 文件中添加我的构建提供程序
<compilation debug="true">
<buildProviders>
<add extension=".xml"
type="ObjectMapper.EntityBuildProvider, ObjectMapper"/>
</buildProviders>
</compilation>
结果是在Visual Studio 2005 中生成了一个具有完整智能感知支持的类型
使用一些字符串实用方法,我添加了对 PascalCase/camelCase 代码生成器的支持,并且 ASP.NET 引擎会在 Temporary ASP.NET Files 文件夹中创建我的 User
类,并将其包含在 App_Code 程序集中。
namespace Mapper.Core
{
public class User
{
private System.Int32 _id;
private System.String _firstName;
private System.String _lastName;
private System.String _username;
private System.String _password;
private System.String _email;
public User()
{
}
public System.Int32 ID
{
get { return _id; }
set { _id = value; }
}
public System.String FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
public System.String LastName
{
get { return _lastName; }
set { _lastName = value; }
}
public System.String Username
{
get { return _username; }
set { _username = value; }
}
public System.String Password
{
get { return _password; }
set { _password = value; }
}
public System.String Email
{
get { return _email; }
set { _email = value; }
}
}
}
在为我的 User
类添加了一些附加属性后,我单击“保存”,Visual Studio 会再次执行我的自定义提供程序的 GenerateCode
方法,而无需我重新编译应用程序,并且我所有新添加的属性都会被创建,并且我拥有对它们的完整智能感知支持。
<types>
<class name="User" namespace="Mapper.Core">
<property name="ID" type="System.Int32" />
<property name="FirstName" type="System.String" />
<property name="LastName" type="System.String" />
<property name="Username" type="System.String" />
<property name="Password" type="System.String" />
<property name="Email" type="System.String" />
<property name="Enabled" type="System.Boolean" />
<property name="Phone" type="System.String" />
<property name="Address1" type="System.String" />
<property name="Address2" type="System.String" />
</class>
</types>
扩展我的自定义语言
看到我的自定义构建提供程序允许我使用 .NET 框架的所有强大功能来实现自定义编程语言(即使我最喜欢的 CodeProject 成员写了一篇关于局限性的精彩文章),我开始添加更多功能
对于 class
XML 节点
table
属性 - 表示映射到生成类的 SQL Server 表。GenerateStoredProcedures
属性(true
/false
) - 指定提供程序是否应为映射的表生成存储过程。SqlStoredProceduresPrefix
属性 - 指定生成的存储过程的前缀。DropExistingStoredProcedures
属性 - 指定生成的 SQL 脚本是否应包含现有数据库对象的DROP
语句。ExportLocation
属性 - 指定应生成 SQL 脚本文件的物理位置。
对于 property
XML 节点
column
属性 - 表示映射到生成属性的表列。IsPrimaryKey
属性 - 表示属性是否为对象的标识符(支持多个标识符)。IsIdentity
属性 - 指定映射的列是否为标识列。SqlType
属性 - 指定映射列的 SQL 类型。SqlLength
属性 - 指定映射列的 SQL 长度。
我还创建了一些附加的字符串实用方法,用于获取名称的复数/单数形式,并且生成的属性还包含一些基本的 XML 注释。由于本文的目的是创建自定义构建提供程序,因此我们不讨论 CodeDom 的实现过程。但是,如果您对该命名空间感兴趣,可以尝试阅读一篇精彩的文章,该文章可以在此处找到。
最终的 XML 文件看起来像这样
<?xml version="1.0" encoding="utf-8" ?>
<types>
<class name="User" table="Users" namespace="Mapper.Core"
SqlStoredProceduresPrefix="DB_"
GenerateStoredProcedures="true"
ExportLocation="E:\GeneratedFiles\">
<property name="ID" column="ID" IsIdentity="true"
IsPrimaryKey="true"
type="System.Int32" SqlType="int" />
<property name="FirstName" column="FirstName"
type="System.String" SqlType="nvarchar"
SqlLength="50" />
<property name="LastName" column="LastName"
type="System.String" SqlType="nvarchar"
SqlLength="50" />
<property name="Username" column="Username"
type="System.String" SqlType="nvarchar"
SqlLength="50" />
<property name="Password" column="Password"
type="System.String" SqlType="nvarchar"
SqlLength="50" />
<property name="Enabled" column="Enabled"
type="System.Boolean" SqlType="bit" />
<property name="Email" column="Email"
type="System.String" SqlType="nvarchar"
SqlLength="50" />
<property name="Phone" column="Phone"
type="System.String" SqlType="nvarchar"
SqlLength="50" />
<property name="Address1" column="Address1"
type="System.String" SqlType="nvarchar"
SqlLength="50" />
<property name="Address2" column="Address2"
type="System.String" SqlType="nvarchar"
SqlLength="50" />
</class>
</types>
以及自定义提供程序生成的代码
namespace Mapper.Core {
[System.Serializable()]
public class User : ObjectMapper.Utils.Entity {
private int _id;
private string _firstName;
private string _lastName;
private string _username;
private string _password;
private bool _enabled;
private string _email;
private string _phone;
private string _address1;
private string _address2;
/// <summary>
/// Gets or sets the user's identifier.
/// </summary>
public int ID {
get {
return _id;
}
set {
if ((value != this._id)) {
this._id = value;
base.MarkDirty();
}
}
}
/// <summary>
/// Gets or sets the user's first name.
/// </summary>
public string FirstName {
get {
return _firstName;
}
set {
if ((value != this._firstName)) {
this._firstName = value;
base.MarkDirty();
}
}
}
// ----------------------------------------
// All other properties go here
// ----------------------------------------
public static Mapper.Core.User GetUser(int id) {
System.Data.SqlClient.SqlCommand selectCommand;
selectCommand =
ObjectMapperUtils.DataUtility.CreateCommand(
"DB_Users_Select");
selectCommand.Parameters.AddWithValue("@ID", id);
System.Collections.Generic.List<Mapper.Core.User> users;
users = Mapper.Core.User.UserListFromReader(
ObjectMapperUtils.DataUtility.ExecuteReader(
selectCommand));
if ((users.Count > 0)) {
return users[0];
}
return null;
}
public override void Insert() {
System.Data.SqlClient.SqlCommand insertCommand;
insertCommand =
ObjectMapperUtils.DataUtility.CreateCommand(
"DB_Users_Insert");
insertCommand.Parameters.AddWithValue("@FirstName", this.FirstName);
insertCommand.Parameters.AddWithValue("@LastName", this.LastName);
insertCommand.Parameters.AddWithValue("@Username", this.Username);
insertCommand.Parameters.AddWithValue("@Password", this.Password);
insertCommand.Parameters.AddWithValue("@Enabled", this.Enabled);
insertCommand.Parameters.AddWithValue("@Email", this.Email);
insertCommand.Parameters.AddWithValue("@Phone", this.Phone);
insertCommand.Parameters.AddWithValue("@Address1", this.Address1);
insertCommand.Parameters.AddWithValue("@Address2", this.Address2);
System.Data.IDataReader reader;
reader =
ObjectMapperUtils.DataUtility.ExecuteReader(insertCommand);
if ((reader.Read() == true)) {
this.ID = System.Convert.ToInt32(reader[0]);
}
if ((reader.IsClosed != true)) {
reader.Close();
}
base.MarkOld();
}
public override void Update() {
System.Data.SqlClient.SqlCommand updateCommand;
updateCommand =
ObjectMapperUtils.DataUtility.CreateCommand("DB_Users_Update");
updateCommand.Parameters.AddWithValue("@ID", this.ID);
updateCommand.Parameters.AddWithValue("@FirstName", this.FirstName);
updateCommand.Parameters.AddWithValue("@LastName", this.LastName);
updateCommand.Parameters.AddWithValue("@Username", this.Username);
updateCommand.Parameters.AddWithValue("@Password", this.Password);
updateCommand.Parameters.AddWithValue("@Enabled", this.Enabled);
updateCommand.Parameters.AddWithValue("@Email", this.Email);
updateCommand.Parameters.AddWithValue("@Phone", this.Phone);
updateCommand.Parameters.AddWithValue("@Address1", this.Address1);
updateCommand.Parameters.AddWithValue("@Address2", this.Address2);
ObjectMapperUtils.DataUtility.ExecuteNonQuery(updateCommand);
base.MarkOld();
}
public override void Delete() {
System.Data.SqlClient.SqlCommand deleteCommand;
deleteCommand =
ObjectMapperUtils.DataUtility.CreateCommand("DB_Users_Delete");
deleteCommand.Parameters.AddWithValue("@ID", this.ID);
ObjectMapperUtils.DataUtility.ExecuteNonQuery(deleteCommand);
base.MarkNew();
}
internal static void Fetch(Mapper.Core.User user,
System.Data.IDataReader reader) {
Mapper.Core.User.Fetch(user, reader, 0);
}
internal static void Fetch(Mapper.Core.User user,
System.Data.IDataReader reader, int startIndex) {
user.ID = reader.GetInt32((0 + startIndex));
user.FirstName = reader.GetString((1 + startIndex));
user.LastName = reader.GetString((2 + startIndex));
user.Username = reader.GetString((3 + startIndex));
user.Password = reader.GetString((4 + startIndex));
user.Enabled = reader.GetBoolean((5 + startIndex));
user.Email = reader.GetString((6 + startIndex));
user.Phone = reader.GetString((7 + startIndex));
user.Address1 = reader.GetString((8 + startIndex));
user.Address2 = reader.GetString((9 + startIndex));
}
internal static System.Collections.Generic.List<Mapper.Core.User>
UserListFromReader(System.Data.IDataReader reader) {
return Mapper.Core.User.UserListFromReader(reader, 0);
}
internal static System.Collections.Generic.List<Mapper.Core.User>
UserListFromReader(System.Data.IDataReader reader,
int startIndex) {
System.Collections.Generic.List<Mapper.Core.User> users;
users = new
System.Collections.Generic.List<Mapper.Core.User>();
for (
; (reader.Read() == true);
) {
Mapper.Core.User user;
user = new Mapper.Core.User();
Mapper.Core.User.Fetch(user, reader, startIndex);
user.MarkOld();
users.Add(user);
}
if ((reader.IsClosed != true)) {
reader.Close();
}
return users;
}
public static
System.Collections.Generic.List<Mapper.Core.User> GetUsers() {
System.Data.SqlClient.SqlCommand selectAllCommand;
selectAllCommand =
ObjectMapperUtils.DataUtility.CreateCommand(
"DB_Users_SelectAll");
return Mapper.Core.User.UserListFromReader(
ObjectMapperUtils.DataUtility.ExecuteReader(
selectAllCommand));
}
}
}
最终屏幕显示了智能感知支持以及生成的 XML 注释。
使用代码
要在单独的项目中测试代码,您应该添加对 ObjectMapper
和 ObjectMapperUtils
程序集的引用。web.config 文件应包含本文所述的 <buildProviders>
节点。然后,您只需将 XML 文件以描述的格式添加到 App_Code 文件夹即可。
未来构想
- 类型之间的关系支持(外键映射)。
- 根据 XML 映射文件的更改,支持创建/修改表。
- 对象从数据源选择的自定义成员,以声明方式实现。
- 使用 XML 文件中的代码片段实现的自定义成员。
关注点
构建提供程序是 ASP.NET 2.0 的强大功能,可以极大地提高我们的生产力。我希望我已经向您展示了使用它们可以获得的优势。