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

试验自定义生成提供程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (17投票s)

2006 年 5 月 1 日

CPOL

4分钟阅读

viewsIcon

54536

downloadIcon

323

一个简单的 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 中生成了一个具有完整智能感知支持的类型

Intellisense support

使用一些字符串实用方法,我添加了对 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>

Intellisense support

扩展我的自定义语言

看到我的自定义构建提供程序允许我使用 .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 注释。

Intellisense support

使用代码

要在单独的项目中测试代码,您应该添加对 ObjectMapperObjectMapperUtils 程序集的引用。web.config 文件应包含本文所述的 <buildProviders> 节点。然后,您只需将 XML 文件以描述的格式添加到 App_Code 文件夹即可。

未来构想

  • 类型之间的关系支持(外键映射)。
  • 根据 XML 映射文件的更改,支持创建/修改表。
  • 对象从数据源选择的自定义成员,以声明方式实现。
  • 使用 XML 文件中的代码片段实现的自定义成员。

关注点

构建提供程序是 ASP.NET 2.0 的强大功能,可以极大地提高我们的生产力。我希望我已经向您展示了使用它们可以获得的优势。

© . All rights reserved.