自定义 DataBindable BusinessObjects 和类型化数据集






4.80/5 (32投票s)
2007年1月4日
16分钟阅读

155072

2048
一篇关于自定义 Businessobjects 与 DataSet 结合检索数据的文章。
引言
一直以来,关于开发者应该使用哪种技术将数据从数据层(DataLayer)通过业务层(BusinessLayer)传递到表示层(Presentation Layer)的问题,一直存在着大量的讨论。通常,有几种主要的“思想”在流传:一种是那些坚持“面向对象范式(one and only object Paradigm)”的人(他们使用 O/R 映射工具将数据库字段映射到自定义对象的属性),另一种则是使用(类型化的)数据集(DataSet)作为中间层,在表示层中呈现数据。
几年前,在我成为一名 .NET 架构师一段时间后,我不得不做出这个艰难的决定。当时,在 .NET 框架中绑定自定义业务对象不像现在这样简单,所以我决定创建业务对象(BusinessObjects),这些对象包含一个“类型化数据集”作为数据容器,并实现了一些基础的“业务类(Business Classes)”,以提供一种通用的方式来验证数据容器中“行”中的数据。
对于所有人来说,有一点是肯定的:在现代开发中,我们应该在开发中实现分离的层/层级(layers/tiers)(对于一些人来说:层是代码的逻辑分离,层级是代码的物理分离,这意味着一个逻辑分层的应用程序将在单个机器上运行,一些逻辑(如远程处理或 WebService)增强功能应该被添加,以使各层运行在不同的层级(机器)上)。
好了,我决定选择数据集(DataSet)的“阵营”,尽管有些人认为(类型化)数据集是懒惰且愚蠢的容器,会产生大量开销,但我仍然(并且一直)对这种在我的业务层中的“愚蠢”实现感到满意!无需进行 O/R 映射,数据库中的数据以透明的方式传递到表示层,关系数据也变得轻而易举!好吧……如果你更改“数据库”,你必须进行一些“管道”操作才能使程序重新编译(正如纯自定义对象那样,你不会收到错误,但如果你忘记将新属性添加到你的业务类中(对于那些抛弃代码生成器的人来说),你将不会注意到有新的属性数据……
“数据中心”(DataCentric)(数据集)和“对象中心”(Object Centric)(自定义对象)这两个世界可能永远无法调和,但随着 .NET 2.0 中绑定自定义业务对象和自动数据访问(仅限 SQL Server!)代码生成的日趋成熟,我将在下一篇文章中重点关注如何调和这两者。
我将介绍一个简单的逻辑分层框架,该框架实现了一个“纯业务对象”层,用于数据绑定到“表示层”,并使用 .NET 2.0 中新推出的数据访问功能(是的!它基于(类型化)数据集!)将数据渲染到我们的自定义业务对象中。为了简单起见,我们将只关注非常简单的业务对象(因此省略缓存、业务规则等)……
我之所以想发表这篇文章,是因为我看到过一些文章或部分代码项目解释了 O/R 映射和数据绑定的原理,但在网上很难找到一个真正文档齐全、内容丰富的示例。因此,我不仅要实现“获取”(Get)部分(这是容易的部分!),还要解释“保存”(Save)部分(这是困难的部分!),而且不仅针对简单的数据对象,还要针对更贴近现实世界的“父子”数据。好吧……如果你正在寻找这样一个内容丰富的示例,请开始阅读这篇文章,希望你会喜欢其中的内容!
本文将以“循序渐进”的教程形式呈现,所以你可以从头开始创建项目,或者直接查看演示代码……
使用代码
第一步:创建数据访问层
好的!让我们开始构建数据访问层。首先,我们将创建一个空的 C# .NET“解决方案”,称之为“DCAF”(DataCentricApplicationFrameWork)。然后向解决方案中添加一个 C#“库”类型的项目,称之为“DCAF.DataLayer”。到目前为止,你应该看到以下视图(参见图 1)。

图 1. 初始解决方案
为了演示的目的,我们将使用 Northwind 数据库,并创建一个窗体来显示所有客户及其相关订单。因此,我们的“DataAccessLayer”将包含一个名为 CustomerOrderTS 的类型化数据集来实现此目的。所以在解决方案资源管理器中,选择当前 DataLayer 项目的“添加” -> “新建项”,然后从显示的模板列表中选择“数据集”,并将其命名为 CustomerOrderTDS.xsd。然后选择解决方案资源管理器中的类型化数据集,并单击左面板中的服务器资源管理器链接。现在你应该会看到数据连接面板出现在左侧(参见图 2)。

图 2. 服务器资源管理器
现在我们将添加我们的数据库数据源,即 NortWhind 数据库中的客户和订单表。为此,首先“右键单击”数据连接项,然后从下拉菜单中选择“添加连接”。接下来选择你的 SQL Server 实例,从数据库下拉菜单中选择 NorthWind 数据库,然后单击“确定”按钮。(参见图 3)。

图 3. 添加连接
接下来,你应该在左侧面板中看到 NortWind 数据库。现在展开表(Tables)组件的树视图,选择 Customers 和 Orders 表(参见图 4),并将它们拖到 CustomerOrdersTDS 的中间面板空白容器中。表会自动添加到表面。

图 4. 创建数据集(类型化)
深入探讨类型化数据集创建的代码超出了本文的范围,但请注意,NorthWind 数据库中已知的表和关系已添加,并且每个表都有自己的 TableAdapter。每个 TableAdapter 都有一个默认创建的加载所有表数据的访问方法。我们将在文档的后续部分中遇到这些 TableAdapter 的用法,届时我们将创建数据服务类。
现在我们已经创建了数据容器,我们必须创建数据服务类。这些类将用作数据访问层和业务层之间的中间件。实际上,我们的数据服务类将从数据库服务器请求数据,将其放入类型化数据集(在我们的演示中,就是我们刚才创建的那个),然后将数据集传递给我们的自定义业务对象。
在实现所有这些服务之前,我们应该在创建的类型化数据集中添加一个方法,该方法将一次性获取我们的客户和订单数据。我们将将此代码作为方法添加到类型化数据集的局部类定义(这是 2.0 中的新功能)中。为此,请在解决方案资源管理器中选择 CustomerOrdersTDS,然后选择“代码”图标。这应该会产生如图 5 所示的代码。

图 5. 局部类定义
现在我们将添加获取批量查询的客户和订单所需的代码。
using DCAF.DataLayer.CustomerOrderTDSTableAdapters;
namespace DCAF.DataLayer {
partial class CustomerOrderTDS
{
public static CustomerOrderTDS GetCustomerOrders()
{
CustomersTableAdapter custAdapter = new CustomersTableAdapter();
OrdersTableAdapter ordAdapter = new OrdersTableAdapter();
CustomerOrderTDS ds = new CustomerOrderTDS();
custAdapter.Fill(ds.Customers);
ordAdapter.Fill(ds.Orders);
return ds;
}
}
}
正如你从代码中注意到的,静态方法 GetcustomerOrders() 以一种单一的方式返回我们客户和订单业务对象所需的数据。
在开发数据访问层时,我们必须采取的最后一步是构建我们的服务类,它将成为数据访问层和相关业务类之间的中间件。
因此,让我们开始创建一个名为 CustomerOrderService.cs 的新类。最终结果显示在下面的代码中。
using System;
using System.Collections.Generic;
using System.Text;
using DCAF.DataLayer.CustomerOrderTDSTableAdapters;
namespace DCAF.DataLayer
{
public class CustomerOrderService
{
#region "Storage"
private CustomersTableAdapter m_customersAdapter = null;
protected CustomersTableAdapter CustomersAdapter
{
get
{
if (m_customersAdapter == null)
{
m_customersAdapter = new CustomersTableAdapter();
}
return m_customersAdapter;
}
}
private OrdersTableAdapter m_ordersAdapter = null;
protected OrdersTableAdapter OrdersAdapter
{
get
{
if (m_ordersAdapter == null)
{
m_ordersAdapter = new OrdersTableAdapter();
}
return m_ordersAdapter;
}
}
#endregion "Storage"
#region "Public Interface
public CustomerOrderTDS.CustomersDataTable GetCustomers()
{
return CustomersAdapter.GetData();
}
public CustomerOrderTDS.OrdersDataTable GetOrders()
{
return OrdersAdapter.GetData();
}
public CustomerOrderTDS GetCustomerOrders()
{
return CustomerOrderTDS.GetCustomerOrders();
}
#endregion "Public Interface"
}
}
正如你可以从代码中检索到的,我们使用单独的 TableAdapter 来检索原子表数据,并使用我们增强的局部类型化数据集方法来一次性检索客户和订单。你可能已经注意到,对于返回单个客户或订单表数据,我们只是在适配器上使用默认的 GetData() 方法,仅此而已!
好的,关于数据访问层和数据访问层服务类就到这里了,让我们立刻转到业务层!
第二步:创建自定义业务层
首先要做的是向我们的解决方案中添加一个新项目,它仍然是 类库 类型,称之为 DCAF.BusinessLayer。
由于我们的自定义业务对象应该知道从哪里获取数据,因此我们此时应该添加对 DCAF.DataLayer 程序集的引用。这可以轻松完成:选择项目文件夹中的“引用”,然后右键单击,选择“添加引用”,转到“项目”选项卡,然后选择程序集,就像图 6 中所示一样。

图 6. 向项目添加程序集引用
接下来,向项目中添加 2 个类,分别名为 CustomerBO(它将保存客户定义)和 OrderBO(它将保存订单定义)。
using System;
using System.Collections.Generic;
using System.Text;
using DCAF.DataLayer;
using DCAF.DataLayer.CustomerOrderTDSTableAdapters;
namespace DCAF.BusinessLayer
{
public class CustomerBO
{
private string m_CustomerId;
private string m_CompanyName;
private List<OrderBO> m_Orders = new List<OrderBO>();
public string CustomerId
{
get { return m_CustomerId; }
set { m_CustomerId = value; }
}
public string CompanyName
{
get { return m_CompanyName; }
set { m_CompanyName = value; }
}
public List<OrderBO> Orders
{
get { return m_Orders; }
}
}
}
Customer Business Object 类包含 2 个属性,一个用于 ID,一个用于名称,并包含一个订单集合(List)。还要注意此处使用了 DCAF.DataLayer 引用,我们稍后会回来讨论这个!
namespace DCAF.BusinessLayer
{
public class OrderBO
{
private int m_OrderId;
private string m_ProductName;
private CustomerBO m_Customer;
private DateTime m_OrderDate;
public CustomerBO Customer
{
get { return m_Customer; }
set { m_Customer = value; }
}
public int OrderId
{
get { return m_OrderId; }
set { m_OrderId = value; }
}
public string ProductName
{
get { return m_ProductName; }
set { m_ProductName = value; }
}
public DateTime OrderDate
{
get { return m_OrderDate; }
set { m_OrderDate = value; }
}
}
}
另一方面,Orders 表也包含一些属性值以及对其所属客户的引用!
最后……我们应该实现我们的 O/R 映射函数来填充我们的自定义业务对象!为此,我实现了下面的方法(解释在代码段之后!)。该方法应添加到 CustomerBO 类中!
public static List<CustomerBO> GetCustomerOrders()
{
//--- Create the DataService
CustomerOrderService dataService = new CustomerOrderService();
CustomerOrderTDS dataContainer;
//--- Create the DataContainer for Customers and Orders retrieval
dataContainer = new CustomerOrderTDS();
//--- Get the Data from the DataService into the DataContainer
dataContainer.Merge(dataService.GetCustomerOrders());
//--- Create a CustomerObject List to hold the Customers
List<CustomerBO> custList = new List<CustomerBO>();
//--- Loop through the CustomerData from our DataContainer
foreach (CustomerOrderTDS.CustomersRow custRow in
dataContainer.Customers.Rows)
{
//--- Create a Customer Object Instance
CustomerBO customer = new CustomerBO();
//--- Map the Relational data to Object properties
//for Customer
ORM.RelationalToObject(customer, custRow);
//--- Select the Related Orders of the Customer
CustomerOrderTDS.OrdersRow[] orderRows =
(CustomerOrderTDS.OrdersRow[])custRow
.GetChildRows("FK_Orders_Customers");
//--- Loop through the related OrderData for the
// Current Customer
int numOrder = 0;
foreach (CustomerOrderTDS.OrdersRow orderRow in
orderRows)
{
numOrder++;
//--- Create an Order Object Instance
OrderBO order = new OrderBO();
//--- Map the Relational data to Object
//properties for Order
ORM.RelationalToObject(order, orderRow);
//--- Add the Customer Reference to the Order
order.Customer = customer;
order.ProductName = string.Format("Product
{0}-{1}", order.OrderId, numOrder);
order.Initializing = false;
//--- Relate the Order to The Current Customer
customer.Orders.Add(order);
}
customer.Initializing = false;
//--- Add the Customer to the CustomerList
custList.Add(customer);
}
return custList;
}
因此,我们创建了一个名为 GetCustomerOrders 的静态方法,该方法返回一个 CustomerObjects 列表(注意:你也可以使用 List 的泛型版本,List<T>)。首先,我们通过数据服务在类型化数据集中获取数据。然后我们遍历客户的数据容器行,添加客户信息,获取订单的 ChildRows(),添加订单信息,最后将客户对象添加到列表中。
为了简化 O/R 映射任务,我在项目中添加了一个 ORM 类。该类包含 2 个方法,一个用于将关系数据映射到对象属性(称为 ORM.RelationalToObject),另一个用于在保存对象时将属性映射回关系数据,并且是动态的,因此这些方法可以重用于任何表/对象映射场景。下面的代码显示了 RelationalToObject 映射的方法。相反的方法将在我们描述保存例程时进行解释。
public static void RelationalToObject( object p_obj, DataRow p_dataRow)
{
//--- Get Object Properties
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(p_obj);
//--- Apply OR-Mapping
foreach (DataColumn column in p_dataRow.Table.Columns)
{
for (int propertyIndex = 0; propertyIndex < props.Count; propertyIndex++)
{
PropertyDescriptor prop;
try
{
prop = props[propertyIndex];
}
catch
{
continue;
}
//--- Omit IListTypes (childcollections)
if (prop.PropertyType.GetInterface("IList") == null)
{
if (prop.Name.Trim().ToLower() == column.ColumnName.Trim().ToLower())
{
prop.SetValue(p_obj, p_dataRow[column]);
}
}
}
}
我们的业务层已经完成,再次强调,仅此演示而言,我只保留了业务对象的非常基础的实现,而没有考虑业务规则、缓存、PropertyChanged 通知(将在描述 Save() 部分时包含)等,因为这些实现超出了本文的范围。
第三步:创建表示层
在 Windows 窗体中呈现数据
好了,现在我们已经完成了数据访问层和业务层的所有管道代码,我们希望在表示层中可视化我们的业务数据。你会注意到,将逻辑分成层不仅可以使代码更易于维护,还为开发周期增加了极大的透明度,这意味着表示层程序员的学习曲线所需的时间更少,因为表示层程序员不必了解其他层的实现细节(业务层和数据层),他只需获取现成的自定义对象,并将精力集中在表示层中呈现它们。
首先要做的是添加一个新项目,这次我们说的是一个 Windows 应用程序。所以开始向解决方案中添加一个新项目,称之为 CDAF.PresentationLayer,并将 Form1 类重命名为 CustomerAdmin。
在此演示中,我们希望将客户及其相关订单分别显示在相互关联的网格中。因此,表示层程序员要做的第一件事就是向项目中添加一个 DataSource。你可以通过选择“数据”然后选择菜单上的“添加新数据源...”来实现(参见图 7)。

图 7. 向项目添加新的数据源
接下来选择对象作为数据源(参见图 8),然后单击“下一步”按钮。

图 8. 添加对象数据源
接下来,我们需要选择数据源的对象位置,这意味着我们必须对我们的业务层程序集添加引用!此时单击“添加引用”(参见图 9)。

图 9. 为对象位置程序集添加引用
接下来,你需要从项目选项卡列表中选择 DCAF.BusinessLayer 程序集(参见图 10)。

图 10. 选择程序集引用
接下来,我们必须选择要绑定的对象。此时选择 CustomerBO(参见图 11)。

图 11. 选择要数据绑定的对象
最后单击“下一步”,然后单击“完成”。请注意,DataSources 文件夹已添加到表示层项目的属性中(参见图 12)。

图 12. 表示层中嵌入的数据源
好了!现在我们已将数据源添加到项目中,我们将添加 Customer 和 Order 业务对象的实例。因此,首先从“数据”选项卡中选择“显示数据源”(参见图 13)。

图 13. 显示数据源
正如你所看到的(参见图 14),左侧的数据源面板显示了我们的 CustomerBO 对象。此时,首先选择 CustomerBO 网格图标并将其拖到窗体上,然后对 Orders 网格图标执行相同的操作。

图 14. 数据源面板
最后,窗体应显示如下(参见图 15)。

图 15. 初始对象绑定的客户窗体
这难道不是 VS IDE 的一个令人印象深刻的增强吗?
IDE 不仅添加了我们的网格,还直接添加了 bindingnavigator、customerbindingsource 和 orderbindingsource(参见图 16)。默认情况下,CustomerGrid.DataSource 绑定到 customerBOBindingSource,OrderGrid.DataSource 绑定到 ordersBindingSource,customerBOBindingNavigator.DataSource 绑定到 customerBOBindingSource。

图 16. 绑定工具
所以,UI 程序员剩下的工作就是从我们的 CustomerBO 获取数据并将相应的 BindingSources 绑定起来,正如你在下面的代码中看到的!
private void CustomerAdmin_Load(object sender, EventArgs e)
{
//--- Get the data through the static method of our CustomerBO
customerBOBindingSource.DataSource = DCAF.BusinessLayer.CustomerBO
.GetCustomerOrders();
//--- DataBind the BindingSources
ordersBindingSource.DataSource = customerBOBindingSource;
ordersBindingSource.DataMember = "Orders";
}
现在按“F5”并微笑!从结果中(参见图 17)你可以看到我们的网格已顺利加载。选择另一个客户,你将看到订单网格自动调整其绑定以反映正确的数据!

图 17. CustomerAdmin
正如你从 OrderGrid 中看到的,我们仍然有一个小问题。对于 Customer 列,我们希望看到 CustomerID 而不是 Customer 对象类型的声明。网格显示 Customer 对象类型声明的原因是绑定列的性质。如果你打开 OrderGird 的网格属性,然后选择 Columns(Collection)属性,你将看到该列绑定到 OrderObject 中 Customer 对象引用的实例。现在,如果我们想在列中显示一些“有意义”的数据,比如说 CustomerID,我们必须订阅 OrderGrid 的 CellFormatting 事件,并添加如下代码。
private void OnCellFormatting(object sender,
DataGridViewCellFormattingEventArgs e)
{
//--- Display CustomerID instead of CustomerObject
if (ordersDataGridView.Columns[e.ColumnIndex]
.DataPropertyName == "Customer")
{
object currentCustomer = customerBOBindingSource.Current;
PropertyDescriptorCollection props = TypeDescriptor
.GetProperties(currentCustomer);
PropertyDescriptor propDesc = props.Find("CustomerID", true);
try
{
e.Value = propDesc.GetValue(currentCustomer).ToString();
}
catch
{
e.Value = "[UNDEFINED]";
}
}
}
如果你在调整代码后重新运行应用程序,你将看到只有 CustomerID 列被绑定!(参见图 18)。

图 18. 增强的 CustomerAdmin
更新数据库中的新添加和已删除数据
到目前为止,我们一直在编写代码将数据从数据访问层绑定到表示层的数据感知组件(DataGridView)。此时,用户可以通过 DataGridView 的界面添加、删除或修改客户或订单相关数据。在添加、删除或修改数据后,用户可以选择将输入的信息更新到数据库。在本文的剩余部分,我们将描述更新数据库中新添加、修改或删除数据所涉及的必要步骤。
更新我们的自定义对象到数据访问层涉及相当多的代码,首先,我们将开始在客户管理窗体的私有成员中添加两个列表实例。这些列表实例将跟踪已删除的客户和订单。
private List<CustomerBO> m_deletedCustomers = null;
private List<OrderBO> m_deletedOrders = null;
接下来,我们应该处理 customerBoBindingSource 的 AddingNew 事件,该事件处理为 BindingSource 添加新的 CustomerObject。(参见图 19)。

图 19. 处理 CustomerBindingSource 的 AddingNew 事件
处理将新客户添加到我们的 CustomerBindingSource 的代码如下所示。
private void customerBOBindingSource_AddingNew(object sender,
AddingNewEventArgs e)
{
CustomerBO customer = new CustomerBO();
customer.IsNew = true;
customer.IsDirty = true;
customer.CompanyName = "<new Customer>";
customer.CustomerId = "<new CustomerID>";
e.NewObject = customer;
customer = null;
}
同样的处理程序也应该为我们的 OrderBindingSource 激活。(请注意,下面的代码显示)负责添加新订单的代码也应该获取父集合(Customer)的引用。由于我们的订单对象必须包含唯一的 OrderID,我们将此 ID 设置为一个负的增量值。我们的“Orders”数据集包含一个 OrderID 列,该列已被设置为自动增量列。在 OrderObject 中设置的 ID 设置为负值,以避免在数据库中插入新订单行时发生冲突。在服务器端插入 OrderTable 中的新行时,数据库服务器将添加新的 OrderID 并分配一个新的唯一 OrderID,该 ID 将在 Update DataSet 中返回,并应与客户端的对象值同步(我将在本文的下一节中解释此功能)。
private void ordersBindingSource_AddingNew(object sender,AddingNewEventArgs e)
{
OrderBO order = new OrderBO();
order.IsNew = true;
order.IsDirty = true;
order.OrderDate = DateTime.Today;
order.ProductName = "<new productname>";
//--- Set Temporary OrderID - Will be set to negative
//value to avoid update conflicts
m_lastOrderID--;
order.OrderId = m_lastOrderID;
//--- Get the Current Customer
CustomerBO currentCustomer = (CustomerBO)customerBOBindingSource.Current;
if (!(currentCustomer == null))
{
order.Customer = currentCustomer;
}
else
{
throw new NullReferenceException("Customer for Order not found !");
}
e.NewObject = order;
order = null;
}
处理修改事件对于表示层开发人员来说要透明得多。有几个类参与处理 CustomerBO 或 OrderBO 的属性修改部分。我们将逐步讨论这些类。这些辅助类实现在项目的业务层程序集中。
让我们首先从 IEventPublisher 接口开始。这个接口包含了对象属性修改事件的蓝图。让我们仔细看看这个接口类。
public interface IEventPublisher
{
void RegisterListener<T>(T p_listener) where T : IEventListener;
void UnregisterListener<T>(T p_listener) where T : IEventListener;
void NotifyListeners();
bool IsDirty {get; set;}
bool Initializing { get; set;}
}
IEventPublisher 接口包含了用于注册或注销监听器对象、在对象属性值更改时通知监听器的方法接口,以及 2 个属性设置:首先是 IsDirty 属性,它将附加对象置于修改状态;其次是 Initializing 属性,在加载对象数据时设置为 true(从而防止在加载对象数据时触发修改事件)。
接下来,我们来看看 IEventListener 接口。这个接口包含一个对 Publisher 对象(在我们的例子中是客户或订单对象)的引用,并描述了用于处理对象属性“修改”事件的方法的方法签名。
public interface IEventListener
{
void OnNotification(IEventPublisher p_publisher);
}
Publisher 类是每个自定义对象业务类的基类。BusinessObject 直接从 Publisher 类派生。Publisher 类包含用于“注册”或“注销”监听器对象的方法。监听器对象是将被通知业务对象类属性更改的对象。典型的监听器可以是一个 Windows 窗体。这个基类还包含一些基属性,用于将业务对象设置为某些初始状态,如“IsNew”、“IsDirty”(已修改)或“IsInitializing”状态。
public abstract class Publisher : IEventPublisher
{
private delegate void m_eventHandler(IEventPublisher p_publisher);
private event m_eventHandler m_event;
#region "IEventPublisher Implementation"
public void RegisterListener<T>(T p_listener) where T : IEventListener
{
m_event += new m_eventHandler(p_listener.OnNotification);
}
public void UnregisterListener<T>(T p_listener) where T : IEventListener
{
m_event -= new m_eventHandler(p_listener.OnNotification);
}
public void NotifyListeners()
{
if (m_event != null)
m_event(this);
}
protected bool m_isDirty = false;
public bool IsDirty
{
get { return m_isDirty; }
set { m_isDirty = value; }
}
protected bool m_initializing = true;
public bool Initializing
{
get { return m_initializing; }
set { m_initializing = value; }
}
protected bool m_isNew = false;
public bool IsNew
{
get { return m_isNew; }
set { m_isNew = true; }
}
#endregion "IEventPublisher Implementation"
}
ObjectChanged Listener 类包含 OnNotification 方法,当(已注册到 NotifyChanged 事件的)业务对象类的属性发生更改时,该方法将被执行。
public class ObjectChangedListener : IEventListener
{
#region "IEventListener Members"
public void OnNotification(IEventPublisher p_publisher)
{
if (!p_publisher.Initializing)
{
p_publisher.IsDirty = true;
}
}
#endregion "IEventListener Members"
}
在这种情况下(参见下面的代码),Notification 事件已附加到 set 属性。因此,当属性值更改时,将触发事件(我们 CustomerBO 的属性)。
public string CustomerId
{
get { return m_CustomerId; }
set
{
m_CustomerId = value;
NotifyListeners();
}
}
public string CompanyName
{
get { return m_CompanyName; }
set
{
m_CompanyName = value;
NotifyListeners();
}
}
当用户点击保存按钮时,将执行下面的代码来更新我们的后端数据库中的已添加、已修改或已删除的对象。由于涉及的代码量很大,我已将代码解释集成在源代码中。
private void customerBOBindingNavigatorSaveItem_Click(object sender,
EventArgs e)
{
//--- Validate the Value which loses control first
this.Validate();
//--- End the CurrentEdit on the BindingSources
customerBOBindingSource.EndEdit();
ordersBindingSource.EndEdit();
//--- Create Customer and Order List to hold the
//Changed objects
List<CustomerBO> changedCustomers = null;
List<OrderBO> changedOrders = null;
//--- Loop through the ObjectStacks to catch changed
//property data
foreach (object obj in customerBOBindingSource)
{
CustomerBO customer = (CustomerBO)obj;
if (customer.IsDirty)
{
if (changedCustomers == null)
changedCustomers = new List<CustomerBO>();
changedCustomers.Add(customer);
}
foreach (OrderBO order in customer.Orders)
{
if (order.IsDirty)
{
if (changedOrders == null)
changedOrders = new List<OrderBO>();
changedOrders.Add(order);
}
}
customer = null;
}
if (changedCustomers != null || changedOrders != null ||
m_deletedCustomers != null || m_deletedOrders !=
null)
{
bool IsUpdateOK = true;
try
{
//--- Update through Business Object Layer
int numUpdate = m_customerBO
.SaveCustomerOrders(changedCustomers,
m_deletedCustomers, changedOrders,
m_deletedOrders);
//--- Display Success
MessageBox.Show(string.Format("{0} rows were
successfully updated to the database !",
numUpdate.ToString()));
}
catch (Exception ex)
{
IsUpdateOK = false;
//--- Show Error
MessageBox.Show(ex.Message, "Error Occured
Update Failed!");
}
finally
{
//--- Reset object state & Release Resources
if (IsUpdateOK)
{
//--- First Syncronize the OrderID's with
//Server Versions
if (changedOrders != null)
{
m_customerBO
.SyncroOrderID(changedOrders);
}
if (changedCustomers != null)
{
foreach (CustomerBO customer in
changedCustomers)
{
customer.IsDirty = false;
customer.IsNew = false;
}
changedCustomers = null;
}
if (changedOrders != null)
{
foreach (OrderBO order in changedOrders)
{
order.IsDirty = false;
order.IsNew = false;
}
changedOrders = null;
}
//--- Release Helper Objects
m_deletedCustomers = null;
m_deletedOrders = null;
//--- Refresh the OrderGrid
ordersDataGridView.Refresh();
}
}
}
}
虽然 已添加 和 已修改 的对象可以在包含的列表中追踪,但 已删除 的对象则不能。所以我们必须手动追踪这些对象。为此,我们必须在代码中实现以下事件处理程序。
在定义部分
public partial class CustomerAdmin : Form
{
private CustomerBO m_customerBO;
private List<CustomerBO> m_deletedCustomers = null;
private List<OrderBO> m_deletedOrders = null;
. . .
}
负责删除客户或订单对象的代码
private void customerBODataGridView_UserDeletingRow(object sender,
DataGridViewRowCancelEventArgs e)
{
OnCustomerOrderDelete(sender,e);
}
private void OnCustomerOrderDelete(object sender,
DataGridViewRowCancelEventArgs e)
{
if (m_deletedCustomers == null)
m_deletedCustomers = new List<CustomerBO>();
m_deletedCustomers.Add((CustomerBO)e.Row.DataBoundItem);
if (((CustomerBO)e.Row.DataBoundItem).Orders != null)
{
if (m_deletedOrders == null)
m_deletedOrders = new List<OrderBO>();
m_deletedOrders.AddRange(((CustomerBO)
e.Row.DataBoundItem).Orders);
}
}
private void ordersDataGridView_UserDeletingRow(
object sender, DataGridViewRowCancelEventArgs e)
{
OnSingleOrderDelete(sender,e);
}
private void OnSingleOrderDelete(object sender,
DataGridViewRowCancelEventArgs e)
{
if (m_deletedOrders == null)
m_deletedOrders = new List<OrderBO>();
m_deletedOrders.Add((OrderBO)e.Row.DataBoundItem);
}
最后,我们的业务层类负责处理到数据访问层的更新。下面的实现也以 乐观 的方式处理 数据库并发 问题。
public int SaveCustomerOrders(List<CustomerBO> p_addedOrModifiedCustomers,
List<CustomerBO> p_deletedCustomers,
List<OrderBO> p_AddedOrModifiedorders,
List<OrderBO> p_deletedOrders)
{
//--- Create the Service to update the Data
CustomerOrderService dataService = new
CustomerOrderService();
//--------------------------------------------------------
//--- Step 1 : Add Deleted Customer Object Information ---
//--------------------------------------------------------
if (p_deletedCustomers != null)
{
foreach (CustomerBO deletedCustomerObject in
p_deletedCustomers)
{
//--- Create New CustomerRow to Hold Reference to
// the Deleted CustomerRow
CustomerOrderTDS.CustomersRow deletedCustomerRow
= m_dataContainer.Customers.NewCustomersRow();
//--- Check if Row Exists in Our DataContainer
deletedCustomerRow =
m_dataContainer.Customers
.FindByCustomerID(deletedCustomerObject
.CustomerId);
//--- Set RowState to Delete in our DataContainer
if (deletedCustomerRow != null)
{
deletedCustomerRow.Delete();
}
}
}
//--------------------------------------------------------
//--- Step 2 : Add Deleted Order Object Information ---
//--------------------------------------------------------
if (p_deletedOrders != null)
{
foreach (OrderBO deletedOrderObject in
p_deletedOrders)
{
//--- Create a New OrderRow to Hold Reference to
//the Deleted OrderRow
CustomerOrderTDS.OrdersRow deletedOrderRow =
m_dataContainer.Orders.NewOrdersRow();
//--- Check if Row Exists in Our DataContainer
deletedOrderRow =
m_dataContainer.Orders.FindByOrderID(
deletedOrderObject.OrderId);
//--- Set RowState to Delete in our DataContainer
if (deletedOrderRow != null)
{
deletedOrderRow.Delete();
}
}
}
//-------------------------------------------------------------
//--- Step 3 : Add New/Modified Customer Object Information ---
//-------------------------------------------------------------
if (p_addedOrModifiedCustomers != null)
{
foreach (CustomerBO addedOrModifiedCustomerObject in
p_addedOrModifiedCustomers)
{
//--- First Check If Current Customer Object is a
// New or Modified Object in our DataContainer
if (addedOrModifiedCustomerObject.IsNew)
{
//--- Add a New CustomerRow to Our
//DataContainer.Customer Table
CustomerOrderTDS.CustomersRow newCustomerRow
= m_dataContainer.Customers
.NewCustomersRow();
//--- Map object properties to rowcolumns
ORM.ObjectToRelational(
addedOrModifiedCustomerObject,
newCustomerRow);
//--- Add the New Customer Row to Our
//DataContainer
m_dataContainer.Customers
.AddCustomersRow(newCustomerRow);
}
else
{
//--- Get Modified Row Information
CustomerOrderTDS.CustomersRow
modifiedCustomerRow = m_dataContainer
.Customers.NewCustomersRow();
//--- Map object properties to rowcolumns
ORM.ObjectToRelational(
addedOrModifiedCustomerObject,
modifiedCustomerRow);
if (modifiedCustomerRow != null)
{
//--- Search modified Row in
//DataContainer
CustomerOrderTDS.CustomersRow
customerRowToModify =
m_dataContainer.Customers
.FindByCustomerID(
modifiedCustomerRow.CustomerID);
//--- Map Changed Data, RowState will be
//set to True for our
//DataContainer.Customer Row !
if (customerRowToModify != null)
{
for (int i = 0; i < m_dataContainer
.Customers
.Columns
.Count; i++)
{
customerRowToModify[i] =
modifiedCustomerRow[i];
}
}
}
}
}
}
//------------------------------------------------------------
//--- Step 4 : Add New/Modified Order Object Information ---
//------------------------------------------------------------
if (p_AddedOrModifiedorders != null)
{
foreach (OrderBO addedOrModifiedOrderObject in
p_AddedOrModifiedorders)
{
//--- First Check if Current Order is a New or
//Modified object in our DataContainer
if (addedOrModifiedOrderObject.IsNew)
{
//--- Add a New OrderRow to Our
//DataContainer.Order Table
CustomerOrderTDS.OrdersRow newOrderRow =
m_dataContainer.Orders.NewOrdersRow();
//--- Map object properties to rowcolumns
ORM.ObjectToRelational(
addedOrModifiedOrderObject, newOrderRow);
//--- Map Foreign Key for Customers
newOrderRow.CustomerID =
addedOrModifiedOrderObject
.Customer
.CustomerId;
//--- Add the New Order Row to Our
//DataContainer
m_dataContainer.Orders
.AddOrdersRow(newOrderRow);
}
else
{
//--- Get Modified Row Information
CustomerOrderTDS.OrdersRow
modifiedOrderRow = m_dataContainer
.Orders
.NewOrdersRow();
//--- Map object properties to rowcolumns
ORM.ObjectToRelational(
addedOrModifiedOrderObject,
modifiedOrderRow);
if (modifiedOrderRow != null)
{
//--- Search Modified Row in
//DataContainer
CustomerOrderTDS.OrdersRow
orderRowToModify = m_dataContainer
.Orders
.FindByOrderID(
modifiedOrderRow.OrderID);
//--- Map Changed Data, RowState will be
//set to True for our
//DataContainer.Order Row !
if (orderRowToModify != null)
{
for (int i = 0; i < m_dataContainer
.Orders
.Columns
.Count; i++)
{
System.Data.DataColumn column =
orderRowToModify
.Table.Columns[i];
if (!column.ReadOnly)
{
orderRowToModify[i] =
modifiedOrderRow[i];
}
}
//--- Re-Map Foreign Key for
//Customer
orderRowToModify.CustomerID =
addedOrModifiedOrderObject
.Customer.CustomerId;
}
}
}
}
}
//--- Update through the DataService
if(m_dataContainer.HasChanges())
{
bool updateOK = true;
try
{
return dataService
.SaveWithTransaction(
m_dataContainer,false);
}
catch (DBConcurrencyException dbconcEx)
{
//--- DbConcurrency Occured, ask User either to
//Persist his changes or reload from server
string message = dbconcEx.Message + "\r\n";
message += "Persist changes to the DataBase
[Yes]\r\n" +
"Reload the changed Data from the
Server [No]";
string caption = "DbConcurrency !";
MessageBoxButtons buttons =
MessageBoxButtons.YesNo;
DialogResult result;
// Displays the MessageBox.
result = MessageBox.Show(message, caption,
buttons);
if (result == DialogResult.Yes)
{
//--- Persist the changes to the Database
try
{
return dataService
.SaveWithTransaction(
m_dataContainer, true);
}
catch (Exception ex)
{
m_dataContainer.RejectChanges();
updateOK = false;
throw ex;
}
}
else
{
try
{
//--- Reload data from server and merge
//with local data
if (m_dataContainer.HasErrors)
{
if (m_dataContainer
.Customers.HasErrors)
{
//------------------------------------------
//--- Resolve DbConcurrency For Customers---
//------------------------------------------
//---Get Serverside RowData
CustomerOrderTDS.CustomersRow[]
customerErrorRows
= (CustomerOrderTDS.
CustomersRow[])
m_dataContainer
.Customers.GetErrors();
//---Merge with local DataSet
foreach (CustomerOrderTDS
.CustomersRow
customerErrorRow
in customerErrorRows)
{
m_dataContainer
.Customers
.Merge(
dataService
.GetCustomerRow(
customerErrorRow
.CustomerID));
}
//--- Remap changed Data to
//object Data
foreach (CustomerBO
customerObject
in p_addedOrModifiedCustomers)
{
//--- Get the Updated Row
//from our DataContainer
CustomerOrderTDS
.CustomersRow customerRow =
(CustomerOrderTDS
.CustomersRow)
m_dataContainer
.Customers
.FindByCustomerID(
customerObject
.CustomerId);
//--- Only Update Error
//Objects
bool isErrorRow = false;
foreach
(CustomerOrderTDS
.CustomersRow
customerErrorRow
in customerErrorRows)
{
if (customerErrorRow
.CustomerID ==
customerObject
.CustomerId)
{
isErrorRow = true;
break;
}
}
if (customerRow != null &&
isErrorRow)
{
ORM.RelationalToObject(customerObject,
customerRow);
}
}
}
//------------------------------------------
//--- Resolve DbConcurrency For Orders ---
//------------------------------------------
if (m_dataContainer.Orders.HasErrors)
{
//--- Get ServerSideData
CustomerOrderTDS.OrdersRow[] orderErrorRows
= (CustomerOrderTDS.OrdersRow[])
m_dataContainer.Orders.GetErrors();
//--- Merge with local DataSet
foreach (CustomerOrderTDS.OrdersRow orderErrorRow
in orderErrorRows)
{
m_dataContainer.Orders.Merge(
dataService.GetOrderRow(orderErrorRow.OrderID));
}
//--- Remap changed Data to object Data
foreach (OrderBO orderObject
in p_AddedOrModifiedorders)
{
/*--- Get the Updated Row from our
DataContainer */
CustomerOrderTDS.OrdersRow orderRow =
(CustomerOrderTDS.OrdersRow)
m_dataContainer.Orders.FindByOrderID(
orderObject.OrderId);
//--- Only Update Error Objects
bool isErrorRow = false;
foreach (CustomerOrderTDS.OrdersRow orderErrorRow
in orderErrorRows)
{
if (orderErrorRow.OrderID ==
orderObject.OrderId)
{
isErrorRow = true;
break;
}
}
if (orderRow != null && isErrorRow)
{
ORM.RelationalToObject(orderObject,
orderRow);
}
}
}
}
}
catch (Exception ex)
{
m_dataContainer.RejectChanges();
updateOK = false;
throw ex;
}
}
}
finally
{
if (updateOK)
{
//--- Accept the Changes on the DataSet
m_dataContainer.AcceptChanges();
}
}
}
return 0;
}
好了……各位读者!希望你们喜欢这篇文章。