Code First约定-数据注释

经过实体框架Code First,可使用您本身的域类表示 EF 执行查询、更改跟踪和更新函数所依赖的模型。Code First 利用称为“约定先于配置”的编程模式。这就是说,Code First 将假定您的类听从 EF 所使用的约定。在这种状况下,EF 将可以找出本身工做所需的详细信息。可是,若是您的类不遵照这些约定,则能够向类中添加配置,以向 EF 提供它须要的信息。数据库

Code First 为您提供了两种方法来向类中添加这些配置。一种方法是使用名为 DataAnnotations 的简单特性,另外一种方法是使用 Code First 的Fluent API,该 API 向您提供了在代码中以命令方式描述配置的方法。编程

本文重点介绍如何使用DataAnnotations(在System.ComponentModel.DataAnnotations 命名空间中)对类进行配置,着重讲述经常使用的配置。不少 .NET 应用程序(如 ASP.NET MVC)都可以理解DataAnnotations,它容许这些应用程序对客户端验证使用相同的注释。数组

我将经过Blog 和 Post 这两个简单的类来讲明 Code First DataAnnotations。服务器

publicclass Blog架构

{并发

  public int Id { get; set; }app

  public string Title { get; set; }框架

  public string BloggerName { get;set; }函数

  public virtual ICollection<Post> Posts { get;set; }ui

}

publicclass Post

{

  public int Id { get; set; }

  public string Title { get; set; }

  public DateTime DateCreated { get;set; }

  public string Content { get;set; }

  public int BlogId { get; set; }

  public ICollection<Comment>Comments { get; set;}

}

Blog 和 Post 类自己就遵照 Code First 约定,无需调整便可让EF 与之共同使用。但您也可使用注释向 EF 提供有关类以及类所映射到的数据库的更多信息。

实体框架依赖于每一个具备键值的实体,它使用键值来跟踪实体。Code First 依赖的一个约定是它在每个 Code First 类中以何种方式表示哪个属性是键。该约定是查找名为“Id”或类名与“Id”组合在一块儿(如“BlogId”)的属性。该属性将映射到数据库中的主键列。

Blog 和 Post 类都遵照此约定。但若是它们不遵照呢?若是 Blog 使用名称 PrimaryTrackingKey,甚至使用 foo 呢?若是Code First 找不到符合此约定的属性,它将引起异常,由于实体框架要求必需要有一个键属性。您可使用键注释来指定要将哪个属性用做 EntityKey。

publicclass Blog

{

  [Key]

  public int PrimaryTrackingKey { get;set; }

  public string Title { get; set; }

  public string BloggerName { get;set; }

  public virtual ICollection<Post> Posts { get;set; }

}

若是您在使用Code First 的数据库生成功能,则Blog 表将具备名为 PrimaryTrackingKey 的主键列,该列默认状况下还定义为 Identity。

 

 

必需

Required 注释告诉 EF 某一个特定属性是必需的。

在 Title 属性中添加 Required 将强制 EF(和 MVC)确保该属性中包含数据。

[Required]

public string Title { get;set; }

Required 特性将使被映射的属性不可为空来影响生成的数据库。请注意,Title 字段已经更改成“not null”。

 

MaxLength 和MinLength

使用MaxLength 和MinLength 特性,您能够就像对Required 那样指定其余属性验证。

下面是具备长度要求的BloggerName。该示例也说明如何组合特性。

[MaxLength(10), MinLength(5)]

public string BloggerName { get;set; }

 

MaxLength 注释将经过把属性长度设置为 10 来影响数据库。MinLength属性不会对数据库产生影响。

 

NotMapped

Code First 约定指示具备受支持数据类型的每一个属性都要在数据库中有表示。但在您的应用程序中并不老是如此。例如,您能够在 Blog 类中使用一个属性来基于 Title 和BloggerName 字段建立代码。该属性能够动态建立,无需存储。您可使用 NotMapped 注释来标记不映射到数据库的全部属性,以下面的 BlogCode 属性。

[NotMapped]

publicstring BlogCode

{

  get

  {

    returnTitle.Substring(0, 1) + ":" +BloggerName.Substring(0, 1);

  }

}

ComplexType

跨一组类描述域实体,而后将这些类分层以描述一个完整实体的状况并很多见。例如,您能够向模型中添加一个名为 BlogDetails 的类。

publicclass BlogDetails

{

  public DateTime? DateCreated { get;set; }

 

  [MaxLength(250)]

  public string Description { get;set; }

}

请注意,BlogDetails 没有任何键属性类型。在域驱动的设计中,BlogDetails 称为值对象。实体框架将值对象称为复杂类型。复杂类型不能自行跟踪。

 

可是 BlogDetails 做为 Blog 类中的一个属性,将做为 Blog 对象的一部分被跟踪。为了让 Code First 认识到这一点,您必须将 BlogDetails 类标记为 ComplexType。

[ComplexType]

publicclass BlogDetails

{

  public DateTime? DateCreated { get;set; }

  [MaxLength(250)]

  public string Description { get;set; }

}

如今,您能够在Blog 类中添加一个属性来表示该博客的 BlogDetails。

    public BlogDetails BlogDetail { get; set; }

在数据库中,Blog表将包含该博客的全部属性,包括在其 BlogDetail 属性中所含的属性。默认状况下,每一个属性都将添加复杂类型名称前缀 BlogDetail。

 

另外,有趣的是,虽然DateCreated 属性在类中定义为不可为空的 DateTime,但相关数据库字段是可为空的。若是想影响数据库架构,则必须使用 Required 注释。

ConcurrencyCheck

ConcurrencyCheck 注释 可用于标记要在用户编辑或删除实体时用于在数据库中进行并发检查的一个或多个属性。若是以前使用 EF 设计器,则这等同于将属性的 ConcurrencyMode 设置为 Fixed。

如今让咱们将ConcurrencyCheck 添加到BloggerName 属性,看看它如何工做。

[ConcurrencyCheck, MaxLength(10),MinLength(5)]   

public  string BloggerName { get;set; }

调用SaveChanges 时,由于BloggerName 字段上具备ConcurrencyCheck 注释,因此在更新中将使用该属性的初始值。该命令将尝试经过同时依据键值和 BloggerName 的初始值进行筛选来查找正确的行。下面是发送到数据库的 UPDATE 命令的关键部分,在其中您能够看到该命令将更新 PrimaryTrackingKey 为 1 且BloggerName 为“Julie”(这是从数据库中检索到该博客时的初始值)的行。

where (([PrimaryTrackingKey]= @4) and([BloggerName] = @5))

@4=1,@5=N'Julie'

若是在此期间有人更改了该博客的博主姓名,则此更新将失败,并引起 DbUpdateConcurrencyException 而且须要处理该异常。

TimeStamp

使用rowversion 或timestamp 字段来进行并发检查更为常见。可是比起使用 ConcurrencyCheck 注释,只要属性类型为字节数组,则不如使用更为具体的 TimeStamp 注释。Code First 将Timestamp 属性与ConcurrencyCheck 属性同等对待,但它还将确保 Code First 生成的数据库字段是不可为空的。在一个指定类中,只能有一个 timestamp 属性。

将如下属性添加到Blog 类:

[Timestamp]

publicByte[] TimeStamp { get;set; }

这样,CodeFirst 将在数据库表中建立一个不可为空的 Timestamp 列。

 

表和列

若是您让Code First 建立数据库,则可能但愿更改它建立的表和列的名称。也能够将 Code First 用于现有数据库。可是域中的类和属性的名称并不老是与数据库中表和列的名称相匹配。

个人类名为Blog,按照约定,Code First 将假定此类映射到名为 Blogs 的表。若是不是这样,您能够用 Table 特性指定该表的名称。举例来讲,下面的注释指定表名称为 InternalBlogs,同时指定了schema,默认的schema就是dbo。

[Table("InternalBlogs",Schema="dbo")]

public class Blog

 

Column 注释更适于用来指定被映射列的特性。您能够规定名称、数据类型甚至列出如今表中的顺序。下面是 Column 特性的示例。

[Column("BlogDescription",TypeName = "ntext")]

public String Description { get; set; }

 

下面是从新生成后的表。表名称已更改成 InternalBlogs,复杂类型的 Description 列如今是BlogDescription。由于该名称在注释中指定,Code First 不会使用以复杂类型名称做为列名开头的约定。

 

DatabaseGenerated

一个重要的数据库功能是可使用计算属性。若是您将 Code First 类映射到包含计算列的表,则您可能不想让实体框架尝试更新这些列。可是在插入或更新数据后,您的确须要 EF 从数据库中返回这些值。您可使用 DatabaseGenerated 注释与 Computed 枚举一块儿在您的类中标注这些属性。其余枚举为 None 和Identity。

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

public DateTime DateCreated { get; set; }

当 Code First生成数据库时,您能够对 byte 或timestamp 列使用此标记,不然您只应该在指向现有数据库时使用,由于 Code First 将不能肯定计算列的公式。

您阅读过以上内容,知道默认状况下,整数键属性将成为数据库中的标识键。这与将 DatabaseGenerated 设置为 DatabaseGenerationOption.Identity 是同样的。若是不但愿它成为标识键,则能够将该值设置为 DatabaseGenerationOption.None。

关系特性:InverseProperty和ForeignKey

Code First 约定将在您的模型中处理最经常使用的关系,可是在某些状况下它须要帮助。

在 Blog 类中更改键属性的名称形成它与 Post 的关系出现问题。

生成数据库时,CodeFirst 会在 Post 类中看到 BlogId 属性并识别出该属性,按照约定,它与类名加“Id”匹配,并做为 Blog 类的外键。可是在此Blog 类中没有 BlogId 属性。解决方法是,在 Post 中建立一个导航属性,并使用 Foreign DataAnnotation 来帮助 CodeFirst 了解如何在两个类之间建立关系(那就是使用 Post.BlogId 属性)以及如何在数据库中指定约束。

publicclass Post

{

  public int Id { get; set; }

  public string Title { get; set; }

  public DateTime DateCreated { get;set; }

  public string Content { get;set; }

  public int BlogId { get; set; }

  [ForeignKey("BlogId")]

  public Blog Blog { get; set; }

}

数据库中的约束显示InternalBlogs.PrimaryTrackingKey 与Posts.BlogId 之间的关系。

 

 

类之间存在多个关系时,将使用 InverseProperty。

在 Post 类中,您可能须要跟踪是谁撰写了博客文章以及谁编辑了它。下面是 Post 类的两个新的导航属性。

public Person CreatedBy { get;set; }

public Person UpdatedBy { get;set; }

您还须要在这些属性引用的 Person 类中添加内容。Person类具备返回到 Post 的导航属性,一个属性指向该用户撰写的全部文章,一个属性指向该用户更新的全部文章。

publicclass Person

{

  public int Id { get; set; }

  public string Name { get; set; }

  public List<Post>PostsWritten { get; set;}

  public List<Post>PostsUpdated { get; set;}

}

Code First 不能自行使这两个类中的属性匹配。Posts 的数据库表应该有一个表示 CreatedBy 人员的外键,有一个表示 UpdatedBy 人员的外键,可是 Code First 将建立四个外键属性:Person_Id、Person_Id一、CreatedBy_Id 和UpdatedBy_Id。(针对每一个导航属性建立一个外键)

 

要解决这些问题,您可使用 InverseProperty 注释来指定这些属性的匹配。

[InverseProperty("CreatedBy")]

publicList<Post>PostsWritten { get; set;}

 

[InverseProperty("UpdatedBy")]

publicList<Post>PostsUpdated { get; set;}

 

由于Person 中的PostsWritten 属性知道这指的是Post 类型,因此它将与 Post.CreatedBy 创建关系。一样,PostsUpdated 也将与 Post.UpdatedBy 创建关系。Code First 不会建立额外的外键。

 

总结

DataAnnotations 不只可用于在 Code First 类中描述客户端和服务器端验证,还让您可以增强甚至更正 Code First 将基于其约定对您的类所做的假设。使用 DataAnnotations,您不只可以推进数据库架构生成,还能将 Code First 类映射到预先存在的数据库。

虽然它们都很是灵活,但请记住,DataAnnotations 只提供您常常须要对 Code First 类进行的配置更改。要为一些边缘状况配置类,则应该采用另外一种替代配置机制,那就是 Code First 的Fluent API。

相关文章
相关标签/搜索