企业应用程序架构:在 .NET 中设计应用程序和服务 - 第二部分






4.71/5 (32投票s)
客户订单管理系统的数据库和库设计
引言
- 第一部分 (带项目详情的分布式应用程序层):了解分布式环境中的分层设计是什么,以及我们将在实际应用程序的实现中如何命名它。
- 第二部分(数据库和库设计):了解数据库设计以及实现与 Edmx 容器交互的库(本文)。
- 第三部分(引擎和管理器设计):了解如何实现包含核心业务逻辑的引擎,以及如何实现带有服务合同的实际 WCF 服务。以及如何使用测试客户端测试服务。
- 第四部分(客户端实现):了解如何使用 MVVM 模式实现调用服务的实际客户端。
这是关于企业应用程序架构设计的规范系列文章的第二篇。在上一篇文章中,我们讨论了分布式应用程序层以及如何在示例应用程序(客户订单管理系统 (COMS))中实现这些层。因此,在本文中,我将介绍数据库设计、实体容器和库的实现。项目名称将保持与架构图右侧提到的名称相同,该图在第一部分中进行了说明。
背景
任何应用程序设计中的第一件事就是创建底层数据库来支持它。我们在设计数据库时必须非常小心,因为我们应该在数据库本身中涵盖尽可能多的业务逻辑,而不是在客户端代码中编写。坦率地说,我不是数据库设计的专家。如果您认为有些地方不准确,请随时发表评论。让我们开始数据库设计。
客户订单管理系统数据库设计
我将采用 COMS(客户订单管理系统)的几个示例用例进行实现,并使用这四张表进行演示。
- 客户表- 包含客户数据,如姓名、ID、地址、送货地址、账单地址等。
- 产品表– 包含产品数据,如产品名称、描述、单价等。
- 订单表- 包含订单下达时间的数据,包括客户 ID、订单日期、发货日期等。
- 订单明细表- 包含一个订单中每个产品的数据(因为您可以在一个订单中购买多个商品),包括订购的产品、数量、单价、折扣等。
您可能知道,为了使关系数据库正常工作,您应该在每个数据库中有一个字段,该字段唯一标识(主键)数据库表中的该行。此外,我们应该根据其关系(外键)在表中建立链接。下表说明了表之间的关系。
表和键详细信息
表格 | 主键 |
客户 |
CustomerID |
产品 |
ProductID |
Order |
OrderID |
OrderDetails |
OrderDetailsID |
表关系详情
表格 | 相关 | 外键 |
订单 |
Customers |
CustomerID |
OrderDetails |
订单 |
OrderID |
OrderDetails |
产品 |
ProductID |
请查看下面的数据库设计图以获取更多想法。
在 Microsoft SQL Server 中创建数据库和表
在创建表之前,我们必须在 SQL Server 中创建自己的数据库。请注意,我使用的是 Microsoft SQL Server Management Studio Express 来创建数据库和表。
打开 Microsoft SQL Server Management Studio Express,您将看到如下所示的窗口
现在,要创建数据库,请遵循 Microsoft TechNet 上的此处的步骤。我假设您将数据库命名为Customer Database。创建数据库后,您将在对象资源管理器树中的数据库下看到一些预定义的对象的显示,例如表、数据库关系图等。请参阅下图。
现在我们可以继续创建表了。
使用表设计器创建新表
- 在对象资源管理器中,右键单击数据库的“表”项,然后单击“新建表”。
- 键入列名,选择数据类型,并为每列选择是否允许 Null。
- 从“文件”菜单中,选择“保存表名”。
- 在“选择名称”对话框中,键入表名,然后单击“确定”。
创建我在数据库设计部分提到的所有四个表,并带有适当的主键。创建完所有表后,我们还需要像在数据库设计部分提到的那样为表创建外键。
您可以使用 SQLQuery 分析器创建外键。单击 SQL Express 工具栏中的新建查询按钮,然后将以下查询粘贴到查询窗口中,选择所有查询并按 F5。完成此操作后,外键将自动创建。
ALTER TABLE Orders WITH CHECK ADD CONSTRAINT [FK_CustomerID] FOREIGN KEY([CustomerID])
REFERENCES [Customers] ([CustomerID])
ALTER TABLE OrderDetails WITH CHECK ADD CONSTRAINT [FK_OrderID] FOREIGN KEY([OrderID])
REFERENCES [Orders] ([OrderID])
ALTER TABLE OrderDetails WITH CHECK ADD CONSTRAINT [FK_ProductID] FOREIGN KEY([ProductID])
REFERENCES [Products] ([ProductID])
我假设您在此阶段已成功完成所有操作。太棒了 :)。现在我们已经完成了客户订单管理系统的数据库设计。请参阅下图,其中包含四个表和一些示例客户数据。
希望您已对如何设计数据库有了想法。
实体容器和库实现
正如我们在第一部分中解释的架构图中所讨论的,我们将讨论并实现从底部开始的第二层(数据访问层)。请参阅下图
您可以看到我清楚地提到了我们将使用什么语言和技术来实现这一层,以及项目名称的详细信息。因此,在开始实现之前,我们将简要讨论这一层的目的。
通常,数据访问层用于与数据库通信以存储/检索数据。以前,我们习惯使用纯 ADO.NET 来编写这一层。但现在,.NET Framework 默认提供ADO.NET Entity Data Model工具。因此,使用 Entity Data Model Wizard,我们可以创建一个 edmx 文件,该文件描述目标数据库架构,并定义 EDM 和数据库之间的映射。
为什么使用类库与 edmx 交互?我们不能在业务逻辑层中直接使用 edmx 文件吗?可以。最好不要在业务逻辑层中直接使用 edmx 文件。业务逻辑应该是一个独立的层。如果您直接在业务逻辑中使用 edmx 文件,则意味着您正在失去平台的扩展性。例如,如果您在数据库中添加一些额外的字段;您需要再次修改您的业务逻辑。为了避免这种依赖关系,我们在库中公开接口和方法来与 edmx 文件交互。
创建 EDMX 和库项目
我们在这里学习如何创建这两个项目以及需要选择什么类型的项目模板。
项目类型
- 一个类库
- 一个带 edmx 的实体项目
- 一个测试客户端项目
解决方案
打开 Visual Studio 2010,从 Visual C# -> Windows 中选择类库项目模板。请参阅截图中的解决方案和项目名称。
我使用了与架构图相同的名称(“ServiceLibaries.CustomerServiceLibrary
”)。我在解决方案中添加了两个文件夹和 .cs 文件用于实现。一个是用于 CustomerServiceLibrary
接口,另一个是用于实际方法实现。但请放心,现在文件中没有任何代码。这是我的 VS 解决方案结构。
让我们开始创建 edmx 文件,然后我们将回到实际的库实现。以下是从特定数据库生成 edmx 文件的步骤。
- 在解决方案资源管理器中,右键单击解决方案,添加一个新项目来维护 edmx 文件。将项目名称保留为“
CustomerOrderManagementSystemDEM
”。 - 右键单击
CustomerOrderManagementSystemDEM
项目,然后单击添加 -> 新建项。 - 在添加新项对话框中,选择左窗格中的“数据”,然后选择ADO.NET 数据实体模型。
- 在名称中,输入 edmx 文件名“
CustomerOrderManagementSystemEntityDataModel
”,然后单击“确定”。
现在您将被要求继续使用实体数据模型向导。从向导中选择从数据库生成选项,然后从组合框中选择您的数据连接。如果您在组合框中找不到您的数据库,请单击新建连接按钮,然后选择您的服务器名称、数据源、身份验证类型(保留为使用 Windows 身份验证),数据库名称为“Customer Database”,然后单击“确定”。
然后您将看到如下所示的连接字符串详细信息页面。单击下一步按钮,然后选择要生成实体的表。我选择了所有四个表。更改对话框底部的模型命名空间(可选),然后单击完成按钮。
当您单击“完成”按钮时,Visual Studio 将开始为您生成带 app.config 文件的 edmx 文件。生成后,您将在 XML 文件中看到 ConnectionStrings
标签以及连接字符串和提供程序名称等其他属性。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="Customer_DatabasesEntities"
connectionString="metadata=res://*/CustomerOrderManagementSystemEntityDataModel.
csdl|res://*/CustomerOrderManagementSystemEntityDataModel.ssdl|res:
//*/CustomerOrderManagementSystemEntityDataModel.msl;
provider=System.Data.SqlClient;
provider connection string='Data Source=XXXXX-PC\SQLEXPRESS;
Initial Catalog="Customer Databases";
Integrated Security=True;MultipleActiveResultSets=True'"
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
除了配置文件,还会生成 .edmx 文件(带代码隐藏)。如果您打开 .cs 文件,您会看到所有表都生成为实体对象类,以及默认的对象上下文类(“Customer_DatabasesEntities
”)。这些是我们将在库中用于与数据库交互的类。
就是这样。我们完成了 edmx 文件生成。让我们回到库实现,正如我们已经讨论过的,我们在库项目中使用了两个 .cs 文件。现在我们可以直接进行实现部分了。以下是 ICustomerServiceLibrary
接口,其中包含提供客户详细信息以及在数据库中保存数据的方法声明。
public interface ICustomerServiceLibrary
{
/// <summary>
/// Get all the customers data
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
List<Customer> GetCustomers(Customer_DatabasesEntities context);
/// <summary>
/// Get specific customer data with customer ID
/// </summary>
/// <param name="customerID"></param>
/// <param name="context"></param>
/// <returns></returns>
Customer GetCustomer(int customerID, Customer_DatabasesEntities context);
/// <summary>
/// Get the actual Entities object context from edmx.
/// </summary>
/// <returns></returns>
Customer_DatabasesEntities GetEntitiesObjectContext();
/// <summary>
/// Save the data in database with specific context.
/// </summary>
/// <param name="context"></param>
void Save(Customer_DatabasesEntities context);
}
接口提供四种功能
GetCustomers
– 通过实体对象上下文从数据库返回所有客户数据。GetCustomer(int CustomerID)
– 返回特定客户详细信息,以客户 ID 作为参数。GetEntitiesObjectContext()
– 返回实际的客户数据库实体对象上下文。Save (context)
– 将数据保存到数据库。
让我们继续方法的实际实现
namespace ServiceLibraries
{
public class CustomerServiceLibrary : ICustomerServiceLibrary
{
// Database entity context.
Customer_DatabasesEntities context = null;
/// <summary>
/// Constructor which initialize including context.
/// </summary>
public CustomerServiceLibrary()
{
context = new Customer_DatabasesEntities();
}
#region ICustomerServiceLibrary Members
/// <summary>
/// Returns customers data as list.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public List<Customer> GetCustomers(Customer_DatabasesEntities context)
{
return context.Customers.ToList() ;
}
/// <summary>
/// Returns specific customer detail.
/// </summary>
/// <param name="customerID"></param>
/// <param name="context"></param>
/// <returns></returns>
public Customer GetCustomer(int customerID, Customer_DatabasesEntities context)
{
var cust = from customer in context.Customers
where customer.CustomerID == customerID select customer;
return cust.FirstOrDefault();
}
/// <summary>
/// Saves the data in database and disposing the context.
/// </summary>
/// <param name="context"></param>
public void Save(Customer_DatabasesEntities context)
{
context.SaveChanges();
context.Dispose();
}
/// <summary>
/// Returns the context reference.
/// </summary>
/// <returns></returns>
public Customer_DatabasesEntities GetEntitiesObjectContext()
{
return context;
}
#endregion
}
}
CustomerServiceLibrary
类的构造函数创建实体对象上下文实例,并在类中共享。如果引擎需要实体上下文对象来将数据保存到数据库,我们可以通过库实例通过 GetEntitiesObjectContext
方法获取。
Save
方法用于在数据库中保存新数据。您可以看到 Save
方法内部调用了 context.SaveChanges()
方法。此方法将更改保存到数据库。要调用此方法,您必须在库项目中添加 System.Data.Entity
程序集引用。请勿忘记在您的库中添加此程序集。否则,您将收到编译错误。
就是这样。现在库已准备好提供客户数据服务。我们可以通过某个测试客户端来测试这个库吗?可以。让我们创建一个简单的控制台应用程序来测试这个库。
测试客户端
我在附加的示例中添加了一个测试客户端项目。此客户端项目包含在控制台中显示客户数据的代码。此外,它还会提示您输入新客户详细信息。输入新客户详细信息后,它将再次从数据库显示所有客户数据,包括新客户数据。请记住,您应该将我们的库和 edmx 项目都作为引用添加到客户端项目中。这是客户端代码static void Main(string[] args)
{
ICustomerServiceLibrary service = new CustomerServiceLibrary();
List list = service.GetCustomers(service.GetEntitiesObjectContext());
Console.WriteLine("Customer ID" + "First Name".PadLeft(21) +
"Last Name".PadLeft(29));
Console.WriteLine("========================================================");
int lastCustId = 0;
foreach (Customer data in list)
{
Console.WriteLine(data.CustomerID + data.FirstName.PadLeft(48) +
data.LastName.PadLeft(10));
lastCustId = data.CustomerID;
}
Customer newCustomer = new Customer();
Console.WriteLine("Enter New Customer Details");
Console.WriteLine("First Name:");
newCustomer.FirstName = Console.ReadLine();
Console.WriteLine("Last Name");
newCustomer.LastName = Console.ReadLine();
Console.WriteLine("Email Id");
newCustomer.Email = Console.ReadLine();
Console.WriteLine("Address1");
newCustomer.Address1 = Console.ReadLine();
Console.WriteLine("Address2");
newCustomer.Address2 = Console.ReadLine();
newCustomer.CustomerID = ++lastCustId;
service.GetEntitiesObjectContext().AddToCustomers(newCustomer);
service.Save(service.GetEntitiesObjectContext());
Console.WriteLine("Customer Data Stored Successfully");
service = new CustomerServiceLibrary();
List newlist = service.GetCustomers(service.GetEntitiesObjectContext());
Console.WriteLine("Customer ID" + "First Name".PadLeft(21) +
"Last Name".PadLeft(29));
Console.WriteLine("=========================================================");
foreach (Customer data in newlist)
{
Console.WriteLine(data.CustomerID + data.FirstName.PadLeft(48) +
data.LastName.PadLeft(10));
}
Console.ReadLine();
}
测试客户端输出
注意
请确保连接字符串已在客户端config文件中提及。否则,应用程序将永远无法连接到数据库。
要运行附加的示例应用程序,您需要在 SQL Server Management Studio Express 中创建数据库和表。但是,为了方便起见,我附加了数据库备份文件,因此您只需下载并将其导入到您的 SQL Server 中。如果您对如何在 SQL Server 中导入数据库有任何疑问,请访问此网站。
糟糕。在完成这篇文章的最后时刻,我的同事 Boopalan(他也擅长技术)提出了这个问题。为什么不将数据库集成到 Visual Studio 中呢?是的,他说得完全正确。我为什么要浪费您的时间来运行演示样本?因此,我决定解释如何在 Visual Studio 中管理/集成数据库,并在此处发布了一篇文章。如果您决定按照这个逻辑进行,您就不需要恢复随示例源一起上传的数据库备份文件。但是,我仍然保留了本文中解释的传统数据库设计概念,因为它将对一些不知道它的读者有所帮助。
我对这个逻辑和示例应用程序感到非常满意,我认为从您的角度来看,运行它真的很容易。因此,如果您认为这篇帖子对您通过库进行实体访问有帮助,我一定会非常感谢一些投票和评论:)。
正如我在引言中所述,其余部分将陆续发布。