QASQL Server 2000Windows VistaDBAWindows 2003.NET 3.0Visual Studio 2005Windows 2000设计/图形架构师Windows XP.NET 2.0SQL Server 2005C# 2.0中级开发Visual StudioSQL ServerWindows.NETC#
业务对象简介






4.28/5 (18投票s)
本文将介绍RockFord Lhotka撰写的《Expert C# 2005 Business Object》一书,该书面向业务应用程序开发人员。

引言
本文将介绍RockFord Lhotka撰写的《Expert C# 2005 Business Object》一书,该书面向业务应用程序开发人员。书中主要内容是关于业务对象(BO),以及如何在各种实际场景中实现它们。本书基于CSLA .NET框架,这是一个生产级的框架,涵盖了许多软件开发周期,包括系统架构设计(物理设计和逻辑设计)、编码和单元测试。书中揭示了框架内部每个类的工作原理,因此对新入门者来说学习曲线相当陡峭。在本系列文章中,我将从软件开发人员的角度介绍CSLA.NET框架。CSLA .NET框架是开源的,其许可证相当宽松。它允许您修改它来创建其他商业或业务软件;您只是不能将框架本身进行修改并作为产品出售。
什么是业务对象?
BO通常代表一组相关的业务功能,并且是一个根据实现了IBussinessObject
接口(如下所示)的任何可序列化类实例化的对象。
public interface IBusinessObject
{
}
正如您所见,任何类都可以成为BO类。但是,为了充分利用框架,您可能希望从更有用的类派生您的BO类,例如BusinessBase<T>、BusinessListBase<T,C>、ReadOnlyBase<T>等,具体取决于您要创建的BO类型。BusinessBase<T>是您将使用的最基本且最有用的BO类。一旦声明了BO类,您就需要声明对象的属性,设置业务规则(用于在将BO保存到数据存储之前验证BO是否有效),创建工厂方法来创建新的BO或检索现有的BO,创建CRUD操作来创建、检索、更新和删除自身,以及任何可能帮助BO消费者使用的附加辅助函数。仅此而已。然后框架将负责其余部分。我将在接下来的示例中更详细地展示。框架的优势
每个人都会问,为什么我需要CSLA.NET框架,它为已经足够复杂的应用程序增加了额外的复杂性。答案很简单。它为您从每个业务应用程序都会遇到的繁琐业务需求中解脱出来。它们是- 集中验证
- Authorization
- 事务
- 高可伸缩性和高性能
- N级撤销
- 单元测试
- 数据绑定支持
集中验证
它允许集中验证业务对象。每个业务对象都负责验证其自身的状态以及它包含的子BO(如果有)。例如,在ASP.NET编程中,我们可以通过JavaScript在浏览器中放置任何用户输入的验证,例如地址行最多只能包含25个字符,并希望用户不要禁用JavaScript执行。或者,我们可以在ASPX页面的代码隐藏文件中放置验证以拒绝任何无效输入。或者,我们可以通过数据库中的存储过程来验证输入。或者XSLT文件。或者XSD文件。我曾在一个项目中看到验证代码散布在上述所有地方。当然,我们可以将所有这些结合起来,但这会导致代码重复并产生维护问题。一个更清晰的解决方案是使用CSLA .NET框架中的BO。BO类负责验证,并维护BO将根据其进行验证的规则列表。在我们的ASP.NET示例中,代码隐藏文件将读取业务规则列表,并通过验证控件设置JavaScript验证例程。如果JavaScript验证执行失败,也没关系,因为验证控件将进行服务器端检查,最终在保存到数据库之前将验证BO。CSLA.NET框架中的数据库仅作为永久数据存储库,不包含任何业务逻辑。Authorization
您可以向BO添加基于字段/属性的授权。默认情况下,它是广泛开放的,这意味着BO的消费者将对BO的所有属性具有读/写访问权限。然而,在某些情况下,并非所有字段都对BO的所有消费者可用。可以通过AuthorizationRules属性授予访问权限。一旦为属性设置了AuthorizationRules,除授权的消费者外,该属性将不再对BO的所有消费者可用。授权也可以基于每个对象实例。事务
一个BO可以包含其他子BO。在在线购物车示例中,购物车是一个BO,其中包含许多商店商品,这些商品是子BO。在CSLA.NET的术语中,我们将购物车BO称为可编辑根对象,每个商店商品都是可编辑子对象。通常,可编辑子对象不能独立存在;它必须是可编辑根对象的一部分。由于我们知道BO作为与它们的消费者的一致性接口,因此可编辑根对象显然是支持事务以确保在保存BO及其子BO时保持一致状态的一个好选择。此设计的好处是,BO的消费者不知道事务的存在。它所知道的是,操作要么成功,要么完全失败,在这种情况下,什么也不会保存,并且会抛出异常。CSLA.NET框架不规定事务代码应如何实现或它可以与什么类型的数据源通信。这取决于程序员来实现它。高可伸缩性和高性能
通过利用CSLA.NET框架,您的应用程序可以部署在客户端-服务器环境中,在这种环境中,您的应用程序直接与数据库或其他数据源通信。或者,无需更改一行代码,您就可以将应用程序部署到3层环境中。在3层环境中,您的应用程序与应用程序服务器通信,该服务器通过连接池机制与数据库或其他数据源通信来创建您的BO。这大大减少了对数据库服务器的争用。您甚至可以将您知道您的BO可能在下一次请求中使用的数据进行缓存,或在BO之间共享,以进一步提高应用程序的可伸缩性。在3层部署中,尽管对于任何单个请求,它可能需要经过更多的步骤并花费更多时间,但可伸缩性的提高带来了更高的吞吐量和更好的整体系统性能。N级撤销
可编辑根对象支持无限级别的撤销,而无需从数据库重新加载数据。您可以获取BO的快照并进行任何更改,然后将其恢复到原始状态。此功能主要方便UI开发人员编辑复杂的BO。在我们的购物车示例中,在进入商店商品对象编辑页面之前,可以通过调用CopyState()函数获取订单BO的快照。用户可以修改商店商品BO,然后他们可以选择保存更改(在这种情况下将调用AcceptChanges())或取消更改(在这种情况下将调用UndoChanges())。这是1级撤销。为任何可编辑根对象添加N级撤销并非易事。幸运的是,该框架开箱即用地支持它。单元测试
单元测试被认为是软件开发的最佳实践。然而,它常常被大多数实现所忽略。如果业务逻辑散布在代码库的各个地方,例如XSLT、Visual Basic、ASP、SQL存储过程、ASP.NET和WinForm应用程序,那么编写单元测试以确保应用程序符合业务需求几乎是不可能的。即使在这种情况下您能够编写单元测试,它通常也无法证明在编写任何代码之前所付出的努力是值得的。然而,如果您基于BO的概念构建应用程序,情况就会完全不同。由于BO具有明确定义的接口并封装了业务逻辑及其数据的所有信息,因此编写单元测试以确保BO满足业务需求是完全可能的。其中一些单元测试代码甚至可以复制粘贴到您的应用程序中,以使单元测试成为编码实践的一部分更具吸引力。数据绑定支持
可编辑根对象内置了对Winform应用程序数据绑定的支持。正如我之前提到的,由于BO为每个属性都有验证规则,当某个字段不通过验证时,将会有视觉指示。不需要代码。您只需创建一个ErrorProvider控件,并将其DataSource属性指向BindingSource对象即可。如下图所示。对于ASP.NET应用程序,数据绑定仍然可以正常工作,但需要一些调整。有关更多信息,请参阅Rockford Lhotka的博客。http://www.lhotka.net/weblog/ASPNET20DataBindingFrustration.aspx我们的第一个BO
我们先从用户BO开始(完整的源代码请参见zip文件)。步骤1 声明类
初次看到上面的声明可能有些令人困惑。基类BusinessBase<T>的声明是为了允许基类的一些方法调用返回类型为T的对象。例如,如果您想克隆对象并检索Name属性,您可以编写代码,例如str = user.Clone().Name,而无需在代码中进行类型转换。Clone是从基类继承的,Name是派生类的属性。 public class User : BusinessBase<User>
{
}
步骤2 定义属性
这很简单。您需要做的就是调用PropertyHasChanged()方法,告知基类该值已被更新。它将仅触发对属性的验证。 protected int _userID = 0;
protected string _name = string.Empty;
public int UserID
{
get
{
return _userID;
}
}
public string Name
{
get
{
return _name;
}
set
{
if (value == null) value = string.Empty;
if (!_name.Equals(value))
{
_name = value;
PropertyHasChanged("Name");
}
}
}
//... more properties declaration
步骤3 创建业务规则
您可以使用下面的一些预建规则。或者您可以创建自己的规则。添加规则后,您始终可以使用IsValid属性来确定BO是否处于有效状态。CSLA.NET框架内部也使用此标志来确定BO是否可以保存。在我们的演示程序中,我们使用此属性来启用/禁用“保存”按钮。 protected override void AddBusinessRules()
{
ValidationRules.AddRule(CommonRules.StringRequired, "Name");
ValidationRules.AddRule(CommonRules.StringRequired, "FullName");
ValidationRules.AddRule(CommonRules.StringMaxLength, new CommonRules.MaxLengthRuleArgs("FullName", 15));
}
步骤4 创建工厂方法
工厂方法通常会调用DataPortal.XYZ方法来检索对象。CSLA.NET框架明确反对使用类构造函数。所有BO都通过工厂方法创建。DataPortal.XYZ方法提供了创建对象的独立于位置的方式。在后台,框架可能会在远程服务器(通常是应用程序服务器)上创建对象,将其序列化,传输,然后在本地机器上反序列化。我们知道,当您通过MS DotNet库反序列化对象时,它将返回一个对象,并且没有有效的方法可以反序列化到现有对象。这就是为什么不使用类构造函数的原因。 public static User GetUser(int userID )
{
return DataPortal.Fetch<User>(new Criteria(userID));
}
public static User NewUser()
{
return DataPortal.Create<User>();
}
步骤5 创建DataPortal_XYZ方法
根据您要创建的BO类型,您可以选择实现一些DataPortal_XYZ方法。总共有5种,分别是DataPortal_Create、DataPortal_Fetch、DataPortal_Update、DataPortal_Insert和DataPortal_Delete。顾名思义,DataPart_XYZ方法提供了一种从数据源创建、获取/检索、更新、插入和删除当前对象的方式。所有DataPortal_XYZ方法仅由CSLA.NET框架调用,具体取决于您调用的基类方法以及BO的状态。场景1。您正在创建新的BO,将调用DataPortal_Create。场景2。您正在创建新的BO并调用保存方法,将调用DataPortal_Insert来保存当前BO。场景3。您正在从数据源检索BO,将调用DataPortal_Fetch。检索BO后,您更改属性值并保存,将调用DataPortal_Update。场景4。如果您正在从数据源检索BO,调用删除方法并保存BO,将调用DataPortal_Delete。这里的很多代码都是样板代码。所有DataPortal_XYZ方法的实现,请参见附加的zip文件。我只想在此展示DataPortal_Fetch方法。有一点需要注意的是名为criteria的函数参数,它通常存储BO的主键。在工厂方法GetUser中,DataPortal.Fetch<User>(new Criteria(userID))的参数与DataPortal_Fetch方法的参数是同一个对象(按值传递)。private void DataPortal_Fetch(Criteria criteria)
{
using (SqlConnection cn = new SqlConnection(Database.Connection))
{
cn.Open();
ExecuteFetch(cn, criteria);
}//using
}
protected void ExecuteFetch(SqlConnection cn, Criteria criteria)
{
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandType = CommandType.Text;
cm.CommandText = "Select UserID, Name, FullName, Description from UserTable where @UserID=UserID;";
cm.Parameters.AddWithValue("@UserID", criteria.UserID);
using (SafeDataReader dr = new SafeDataReader(cm.ExecuteReader()))
{
FetchObject(dr);
ValidationRules.CheckRules();
}
}//using
}
protected void FetchObject(SafeDataReader dr)
{
dr.Read();
_userID = dr.GetInt32("UserID");
_name = dr.GetString("Name").TrimEnd();
_fullName = dr.GetString("FullName").TrimEnd();
_description = dr.GetString("Description").TrimEnd();
}
单元测试
由于User BO具有明确定义的接口并封装了所有数据,因此编写单元测试变得非常简单。这是创建新用户的测试。 [TestMethod()]
public void UserNewTest()
{
User u;
u = UserGroupLibrary.User.NewUser();
}
测试将一个新的用户BO插入到数据源中。 [TestMethod()]
public void UserInsertTest()
{
User u;
u = GetValidNewUserObject();
u.Save();
}
单元测试业务规则也同样简单。 [TestMethod()]
public void UserBussinessRuleTest()
{
User u;
// initial validation, as a starting point
u = GetValidNewUserObject();
Assert.IsTrue(u.IsValid, "Valid user BO failed to pass test!");
// Name field is required
u = GetValidNewUserObject();
u.Name = null;
Assert.IsFalse(u.IsValid, "Business rule (Name field is required)for User BO failed to pass test!");
// FullName field is requried
u = GetValidNewUserObject();
u.FullName = null;
Assert.IsFalse(u.IsValid, "Business rule (FullName field is required)for User BO failed to pass test!");
// FullName field can not exceed 15 chars, test both positive and negative tests
u = GetValidNewUserObject();
u.FullName = string.Empty.PadLeft(15, 'x');
Assert.IsTrue(u.IsValid, "Business rule (FullName field can not exceed 15 chars)for User BO failed to pass test!");
u.FullName = string.Empty.PadLeft(16, 'x');
Assert.IsFalse(u.IsValid, "Business rule (FullName field can not exceed 15 chars)for User BO failed to pass test!");
}
与WinForm应用程序的数据绑定
一旦用户BO创建并经过单元测试,我们就可以在WinForm应用程序中使用它。以下步骤演示了如何创建一个用户控件来封装用户BO编辑屏幕。步骤1 创建一个空的自定义控件
步骤2 创建一个BindSource对象并指向我们的User BO

步骤3 创建显示BO属性的控件。下图显示了将文本框控件绑定到数据BindingSource。

步骤4 (可选)创建一个ErrorProvider对象,并将其DataSource属性设置为步骤1中创建的BindSource对象。它提供了任何验证错误的视觉指示。

步骤5 在运行时分配BO。这在自定义控件的构造函数中完成,在该控件显示任何内容之前。
UserBindingSource.DataSource = user;
步骤6 在主WinForm类中挂钩它。只需一行代码即可使用自定义控件。是不是没费什么功夫?
new UserEdit(User.GetUser(Int16.Parse(textBox1.Text)), this);我们刚刚创建的WinForm应用程序具有CSLA.NET应用程序的所有特征。它具有高度的可伸缩性,组织良好(这转化为低维护成本)并且经过单元测试。
结论
在本文中,我演示了如何使用CSLA.NET框架创建简单的程序。我希望我能有时间写一篇续集,介绍CSLA.NET中的其他有用类以及用于创建即用型BO的代码生成技术。事实上,此项目中的许多代码都从名为CSLAcontrib的代码生成程序中提取出来,以便新用户能够理解。希望您到目前为止还喜欢。我还要借此机会感谢Rogerio Caceres在撰写本文中的支持。