用四步法使用 IdeaBlade DevForce 创建应用程序





4.00/5 (3投票s)
了解使用 DevForce 构建应用程序的体验,并附带分步说明、示例和代码。
DevForce 由经验丰富的企业应用程序开发人员从头开始设计,并牢记几个非常重要的目标:
- 使其(遵循爱因斯坦的名言)“尽可能简单,但不可过于简单”。
- 支持(并鼓励)架构中的“关注点分离”:您的业务模型(及所有业务逻辑)与用户界面完全分离,因此您可以将其重用于一个以上用户界面和一个以上应用程序。
- “不要限制我。” 您可以构建您*想要*构建的应用程序,并包含您需要的功能和用户界面。我们使这一过程变得更加容易,并确保您的最终产品真正具有工业级强度——但我们不会越俎代庖,也不会妨碍您。开始时不会,而——更重要的是——在您投入了大量开发工作,以至于改变方向不再是选项时,即便您多么希望能够改变,也不会妨碍您。
那么,使用 DevForce Classic 构建应用程序是什么样的体验呢?让我们一步步来看这四个简单的步骤。
- 从数据库创建数据模型
- 添加业务逻辑
- 从数据模型自动填充用户界面
- N 层部署
从数据库创建数据模型
您首先从 Visual Studio 的“工具”菜单中启动 DevForce Object Mapper,并指定要使用的数据库。[1]
接下来,在 Object Mapper 中选择哪些数据库表将作为业务对象的基础。配置这些业务对象后,Object Mapper 将生成业务逻辑的基类,然后您可以向子类添加自定义逻辑。
如果需要,可以重命名任何业务对象类:右侧是我们准备将基于数据库 OrderSummary 表的业务对象重命名为“Order”。
您也可以重命名对象的任何属性。您不必拘泥于后端数据源中使用的名称,这些名称可能反映了不适合您的各种命名约定和做法。对象模型是*您的*世界,您说了算!
DevForce 还会将*关系属性*生成到您的业务类中。这些属性反映了在源数据库中发现的外键关系。

在您随后针对模型编写的代码中,您可以通过引用 DevForce 生成的 Order 的 `OrderDetails` 属性来检索该 Order 的行项目:例如,`myOrder.OrderDetails`。
在 Object Mapper 中,您可以进行更多操作,包括请求它自动生成验证逻辑以强制执行数据库架构对数据的约束:例如,允许或不允许 NULL 值,强制执行字符串列的宽度等等。但它不必像您需要的那样复杂,您可以逐步引入细节。Object Mapper 支持往返编辑,因此您可以在整个应用程序开发周期中返回到它来添加、删除或修改对象。
您可以通过查看发布的教学视频了解更多关于对象映射过程的细节:
http://www.ideablade.com/DevForceClassic/DevForceClassic_videos.html
现在,我们保存我们的工作,DevForce Object Mapper 将为我们的对象模型生成 .NET 代码。
添加业务逻辑
现在您有了一个业务对象模型:一个用于引入自定义业务逻辑的场所和结构。仔细查看上面的生成文件,您会发现 Employee 类——实际上,每个业务对象类——都有两个生成的部分。我们称之为“开发者类”(位于 `Employee.cs` 文件中)和“生成”或“DataRow”类(位于 `EmployeeDataRow.cs` 文件中)。开发者类是您将放入所有自定义*业务逻辑*的地方;而“DataRow”类包含您的业务实体的所有基本属性和功能。
一旦创建,`Employee.cs` 开发者类文件将不会被 Object Mapper 修改。您在此处进行更改和增强,Object Mapper 将保留它们。相比之下,“DataRow”类文件完全属于 Object Mapper,并且每次您在 Object Mapper 中更改模型时都会重新生成。这种两部分结构是您在整个开发周期中与 Object Mapper 进行“往返”编辑能力的关键。
让我们为我们的 `Employee` 类添加一些业务逻辑。去除注释后,这是它生成后立即的样子:
using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using IdeaBlade.Persistence;
using IdeaBlade.Rdb;
using IdeaBlade.Persistence.Rdb;
using IdeaBlade.Util;
namespace Model {
[Serializable]
public sealed partial class Employee : EmployeeDataRow {
#region Constructors -- DO NOT MODIFY
private Employee() : this(null) {}
public Employee(DataRowBuilder pRowBuilder)
: base(pRowBuilder) {
}
#endregion
#region Suggested Customizations
#endregion
// Add additional logic to your business object here...
}
#region EntityPropertyDescriptors.EmployeePropertyDescriptor
#endregion
}
我们将添加一些自定义属性。
using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using IdeaBlade.Persistence;
using IdeaBlade.Rdb;
using IdeaBlade.Persistence.Rdb;
using IdeaBlade.Util;
namespace Model {
[Serializable]
public sealed partial class Employee : EmployeeDataRow {
#region Constructors -- DO NOT MODIFY
private Employee() : this(null) {}
public Employee(DataRowBuilder pRowBuilder)
: base(pRowBuilder) {
}
#endregion
#region Suggested Customizations
#endregion
// Add additional logic to your business object here...
#region Create Method
public static Employee Create(PersistenceManager pPersMgr,
String pFirstName, String pLastName, DateTime? pBirthDate) {
// Creates the aEmployee but it is not yet accessible to the application
Employee aEmployee = (Employee)pPersMgr.CreateEntity(typeof(Employee));
// Using the IdeaBlade Id Generation technique
pPersMgr.GenerateId(aEmployee, Employee.IdEntityColumn);
aEmployee.AddToManager();
// Add custom code here
aEmployee.FirstName = pFirstName; // new Employees always get a name
aEmployee.LastName = pLastName;
aEmployee.BirthDate = pBirthDate;
return aEmployee;
}
#endregion
#region Properties
/// <summary>
/// LastName in all upper case
/// </summary>
public override String LastName {
get { return base.LastName.ToUpper(); }
set { base.LastName = value; }
}
/// <summary>
/// LastName, FirstName
/// </summary>
public String LastFirst {
get {
String aLastFirst = this.LastName + ", " + this.FirstName;
if (aLastFirst == ", ") aLastFirst = "(Not specified)";
return aLastFirst;
}
}
/// <summary>
/// Age as of today
/// </summary>
public int Age {
get {
if (null == BirthDate) return 0;
DateTime oBirthDate = (DateTime)this.BirthDate;
DateTime oToday = DateTime.Today;
int oAge = oToday.Year - oBirthDate.Year;
if (oBirthDate.AddYears(oAge) > oToday) oAge--;
if (oAge < 0) return 0;
else return oAge;
}
}
/// <summary>
/// Total revenue for this Employee's orders
/// </summary>
public double TotalOrderRevenue {
get {
double revenue = 0;
foreach (Order aOrder in this.Orders) {
foreach (OrderDetail aOrderDetail in aOrder.OrderDetails) {
revenue += aOrderDetail.Quantity * Convert.ToDouble(aOrderDetail.UnitPrice) *
aOrderDetail.Discount;
}
}
return revenue;
}
}
#endregion
}
#region EntityPropertyDescriptors.EmployeePropertyDescriptor
#endregion
}
我们添加了一个 `Create()` 方法,以确保新创建的 `Employees` 实例包含最少必需的数据,并将其置于 DevForce `PersistenceManager` 的控制之下。(`PersistenceManager` 维护着一个复杂且性能增强的客户端缓存,并且还处理应用程序中所有保存和数据检索操作。我们将在 N 层部署部分详细讨论这一点。)
我们还添加了自定义属性 `LastFirst`、`Age` 和 `TotalOrderRevenue`,并重写了 `LastName` 属性的默认定义。通过这样做,我们控制了一个由 Object Mapper 生成到 `DataRow` 类中的属性(并且每次我们在那里进行更改时都会再次生成)。我们以一种确保我们的更改永远不会被覆盖的方式进行了操作。
您的自定义业务逻辑可以根据需要变得复杂而强大。例如,DevForce 拥有一个复杂的“Verification”系统,可帮助您建模复杂的[数据验证逻辑](https://codeproject.org.cn/Articles/11894/Devforce-Validation-using-Business-object-classes),包括跨属性甚至跨对象的验证。
您还可以实施安全性,不仅可以防止未经授权的用户访问您的数据,还可以根据用户的安全权限授予对数据和 UI 功能的选择性访问。DevForce 包括一个登录管理器以及允许您拦截数据检索和保存请求的服务器端方法,以便您可以根据用户确定是允许它们按请求执行,还是允许它们修改后执行,或者完全不允许。
同样,当您还在努力使基本功能正常运行时,不必担心应用程序的这一维度。通过 DevForce,安全性添加的结构从第一天起就已到位。当您准备好使用它时,它就在那里。
从数据模型自动填充用户界面
现在我们有了域模型,并且准备开始构建用户界面。模型不必是“完成”的;相反,您更有可能在模型和 UI 之间进行迭代往返,就像在模型中,您在 Object Mapper 和自定义业务逻辑之间进行往返一样。
我们将构建一个主/明细窗体来显示有关我们的员工及其销售订单的数据。我们首先将一个 Windows 应用程序(WinForms)项目添加到我们的解决方案中。我们将其命名为“UI”。
我们的 UI 项目需要使用我们在 Model 程序集中定义的业务对象,因此我们现在添加对该程序集的引用。
我们在 Visual Studio 中配置窗体的几个基本属性,然后将两个 `BindingSources` 和一个 `BindingNavigator` 拖放到窗体上。
接下来,我们将两个 DevForce UI 组件 `ControlBindingManager` 和 `DataGridViewBindingManager` 拖放到窗体上。`ControlBindingManager` 将管理 Employee 属性与 TextBox、DatePicker、ComboBox 等控件之间的数据绑定;我们将它重命名为 `mEmployeeControlBindingManager`。`DataGridViewBindingManager` 将管理 Order 对象属性与 .NET DataGridView 网格控件列之间的[数据绑定](https://codeproject.org.cn/Articles/11894/Devforce-Validation-using-Business-object-classes);因此,我们将它重命名为 `mOrdersDataGridViewBindingManager`。(DevForce 还为 Developer Express XtraGrid 和 Infragistics UltraGrid 控件提供 BindingManagers。)
现在我们准备使用控件填充窗体并配置数据绑定。DevForce BindingManagers 包含可视化设计器,使此过程变得容易。让我们从 `ControlBindingManager` 开始,并设置窗体以显示 Employee 的信息。
首先选择组件托盘中的 `mEmployeeControlBindingManager`,然后单击其右上角的“智能标签”。这将弹出一个菜单,从中我们将选择“自动填充”选项。
BindingManager 需要知道它将配置数据绑定的业务对象类型,因此我们在 Model 程序集中指定 Employee 类型。
将 BindingManager 设计器告知业务对象类型后,它会显示一个网格,其中包含 Employee 中可用于数据绑定的属性。我们只需将所需的属性拖到对话框的网格区域,设计器就会根据每个属性的数据类型和元数据选择合适的控件。
如果需要,我们可以覆盖设计器选择的控件……
……但如果我们已经告诉它我们偏好的控件套件(.NET、DevExpress 或 Infragistics),这很少有必要。
在选择要绑定的属性时,我们可以深入到相关对象的详细信息中,并指定一个“属性路径”来遍历对象模型。左侧,我们选择了 Employee 的 Manager 的 Photo 属性。该关系源自数据库,在数据库的表中定义。Object Mapper 发现了它并自动为 Employee 类型生成了 Manager 属性。
我们还可以将 `Manager` 属性整体拖到我们的数据绑定网格中。设计器会为该属性选择一个 ComboBox,通过少量配置,它将显示当前 Employee 的经理的姓名,而不是所有可能的经理列表。然后,当需要时,可以轻松直观地使用 ComboBox 来重新分配 Employee 给不同的经理。
这是我们在设计器中完成的数据绑定可视化规范。
当我们单击“<确定>”按钮时,设计器将填充我们的窗体并生成代码来配置数据绑定。我们调整了几个控件的位置,得到了一个看起来像这样的窗体:
此时我们几乎准备好使用我们的窗体了,但首先,让我们配置一个 DataGridView 来显示我们的 Employee 作为销售代表的订单。我们从组件托盘中的 `mOrdersDataGridViewBindingManager` 开始,选择“配置数据绑定”。
这次我们想创建到 Order 而不是 Employee 的绑定。
和以前一样,我们选择要显示的属性。
单击“确定”,重新定位并调整网格大小,瞧,它已经准备好使用了,并且所有数据绑定都已配置好。
我们仍然需要将我们的 `BindingSources` 连接到 `BindingNavigator` 和 `BindingManagers`,并编写一些代码来检索所需的员工。我们可以通过 Visual Studio Winforms 设计器进行进一步配置,但如果我们所有的剩余工作都在窗体的“代码隐藏”中完成,那么更容易看到全局。下面是完成的窗体代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Model;
using IdeaBlade.Persistence;
namespace UI {
public partial class EmployeeForm : Form {
public EmployeeForm() {
InitializeComponent();
}
private void EmployeeForm_Load(object sender, EventArgs e) {
ConfigureBindingSources();
ConfigureBindingNavigator();
ConfigureBindingManagers();
LoadSupportingData();
LoadMainData();
}
private void ConfigureBindingSources() {
mEmployeesBindingSource.DataSource = mEmployees;
mOrdersBindingSource.DataSource = mOrders;
mManagersBindingSource.DataSource = mManagers;
mEmployeesBindingSource.CurrentChanged +=
new EventHandler(mEmployeesBindingSource_CurrentChanged);
}
void mEmployeesBindingSource_CurrentChanged(object sender, EventArgs e) {
mOrders.ReplaceRange(CurrentEmployee.Orders);
}
private void ConfigureBindingNavigator() {
mEmployeesBindingNavigator.BindingSource = mEmployeesBindingSource;
}
private void ConfigureBindingManagers() {
mEmployeeControlBindingManager.BindingSource = mEmployeesBindingSource;
mOrdersDataGridViewBindingManager.BindingSource = mOrdersBindingSource;
}
private void LoadSupportingData() {
mManagers.ReplaceRange(mPersistenceManager.GetEntities<Employee>());
mManagers.ApplySort("LastName", ListSortDirection.Ascending, true);
}
private void LoadMainData() {
mEmployees.ReplaceRange(mPersistenceManager.GetEntities<Employee>());
}
public Employee CurrentEmployee {
get {
return (Employee)mEmployeesBindingSource.Current;
}
}
#region Private Fields
PersistenceManager mPersistenceManager = PersistenceManager.DefaultManager;
EntityList<Employee> mEmployees = new EntityList<Employee>();
EntityList<Order> mOrders = new EntityList<Order>();
EntityList<Employee> mManagers = new EntityList<Employee>();
#endregion Private Fields
}
}
从 `Form_Load` 处理程序调用的五个方法构成了向您展示步骤的路线图:
方法 | 操作 |
|
将 DevForce EntityLists 指定为窗体中使用的 .NET BindingSources 的 DataSource。DevForce EntityList<T> 是 .NET BindingList<T> 的子类,提供更丰富的[数据绑定](https://codeproject.org.cn/Articles/11894/Devforce-Validation-using-Business-object-classes)功能。 配置控制 Employees 的 BindingSource 的 CurrentChanged 处理程序。当用户切换到新的 Employee 时,该处理程序会加载新的 Employee 的 Orders EntityList。 |
|
将 BindingNavigator 链接到控制 Employees 的 BindingSource。 |
|
将 BindingManagers 链接到控制 Employees 和 Orders 的 BindingSources。 |
|
将数据加载到用于填充显示 Managers 的 ComboBox 的列表中。 |
|
加载 Employee 数据。 |
这是运行中的完成窗体。
构建它很简单,它也准备好扩展成更复杂的东西。DevForce 不会让你陷入困境!
N 层部署
现在您已经构建了应用程序,需要进行 N 层部署。您是否记得*设计并编写*了 N 层操作?没有 DevForce,要创建有效的[基础架构](https://codeproject.org.cn/Articles/11894/Devforce-Validation-using-Business-object-classes)来将数据从客户端移动到中间层再到数据层并返回——更不用说一致地应用业务逻辑和消除重复数据检索的客户端缓存了——这是一项艰巨的任务,需要大量的专业知识和经验才能正确完成。有了 DevForce,您就获得了封装中的专业知识和经验。当然,仍然有一些事情需要考虑,但[根本上](https://codeproject.org.cn/Articles/11894/Devforce-Validation-using-Business-object-classes)您的架构已经为 N 层部署做好了准备。
让我们以我们刚刚构建的应用程序为例。我们是否考虑过 N 层操作?并没有真正考虑。我们是否能够基本按原样部署该应用程序并期望它能够进行 N 层操作?是的,我们可以!
一项重要的任务是确定哪些程序集和其他文件需要在客户端部署,哪些需要在中间层部署。为了帮助完成这项任务,DevForce 提供了一个非常实用的小工具,称为 Deployment Test Tool。Deployment Test Tool 会向您显示 N 层应用程序的程序集和可执行文件,并指示哪些需要部署在哪里。
除了应用程序可执行文件和 DLL 外,IdeaBlade.ibconfig 配置文件的一份副本会部署到客户端和服务器文件夹。但副本并非完全相同:客户端配置文件中已删除后端数据库和/或 Web 服务的连接字符串。客户端应用程序现在知道如何以及在哪里联系中间层服务器,但根本不了解如何连接后端数据源。被盗的笔记本电脑无法用于破坏数据库!
只需进行我们刚刚完成的这些工作,您就可以准备在 N 层操作中测试您的应用程序了。您还没有将服务器文件移动到单独的计算机,但通过启动 `ServerConsole.exe`,您可以在本地计算机上创建一个 DevForce Business Object Server 实例。(完成后,您还可以将服务器部署为 Windows 服务或 IIS 下的服务。)
如果您现在启动 `UI.exe`,您将在一个*独立于* Business Object Server 的进程中运行您的客户端应用程序,并且这两个应用程序将立即开始跨进程通信。
会自动创建一个调试日志,显示客户端和服务器应用程序之间所有重要的通信。
当您准备好部署到不同的机器时,您已经可以在自己的机器上方便地测试应用程序的 N 层操作了。
Business Object Server 可以作为控制台应用程序、Windows 服务或使用 IIS 进行部署。如果您选择后者,您可以利用多处理核心进行扩展,并使用 SSL 加密客户端和服务器之间的所有通信。
结论
这就是我们的四个简单步骤。我们从数据库创建了数据模型;向模型添加了业务逻辑;在 DevForce UI 设计器工具的大力帮助下创建了 UI;并进行了 N 层部署。这个过程的任何部分都不会将您锁定在您不想要的应用程序样式中:您说了算。
DevForce 附带丰富的文档、代码示例和教程,可帮助您处理各种开发任务;世界一流的工程师团队将在您遇到困难时提供帮助。今天就来尝试一下吧!