《掌握 ASP.NET 之路:自定义实体类简介》(中文)原文见:
http://www.microsoft.com/china/msdn/library/webservices/asp.net/CustEntCls.mspx
【另】:更多参考资料和链接也见原文,强烈推荐
DataSet的问题:
1、缺少抽象,开发人员必须了解其基础架构;
2、弱类型,返回的是System.Object,必须进行类型转换Convert.ToInt32等之后才能使用,降低了效率,增加了出错的可能性;
3、不是面向对象的,无法充分利用OO的技术。
使用DataSet,它的缺点将在复杂系统中成倍的扩大。
自定义实体类的挑战:
要求更多的代码。我们不是简单地获取数据并自动填充 DataSet,而是获取数据并手动将数据映射到自定义实体(必须先创建好)。由于这个工作是重复的,我们可以使用代码生成工具或者ORM映射。
自定义实体类的代码示例:
public class User {
#region 'Fields and Properties'
private int userId;
private string userName;
private string password;
public int UserId {
get { return userId; }
set { userId = value; }
}
public string UserName {
get { return userName; }
set { userName = value; }
}
public string Password {
get { return password; }
set { password = value; }
}
#endregion
#region 'Constructors'
public User() {}
public User(int id, string name, string password) {
this.UserId = id;
this.UserName = name;
this.Password = password;
}
#endregion
}
自定义实体类的优点:
1、可以让我们利用继承和封装等OO技术;
2、可以添加自定义行为;
3、属于强类型,可以获得代码自动完成功能(IntelliSense);
4、属于强类型,不太需要容易出错的强制类型转换。
对象和关系的映射:
public User GetUser(int userId) {
SqlConnection connection = new SqlConnection(CONNECTION_STRING);
SqlCommand command = new SqlCommand('GetUserById', connection);
command.Parameters.Add('@UserId', SqlDbType.Int).Value = userId;
SqlDataReader dr = null;
try{
connection.Open();
dr = command.ExecuteReader(CommandBehavior.SingleRow);
if (dr.Read()){
User user = new User();
user.UserId = Convert.ToInt32(dr['UserId']);
user.UserName = Convert.ToString(dr['UserName']);
user.Password = Convert.ToString(dr['Password']);
return user;
}
return null;
}finally{
if (dr != null && !dr.IsClosed){
dr.Close();
}
connection.Dispose();
command.Dispose();
}
}
以上代码还保留着DataSet弱类型需要转换这样的缺点,所以应该进一步改进,即将映射的转换过程提取到一个函数中,使这段代码能够重复使用——
public User PopulateUser(IDataRecord dr) {
User user = new User();
user.UserId = Convert.ToInt32(dr['UserId']);
//检查 NULL 的示例
if (dr['UserName'] != DBNull.Value){
user.UserName = Convert.ToString(dr['UserName']);
}
user.Password = Convert.ToString(dr['Password']);
return user;
}
需要说明1:我们不对映射函数使用 SqlDataReader,而是使用 IDataRecord。这是所有 DataReader 实现的接口。使用 IDataRecord 使我们的映射过程独立于供应商。也就是说,我们可以使用上一个函数从 Access 数据库中映射 User,即使它使用 OleDbDataReader 也可以。如果您将这个特定的方法与 Provider Model Design Pattern(链接 1、链接 2)结合使用,您的代码就可以轻松地用于不同的数据库提供程序。
需要说明2:数据访问是否该和自定义实体类放在一起?为了扩展和维护的方便,通常应该将数据访问层和业务层明确分离。
自定义集合:
简单的解决方案是使用Arraylist,但它的问题和DataSet相关,即也是弱类型,无法添加自定义行为。幸亏 Microsoft .NET Framework 提供了一个专门为了此目的而继承的类:CollectionBase。CollectionBase 的工作原理是,将所有类型的对象都存储在专有 Arraylist 中,但是通过只接受特定类型(例如 User 对象)的方法来提供对这些专有集合的访问。代码示例——
public class UserCollection :CollectionBase {
public User this[int index] {
get {return (User)List[index];}
set {List[index] = value;}
}
public int Add(User value) {
return (List.Add(value));
}
public int IndexOf(User value) {
return (List.IndexOf(value));
}
public void Insert(int index, User value) {
List.Insert(index, value);
}
public void Remove(User value) {
List.Remove(value);
}
public bool Contains(User value) {
return (List.Contains(value));
}
}
映射到自定义集合:
将我们的关系数据映射到自定义集合的过程与我们对自定义实体执行的过程非常相似。我们不再创建一个实体并将其返回,而是将该实体添加到集合中并循环到下一个:
public UserCollection GetAllUsers() {
SqlConnection connection = new SqlConnection(CONNECTION_STRING);
SqlCommand command =new SqlCommand('GetAllUsers', connection);
SqlDataReader dr = null;
try{
connection.Open();
dr = command.ExecuteReader(CommandBehavior.SingleResult);
UserCollection users = new UserCollection();
while (dr.Read()){
users.Add(PopulateUser(dr));
}
return users;
}finally{
if (dr != null && !dr.IsClosed){
dr.Close();
}
connection.Dispose();
command.Dispose();
}
}
添加自定义行为:
示例一
public User FindUserById(int userId) {
foreach (User user in List) {
if (user.UserId == userId){
return user;
}
}
return null;
}
示例二
public UserCollection FindMatchingUsers(string search) {
if (search == null){
throw new ArgumentNullException('search cannot be null');
}
UserCollection matchingUsers = new UserCollection();
foreach (User user in List) {
string userName = user.UserName;
if (userName != null && userName.StartsWith(search)){
matchingUsers.Add(user);
}
}
return matchingUsers;
}
绑定自定义集合:
自定义集合绑定到web控件和DataSet一样很简单(这是因为 CollectionBase 实现了用于绑定的 Ilist)。自定义集合可以作为任何控件的 DataSource,而 DataBinder.Eval 只能像您使用 DataSet 那样使用——
UserCollection users = DAL.GetAllUsers();
repeater.DataSource = users;
repeater.DataBind();
<!-- HTML -->
<asp:Repeater onItemDataBound='r_IDB' ID='repeater' Runat='server'>
<ItemTemplate>
<asp:Label ID='userName' Runat='server'>
<%# DataBinder.Eval(Container.DataItem, 'UserName') %><br />
</asp:Label>
</ItemTemplate>
</asp:Repeater>
高级内容:
1、并发
2、性能
3、排序与筛选
4、代码生成
5、ORM映射
6、泛型
7、可以为空的类型
8、迭代程序
【强烈推荐参考:《DataSet与自定义实体类以及集合的比较》】
DataSets vs. Collections by Dino Esposito对比分析了DataSet, Typed DataSet和Custom Entities and Collections(定制业务实体和集合)作为multi-tier之间的数据传递,并阐述各自的适用情况(When to Use Which)。
文章建议:在小型项目中,考虑到预算、截至时间等因素,可以使用DataSet快速开发。如果项目时间长、投入多,业务逻辑复杂,则使用自定义实体类以及集合,对于维护、扩展、灵活、代码的优雅、单元测试等,都能带来益处。前期的投入是值得的。
文章建议阅读:For more information on these topics, take a look at the following blogs: Scott Hanselmans at Returning DataSets from WebServices is the Spawn of Satan and Represents All That Is Truly Evil in the World, Jelle Druyts at DataSets Are Not Evil, Andrew Conrads at Nix the DataSet??????, and ObjectSharp at DataSet FAQ.
各项比较:
DataSetTyped DataSetCustom EntitiesBuilt-in support for concurrency
Yes
Yes
To be added
Data Relationship
Yes
Yes
No
Serialization
Inefficient in .NET Framework 1.x
Same as DataSet, but can be improved
To be added
NULL values
No
Yes
To be added
Schema abstraction
Yes
Yes
Yes
Strong typing
No
Yes
Yes
Support for hierarchical data
Yes, but through a relational API
Yes, but through a relational API
Yes
Free-form data
No
No
Yes
Custom behavior
No
To be added
Yes
Ease of development
Yes
Yes
No, but can be improved through custom wizards and code generation
.NET data binding
Yes
Yes
To be added; requires the implementation of several additional interfaces
Interfacing with Web services
Costly, unless knowledge of the object is assumed on the client
Schema information is more precise and can be handled by the client
Yes
XML integration
Yes
Yes
To be added
Expression language
Yes
Yes
To be added
Data aggregation
Yes
Yes
To be added
如果采用Custom Entities,建议了解如下常用的Enterprise Design Patterns:Design Patterns for Building a DALPattern
Description
Active Record
The entity object stores its own data as well as any available methods. Clients get an instance of the object and work with it as needed.
Data Mapper
The entity object contains only its own data. A neat separation exists between data and behavior. Behavior is delegated to an array of separate classes specific to the object.
Table Data Gateway
Variation of Data Mapper that delegates the implementation of required behaviors to external, gateway classes not specific to the object. The gateway can take scalar and instance data and serve multiple business objects.