3.9.- 领域实体的”数据注解“
目前为止我们让EF使用默认的协定发现模型,但有时候当我们的类不按照协定,我们需要进行更多的配置。
就像我们提到的,有两个选择;我们先看数据注解然后看”Fluent API“。
假设我们的BankAccount实体的Id属性不是”BankAccountId‟而是叫做“BankAccountNumber‟的属性。我们
试着运行应用但会得到InvalidOperationException,提示实体类型'BankAccount'没有定义键。因为EF不知道
”BankAccountNumber‟应该是主键。
为了解决这个问题,这里我们使用“数据注解”,需要添加一个引用:
项目 -> 添加引用...
选择 .NET 选项卡
选择 “System.ComponentModel.DataAnnotations”
点击 确定
在.cs文件顶添加using声明:
using System.ComponentModel.DataAnnotations;
考虑到这个命名空间不是EF而是.NET框架的一部分,“数据注解“也可以用在其他的技术,不只是EF。
如果EF需要其他的属性,可以在EntityFramework.dll找到,但是之后,这一步对持久化透明不利。
现在我们在”BankAccountNumber‟属性上加上主键的注解:
目前为止我们让EF使用默认的协定发现模型,但有时候当我们的类不按照协定,我们需要进行更多的配置。
就像我们提到的,有两个选择;我们先看数据注解然后看”Fluent API“。
假设我们的BankAccount实体的Id属性不是”BankAccountId‟而是叫做“BankAccountNumber‟的属性。我们
试着运行应用但会得到InvalidOperationException,提示实体类型'BankAccount'没有定义键。因为EF不知道
”BankAccountNumber‟应该是主键。
为了解决这个问题,这里我们使用“数据注解”,需要添加一个引用:
项目 -> 添加引用...
选择 .NET 选项卡
选择 “System.ComponentModel.DataAnnotations”
点击 确定
在.cs文件顶添加using声明:
using System.ComponentModel.DataAnnotations;
考虑到这个命名空间不是EF而是.NET框架的一部分,“数据注解“也可以用在其他的技术,不只是EF。
如果EF需要其他的属性,可以在EntityFramework.dll找到,但是之后,这一步对持久化透明不利。
现在我们在”BankAccountNumber‟属性上加上主键的注解:
//POCO Domain Entity using ‘Data Annotations’
//
public class BankAccount : Entity
{
//Attributes
//
[Key]
public int BankAccountNumber { get; set; }
public string BankAccountNumber { get; set; }
public decimal Balance { get; set; }
public int CustomerId { get; set; }
public bool Locked { get; set; }
//
//Domain Entity Logic
//
...
...
...
}
现在,我们可以使用BankAccountNumber作为数据库的主键。
注意:“数据注解”不是EF中的新概念。我们也可以使用在ASP.NET Dynamic Data 和“„WCF RIA Services‟中。
实际上,这些技术也使用相同的程序集和命名空间:System.ComponentModel.DataAnnotations
有很多其他的数据注解属性。下面展示了数据注解类的所有属性:
3.10.- 实体验证
从EF 4.1开始,这是EF第一次提供实体的验证。这个主意可能看起来很简单,但其实不是,它的实现对于
大多数从头开始的项目有很大的影响。
区分实体模型的验证和业务概念的验证很重要。一般来说,实体模型验证可以省去很多不必要的返工。
实体可以由几种方法实现,下面是主要的:
- 数据注解(EF)
- Fluent API (EF)
- 实现IValidatableObject
3.10.1.- 使用实体验证的数据注解
与其他任何主题的一样,尽管数据注解非常有吸引力,这也是一种使实体不纯净的方法,所以在领域驱动
设计中我们不推荐使用数据注解。我们推荐使用IValidatableObject或者“Fluent API‟。
下面是一个使用数据注解的一个实体示例:
这段代码的问题是当我们使用在EF程序集中定义的属性时,这样我们的实体就不是持久化透明了。
另一方面,如果我们使用.NET程序集中的属性数据注解时,就没有对EF的直接依赖。
如果我们不想再验证属性中有EF或MVC的依赖,我们会使用派生自ValidationAttribute的属性。
所以我们可以用下面的属性:
表 14.- System.ComponentModel.DataAnnotations 验证注解
验证属性 目的
StringLengthAttribute 字符串的最大曾度
RequiredAttribute 必须的元素
RegularExpressionAttribute 需要匹配指定的正则表达式
RangeAttribute 检查在一定区间的值
DataTypeAttribute 指定关联一个字段的额外类型的名字
CustomValidationAttribute 自定义验证
CustomValidationAttribute提供了自定义验证的代理;因此这会是领域没有外部依赖的一部分。
例如,下面我们定义信用卡号的自定义验证。
从EF 4.1开始,这是EF第一次提供实体的验证。这个主意可能看起来很简单,但其实不是,它的实现对于
大多数从头开始的项目有很大的影响。
区分实体模型的验证和业务概念的验证很重要。一般来说,实体模型验证可以省去很多不必要的返工。
实体可以由几种方法实现,下面是主要的:
- 数据注解(EF)
- Fluent API (EF)
- 实现IValidatableObject
3.10.1.- 使用实体验证的数据注解
与其他任何主题的一样,尽管数据注解非常有吸引力,这也是一种使实体不纯净的方法,所以在领域驱动
设计中我们不推荐使用数据注解。我们推荐使用IValidatableObject或者“Fluent API‟。
下面是一个使用数据注解的一个实体示例:
//Initial Customer Entity
//
public class Customer : Entity
{
public int CustomerId { get; set; }
[Required()]
[MaxLength(20)]
public string FirstName { get; set; }
[Required()]
[StringLength(20)]
public string LastName { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string ZipCode { get; set; }
}
这段代码的问题是当我们使用在EF程序集中定义的属性时,这样我们的实体就不是持久化透明了。
另一方面,如果我们使用.NET程序集中的属性数据注解时,就没有对EF的直接依赖。
如果我们不想再验证属性中有EF或MVC的依赖,我们会使用派生自ValidationAttribute的属性。
所以我们可以用下面的属性:
表 14.- System.ComponentModel.DataAnnotations 验证注解
验证属性 目的
StringLengthAttribute 字符串的最大曾度
RequiredAttribute 必须的元素
RegularExpressionAttribute 需要匹配指定的正则表达式
RangeAttribute 检查在一定区间的值
DataTypeAttribute 指定关联一个字段的额外类型的名字
CustomValidationAttribute 自定义验证
CustomValidationAttribute提供了自定义验证的代理;因此这会是领域没有外部依赖的一部分。
例如,下面我们定义信用卡号的自定义验证。
public static ValidationResult ValidateCCNumber(string creditCardNumber)
{
bool result;
//TODO: Validate DNI
... CC validation algorithm ...
...
if (!result)
{
return new ValidationResult("Invalid CC number", new string[] { "CC" });
}
else
return null;
}
然后,我们会这样修改实体。
[CustomValidation(typeof(OrderValidation),"ValidateCCNumber")]
public string CreditCardNumber { get; set; }
总的来说,最解耦的方法是在领域模型层使用IvalidatableObject或在数据访问层实现”Fluent API‟。
3.10.2.- 使用IValidatableObject实体验证
这个方法是非常持久化透明的,在领域模型层的验证代码没有外部依赖。
该方法尤其适用于需要验证多个实体状态的业务验证。通常,这种验证视为“类级别验证”。
为了实现这样的验证,EF产品组给予POCO代码优先实体IValidatableObject的支持,所以我们的实体可以
实现该接口来做更复杂的验证。
对于这样验证重要的一点是只有在没有"注解级别“错误时才会执行。
下面的代码展示了实现IvalidatableObject的验证
public abstract class Product
:Entity,IValidatableObject
{
//...
//Ommitted Product Entity properties and methods
//...
//...
public IEnumerable<ValidationResult> Validate(ValidationContext
validationContext)
{
var validationResults = new List<ValidationResult>();
if (String.IsNullOrEmpty(Title) || String.IsNullOrWhiteSpace(Title))
validationResults.Add(new ValidationResult(Messages.
validation_ProductTitleCannotBeNullOrEmpty,
new string[] { "Title" }));
if (String.IsNullOrEmpty(Description)|| String.
IsNullOrWhiteSpace(Description))
validationResults.Add(new ValidationResult(
Messages.validation_ProductDescriptionCannotBeNullOrEmpty,
new string[] { "Description" }));
if (AmountInStock < 0)
validationResults.Add(new ValidationResult(Messages.
validation_ProductAmountLessThanZero,
new string[] { "AmountInStock" }));
if (UnitPrice < 0)
validationResults.Add(new ValidationResult(Messages.
validation_ProductUnitPriceLessThanZero,
new string[] { "UnitPrice" }));
return validationResults;
}
}