概述

如前所述,领域模型(Domain Model)只是涉及程序业务逻辑的 POCO 类。所以不管是开发哪类程序,领域模型设计的原则和技巧是相通的。

基本设计原则——SOLID 原则

C# 是一种面向对象的编程语言。作为一名经验丰富的 C# 开发人员,你可能听说过面向对象设计的一大常识—— SOLID 原则。要设计一个好的领域模型,也应该遵循 SOLID 原则,当然在必要时可以视业务逻辑做适当调整。

下表是 SOLID 原则的摘要。你可以在 Wikipedia 上找到更多详细信息。另外,如果你想进一步了设计原则,我强烈推荐《Agile Principles, Patterns, and Practices in C#》。

首字母 原则 概念
S Single responsibility principle (SRP)
单一职责原则
一个类应该只有一个职责。
换句话说,类在设计好后再修改它的潜在原因只能有一个。
O Open/closed principle (OCP)
开闭原则
软件实体应该是可扩展,而不可修改的。
换句话说,类对扩展是开放的,而对修改是封闭的。
L Liskov substitution principle (LSP)
里氏替换原则
程序中的对象应可替换为其子类实例,且不会改变程序的正确性。
换句话说,当一个子类的实例能够替换任何其父类的实例时,它们间才具有 is-A 关系 。
I Interface segregation principle (ISP)
借口隔离原则
使用多个专用的接口比使用单一的通用接口好。 
换句话说,不能强迫消费类去依赖那些他们不使用的接口。
D Dependency inversion principle (DIP)
依赖反转原则
应该依赖于抽象而不是实体。

高级设计原则和设计模式

当将现实世界对象抽象并封装成领域模型时,领域模型之间的关系映射了现实世界对象之间的关系。这其中有些关系是清晰、固定、通用的。例如,经理的下属也可以是经理,文件夹中的子项也可以是文件夹。对于它们,我们可以设计一个接口:

  1. public interface IComponent
  2. {
  3. int ID { get; set; }
  4. string Name { get; set; }
  5. IComponent Parent { get; set; }
  6. ICollection<IComponent> Children { get; set; }
  7. }

这种将一组对象组合在一起并生成数据结构的关系,我们称之为设计模式。设计模式的教学超出了本课程的范畴,如果你想了解更多有关设计模式的信息,推荐你看看 DoFactory。DoFactory 有所有 23 种经典设计模式的定义及其 C# 实现。

PS:设计模式是基于 SOLID 原则的。只要你遵循 SOLID 原则来抽象业务逻辑和创建领域模型,那么设计模式将不请自来。

将领域模型映射到数据库表

领域模型的另一个来源是数据库。既然我们安装了 MySQL 及其示例数据库,那我们就使用 sakila 数据库作为示例。
2.2 Domain Model 的设计 - 图1
当我们将表结构转换为模型类时,有两组关键信息:

  1. Columns tab 的列定义详细信息

  2. Information 面板的简要信息

从这两个面板我们可以得到如下信息:

  1. actor 表有四列,分别是 actor_id、first_name、last_name 和 last_update

  2. 这四列的 MySQL 数据类型分别是 smallint(5) unsigned、varchar(45)、varchar(45) 和 timestamp

  3. 表的主键是 actor_id,last_update 列的值是由触发器自动生成的

基于以上信息,我们设计的模型类如下:

  1. public partial class Actor
  2. {
  3. public int Actor_ID { get; set; }
  4. public string First_Name { get; set; }
  5. public string Last_Name { get; set; }
  6. public DateTime Last_Update { get; set; }
  7. }

为什么要这样创建领域模型类?让我们带着疑问接着往下看。

领域模型作为实体类

大多数 Web 开发框架都使用 ORM 框架将领域模型类映射到数据库表。使用 ORM 框架的好处是“业务逻辑层开发人员”可以直接操作数据库,而无需在 C# 中混合使用 SQL 语句。ORM 框架可以将数据的 CRUD 都转换为 SQL 语句,也可以将存储过程包装在类方法中。

EF Core 就是适用于 ASP.NET Core 的 ORM 框架。为了在项目中使用 EF Core,我们需要添加其 NuGet 包。将 EF Core 引用添加到项目后,我们可以使用 EF Core 的特性标注领域模型类,使其成为实体类。

修改后的模型/实体类:

  1. public partial class Actor
  2. {
  3. [Key]
  4. public int Actor_ID { get; set; }
  5. public string First_Name { get; set; }
  6. public string Last_Name { get; set; }
  7. public DateTime Last_Update { get; set; }
  8. }

更近一步,我们可以把属性名里面的下划线都去掉:

  1. [Table("actor")]
  2. public partial class Actor
  3. {
  4. [Key, Column("actor_id")]
  5. public int ActorID { get; set; }
  6. [Column("first_name")]
  7. public string FirstName { get; set; }
  8. [Column("last_name")]
  9. public string LastName { get; set; }
  10. [Column("last_update")]
  11. public DateTime LastUpdate { get; set; }
  12. }

使用部分类来分离数据与操作

你可能已经注意到了我们的模型类是部分类(partial)。那是因为我们希望将模型的操作/行为部分与数据/实体部分分开。这样做的一大好处是,你可以使用自动化工具生成模型类的数据/实体部分,而无需覆盖或删除操作/行为代码。

例如,当我们用工具重新生成 Actor 的实体类部分时,不会影响下面的代码:

  1. public partial class Actor
  2. {
  3. public IList<Film> GetFilmsInStock() {
  4. // call stored procedures
  5. }
  6. public IList<Film> GetFilmsNotInStock() {
  7. // call stored procedures
  8. }
  9. }

将数据库数据类型映射为 C# 数据类型

DBMS 通常拥有比程序框架更丰富的数据类型集。这主要是因为 DBMS 需要处理极具挑战性的数据存储和数据操作方案。若要正确的将表列映射到模型类属性,可以参考下表:

MySQL

MySQL 类型名 GetColumnClassName 的返回值 .NET Framework 类型
BIT(1) BIT bool/System.Boolean
BIT(>1) BIT byte[]
TINYINT TINYINT bool/System.Boolean if the configuration property tinyInt1isBit is set to true (the default) and the storage size is 1, or int/System.Int32 if not.
BOOL, BOOLEAN TINYINT See TINYINT, above as these are aliases for TINYINT(1), currently.
SMALLINT[(M)] [UNSIGNED] SMALLINT [UNSIGNED] int/System.Int32 (uint/System.UInt32 if it is UNSIGNED)
MEDIUMINT[(M)] [UNSIGNED] MEDIUMINT [UNSIGNED] int/System.Int32 (uint/System.UInt32 if it is UNSIGNED)
INT,INTEGER[(M)] [UNSIGNED] INTEGER [UNSIGNED] int/System.Int32, if UNSIGNED System.UInt32
BIGINT[(M)] [UNSIGNED] BIGINT [UNSIGNED] long/System.Int64, if UNSIGNED ulong/System.UInt64
FLOAT[(M,D)] FLOAT float/System.Single
DOUBLE[(M,B)] DOUBLE double/System.Double
DECIMAL[(M[,D])] DECIMAL decimal
DATE DATE System.DateTime
DATETIME DATETIME System.DateTime
TIMESTAMP[(M)] TIMESTAMP System.DateTime
TIME TIME System.DateTime
YEAR[(2 | 4)] YEAR int/System.Int32
CHAR(M) CHAR string/System.String (unless the character set for the column is BINARY, then byte[] is returned.
VARCHAR(M) [BINARY] VARCHAR string/System.String (unless the character set for the column is BINARY, then byte[] is returned.
BINARY(M) BINARY byte[]
VARBINARY(M) VARBINARY byte[]
TINYBLOB TINYBLOB byte[]
TINYTEXT VARCHAR string/System.String
BLOB BLOB byte[]
TEXT VARCHAR string/System.String
MEDIUMBLOB MEDIUMBLOB byte[]
MEDIUMTEXT VARCHAR string/System.String
LONGBLOB LONGBLOB byte[]
LONGTEXT VARCHAR string/System.String
ENUM(‘value1’,’value2’,…) CHAR string/System.String
SET(‘value1’,’value2’,…) CHAR string/System.String

SQLServer

SQL Server 数据库引擎类型 .NET Framework 类型
bigint long/System.Int64
binary byte[]/System.Byte[]
bit bool/System.Boolean
char string/System.String/char[]/System.Char[]
date/(SQL Server 2008 and later) System.DateTime
System.DateTime System.DateTime
System.DateTime2/(SQL Server 2008 and later) System.DateTime
System.DateTimeoffset/(SQL Server 2008 and later) System.DateTimeOffset
decimal/System.Decimal decimal/System.Decimal
FILESTREAM attribute (varbinary(max)) byte[]/System.Byte[]
float double/System.Double
image byte[]/System.Byte[]
int int/System.Int32
money decimal/System.Decimal
nchar string/System.String/char[]/System.Char[]
ntext string/System.String/char[]/System.Char[]
numeric decimal/System.Decimal
nvarchar string/System.String/char[]/System.Char[]
real float/System.Single
rowversion byte[]/System.Byte[]
smallSystem.DateTime System.DateTime
smallint short/System.Int16
smallmoney decimal/System.Decimal
sql_variant object/System.Object
text string/System.String/char[]/System.Char[]
time/(SQL Server 2008 and later) System.TimeSpan
timestamp byte[]/System.Byte[]
tinyint byte/System.Byte
uniqueidentifier System.Guid
varbinary byte[]/System.Byte[]
varchar string/System.String/char[]/System.Char[]
xml System.Xml