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






4.60/5 (39投票s)
2002年3月21日
5分钟阅读

321164

23
通过属性和反射将业务对象持久化。
目录
引言
自从我开始使用 .NET (Beta 1 版本之后) 以来,我一直在开发小型应用程序,这些应用程序将数据存储在 MS Access 或 SQL Server 中。当然,ADO.NET 比 ADO 或 OleDB 简单得多。我所知道的是,我需要一个打开的连接,并且可以从 DataSet
或 DataReader
获取数据库数据。您可以根据自己的需求选择检索数据库数据的方式。
我知道强类型 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