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

使用属性和反射持久化业务对象的 Datalayer - 第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (39投票s)

2002年3月21日

5分钟阅读

viewsIcon

321164

downloadIcon

23

通过属性和反射将业务对象持久化。

目录

引言

自从我开始使用 .NET (Beta 1 版本之后) 以来,我一直在开发小型应用程序,这些应用程序将数据存储在 MS Access 或 SQL Server 中。当然,ADO.NET 比 ADO 或 OleDB 简单得多。我所知道的是,我需要一个打开的连接,并且可以从 DataSetDataReader 获取数据库数据。您可以根据自己的需求选择检索数据库数据的方式。

我知道强类型 DataSet 的强大功能以及它能节省的时间,但我还是喜欢自己进行数据库编程的方式。我喜欢有一个类,然后调用一个方法来更新它。因此,如果我想在数据库中添加新行,我只需创建一个新的对象实例,设置其属性,然后调用更新方法。就是这么简单!!

但这种编程方式让我键入了很多代码。业务对象类、数据库更新代码和数据库读取代码。起初,我没有使用存储过程来更新数据库,所以要更新一个表,我会为我的每个业务对象编写一个 SQL 语句。这是一项繁琐的工作,每当我更改数据库或业务设计时都必须重新进行。

我的解决方案

我的解决方案的开端是创建一个简单的类 (DALQueryBuilder,参见上一篇文章的源代码),它能让我键入更少的代码来更新一个对象。我所要做的就是添加列名/值,然后该类就会生成 SQL 语句。完成这些后,我高兴了一周……

当我开始使用 SQL Server 而不是 Access 时,我的心情变了。我不应该使用纯 SQL 语句来更新我的对象,我必须坚持使用存储过程。一开始就遇到了麻烦……我不得不创建大量的 SQL 参数来更新我的对象。又一项枯燥的工作……

我注意到我可以编写一个简单的类来生成这些参数,就像 SQL 语句生成器类一样。虽然这个解决方案可以让我少键入很多代码,但每当我的解决方案发生变化时,我仍然需要审查更新代码。

然后我产生了创建类来描述它们应如何在数据库中持久化的想法。我将使用属性来说明我的对象应持久化到哪个数据库表,以及其他属性来说明哪些属性应映射到表列。从此以后,在解决方案更改后更新我的代码,我只需要更改业务对象类。

为了让您阅读更轻松,我决定将这篇文章分为 3 部分。第一部分将解释用于描述业务类的属性。第二部分将解释我如何收集这些信息,最后一部分我将向您展示完整的解决方案。

尽管我仍然使用 Access,但该解决方案仅在 SQL Server 7.0 中进行了测试。如果您在 Access 中测试过并且不起作用,请告诉我。

第一部分 - 属性

属性是为程序集、类、属性、方法和字段提供描述性信息的一种方式。有些已经是 .NET Framework 的一部分,但您可以创建自己的。

我使用属性来描述类如何存储在数据库中。在一个类中,我将说明哪些属性应该被持久化,以及应该使用哪个存储过程来更新数据库(如果有的话)。为了描述表的列,我在类的属性上使用了属性。列可以是简单的数据字段、唯一键或外键。为了更好地理解属性,我建议阅读 .NET 帮助或 James T. Johnson 的文章

如何创建我自己的属性?

这相当简单。您创建一个派生自 System.Attribute 类的类。按照命名约定,您的类应该有一个 Attribute 后缀。当您创建属性时,您使用一个属性来告知它应该如何使用。它应该用于类吗?属性?允许多个定义吗?

现在是时候看一些代码了。这些是用于描述业务对象的属性

using System;
using System.Data;

namespace DAL
{

    [AttributeUsage(AttributeTargets.Property)]
    public class BaseFieldAttribute : Attribute
    {
        string columnName;

        public BaseFieldAttribute(string columnName)
        {
            this.columnName = columnName;

        }

        public string ColumnName
        {
            get { return columnName;  }
            set { columnName = value; }
        }

    }


    [AttributeUsage(AttributeTargets.Property)]
    public class DataFieldAttribute : BaseFieldAttribute
    {
        DbType dbType = DbType.String;
        int    size   = 0;


        public DataFieldAttribute(string columnName) : base(columnName)
        {

        }

        public DbType Type
        {
            get { return dbType;  }
            set { dbType = value; }
        }

        public int Size
        {
            get { return size;  }
            set { size = value; }
        }
    };

    [AttributeUsage(AttributeTargets.Property)]
    public class KeyFieldAttribute : BaseFieldAttribute
    {
        public KeyFieldAttribute(string columnName) : base(columnName)
        {

        }
    };

    [AttributeUsage(AttributeTargets.Property)]
    public class ForeignKeyFieldAttribute : BaseFieldAttribute
    {
        public ForeignKeyFieldAttribute(string columnName) : base(columnName)
        {

        }

    };

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
    public class DataTableAttribute : Attribute
    {
        string tableName;
        string updateStoredProcedure   = "";

        public DataTableAttribute(string tableName)
        {
            this.tableName = tableName;
        }


        public string TableName
        {
            get { return tableName;  }
            set { tableName = value; }
        }


        public string UpdateStoredProcedure
        {
            get { return updateStoredProcedure;  }
            set { updateStoredProcedure = value; }
        }
    }
}

正如您在每个类顶部注意到的,有一个 AttributeUsage 属性。它只是说明了属性应该如何使用。

我将如何使用那些属性来描述一个类?

假设您有一个存储客户和联系人信息的应用程序。在 OO 设计中,我们从 Person 类开始。联系人是 Person 加上地址和联系方式信息。客户是联系人 + 其购买统计信息。此外,客户有其从属项,这些从属项是 Person。我知道这有点蠢,但我将在后续文章中一直使用它。

这些类的代码如下所示

using System;
using System.Data;
using DAL;



namespace TestApp
{

    public class Person
    {
        string name = "";
        int age = 0;
        int id = 0;


        [KeyField("id")]
        public int Id
        {
            get { return id;  }
            set { id = value; }
        }

        [DataField("name", Size=50)]
        public string Name
        {
            get { return name;  }
            set { name = value; }
        }

        [DataField("age")]
        public int Age
        {
            get { return age;  }
            set { age = value; }
        }

        public override string ToString()
        {
            return string.Format("{0}, {1} years old", Name, Age);
        }
    }


    [DataTable("contact", UpdateStoredProcedure="sp_UpdateContact")]
    public class Contact : Person
    {
        string phone = "";
        string email = "";
        string address = "";
        string address2 = "";
        string city = "";
        string postalCode = "";
        string state = "";
        string country = "";

        [DataField("phone", Size=20)]
        public string Phone
        {
            get { return phone;  }
            set { phone = value; }
        }

        [DataField("email", Size=80)]
        public string Email
        {
            get { return email;  }
            set { email = value; }
        }

        [DataField("address", Size=80)]
        public string Address
        {
            get { return address;  }
            set { address = value; }
        }


        [DataField("address2", Size=80)]
        public string Address2
        {
            get { return address2;  }
            set { address2 = value; }
        }


        [DataField("city", Size=50)]
        public string City
        {
            get { return city;  }
            set { city = value; }
        }


        [DataField("postalCode", Size=20)]
        public string PostalCode
        {
            get { return postalCode;  }
            set { postalCode = value; }
        }


        [DataField("state", Size=4)]
        public string State
        {
            get { return state;  }
            set { state = value; }
        }


        [DataField("country", Size=50)]
        public string Country
        {
            get { return country;  }
            set { country = value; }
        }


        public override string ToString()
        {
            return string.Format("<Contact>{0} - {1} from {2}", Id, Name, Country);
        }
    }


    public enum CustomerRelationship { Family, Friend, Other };

    [DataTable("customerDependent", UpdateStoredProcedure="sp_UpdateCustomerDependent")]
    public class CustomerDependent : Person
    {
        int customerId = 0;
        CustomerRelationship relationship = CustomerRelationship.Family;

        protected CustomerDependent()
        {

        }

        public CustomerDependent(int customerId)
        {
            this.customerId = customerId;
        }

        [ForeignKeyFieldAttribute("customerId")]
        public int CustomerId
        {
            get { return customerId;  }
            set { customerId = value; }
        }

        [DataFieldAttribute("relationship")]
        public CustomerRelationship Relationship
        {
            get { return relationship;  }
            set { relationship = value; }
        }
    }




    public enum CustomerStatus { Active, Inactive };

    [DataTable("customer", UpdateStoredProcedure="sp_UpdateCustomer")]
    public class BaseCustomer : Contact
    {
        CustomerStatus status = CustomerStatus.Active;
        Decimal totalPurchased = 0M;
        int numberOfPurchases  = 0;
        DateTime dateRegistered = DateTime.Now;

        [DataField("status")]
        public CustomerStatus Status
        {
            get { return status;  }
            set { status = value; }
        }


        [DataField("totalPurchased")]
        public Decimal TotalPurchased
        {
            get { return totalPurchased;  }
            set { totalPurchased = value; }
        }

        [DataField("numberOfPurchases")]
        public int NumberOfPurchases
        {
            get { return numberOfPurchases;  }
            set { numberOfPurchases = value; }
        }

        [DataField("dateRegistered")]
        public DateTime DateRegistered
        {
            get { return dateRegistered;  }
            set { dateRegistered = value; }
        }


        public override string ToString()
        {
            return string.Format("<Customer>{0} - {1} from {2}, registered in {3}."+
                                 " #{4} purchases spending a total of $ {5}",
                          Id,
                          Name,
                          Country,
                          DateRegistered,
                          NumberOfPurchases,
                          TotalPurchased);
        }

    }

    public class Customer : BaseCustomer
    {

        ArrayList dependents = null;

        public ArrayList Dependents
        {
            get
            {
                if (dependents == null)
                {
                    DAL dal = new DAL();
                    dependents = dal.GetCustomerDependents(this);
                }

                return dependents;
            }
        }

        public CustomerDependent NewDependent()
        {
            return new CustomerDependent(Id);
        }

        public Decimal PurchaseMedia
        {
            get { return TotalPurchased / NumberOfPurchases; }
        }


    }

}

Person 类是我们所有类的基类。它是唯一一个没有 DataTable 属性的类,因为它不会被持久化。只有 Contacts、CustomerDependents 和 Customers 会在我们的数据库中。但是 Person 类已经定义了一些每个派生类都必须拥有的列。例如 Id 属性。它是一个 int 键字段,在这种情况下是一个自动编号列。Name 属性是一个长度为 50 的字符串,依此类推。唯一不同的属性是 CustomerDependent::CustomerId,它是一个外键。

要在 SQL Server 中创建这些类,只需在 Query Analyzer 工具中运行这些 SQL 脚本

if exists (select * from sysobjects where id = object_id(N'[dbo].[contact]') 
           and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[contact]
GO

if exists (select * from sysobjects where id = object_id(N'[dbo].[customer]') 
           and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[customer]
GO

if exists (select * from sysobjects where id = object_id(N'[dbo].[customerDependent]') 
           and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[customerDependent]
GO

CREATE TABLE [dbo].[contact] (
    [id] [int] IDENTITY (1, 1) NOT NULL ,
    [name] [varchar] (50) NOT NULL ,
    [age] [int] NOT NULL ,
    [address] [varchar] (80) NOT NULL ,
    [postalCode] [varchar] (20) NOT NULL ,
    [phone] [varchar] (20) NOT NULL ,
    [email] [varchar] (80) NOT NULL ,
    [address2] [varchar] (80) NOT NULL ,
    [city] [varchar] (50) NOT NULL ,
    [state] [varchar] (4) NOT NULL ,
    [country] [varchar] (50) NOT NULL
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[customer] (
    [id] [int] IDENTITY (1, 1) NOT NULL ,
    [name] [varchar] (50) NOT NULL ,
    [age] [int] NOT NULL ,
    [address] [varchar] (80) NOT NULL ,
    [postalCode] [varchar] (20) NOT NULL ,
    [phone] [varchar] (50) NOT NULL ,
    [email] [varchar] (80) NOT NULL ,
    [address2] [varchar] (80) NOT NULL ,
    [city] [varchar] (50) NOT NULL ,
    [state] [varchar] (4) NOT NULL ,
    [country] [varchar] (50) NOT NULL ,
    [totalPurchased] [money] NOT NULL ,
    [numberOfPurchases] [int] NOT NULL ,
    [dateRegistered] [datetime] NOT NULL ,
    [status] [smallint] NOT NULL
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[customerDependent] (
    [id] [int] IDENTITY (1, 1) NOT NULL ,
    [name] [varchar] (50) NOT NULL ,
    [customerId] [int] NOT NULL ,
    [relationship] [int] NOT NULL ,
    [age] [int] NOT NULL
) ON [PRIMARY]
GO

敬请期待

在下一篇文章中,我将向您展示反射将如何帮助我们收集更新数据库所需的信息。我还会创建一个简单的工具应用程序,该应用程序将根据程序集中定义的类生成创建表的 SQL 脚本。
© . All rights reserved.