通过实体框架 Code First,可以使用您自己的域类表示 EF 执行查询、更改跟踪和更新函数所依赖的模型。Code First 利用称为“约定先于配置”的编程模式。这意味着 Code First 将假设类遵循 EF 用于概念模型的架构约定。在这种情况下,EF 将能够找出自己工作所需的详细信息。但是,如果您的类不遵守这些约定,则可以向类中添加配置,以向 EF 提供它需要的信息。

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

本文将重点讨论如何在 Fluent API 中优化关系。Code First 约定非常适合根据指向子集合或单个类的属性识别类之间的常见关系。当类不使用外键时,Code First 可以推断数据库外键。但有时类提供的信息不足,Code First 无法正常处理这些关系。

介绍模型

我将从两个简单的类 Blog 和 Post 开始,这里 Blog 与 Post 有一对多关系。

public class Blog      {          public int Id { get; set; }          public string Title { get; set; }          public string BloggerName { get; set; }          public virtual ICollection<Post> Posts { get; set; }      }      public class 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 Blog Blog { get; set; }      }

了解一对多关系的约定

在类中定义一对多关系的一种常见方法是在一个类中包含一个子集合,然后在子类中包含一个外键属性和一个导航属性。在上面的示例中,Blog 有一个 Posts 属性,它是 Post 类型的 ICollection。Post 有一个外键 BlogID 和一个导航属性 Blog,该导航属性指回其父 Blog。

此设置符合 Code First 约定的预期,因此,Code First 将创建以下数据库表:

图 1

请注意,Code First 将 BlogId 用作数据库外键(Posts.BlogId 和 Blogs.Id 之间定义了主键/外键约束),该值不可为 Null。这是 Code First 约定根据类确定的。

没有外键属性时使用 HasRequired 提供帮助

如果 Post 类中没有 BlogId 属性但有导航属性 Blog,该怎么办呢?Code first 仍然能够创建关系,因为它知道这一 Blog 属性指回 Blog 实体。因此它仍然会创建图 2 中所示的数据库外键 Posts.Blog_Id,以及将其链接到 Blog.Id 的约束。

图 2

但有一个重要区别,Blog_Id 可以为 Null。可以添加与 Blog 绑定的 Posts。Code First 约定就是这样解释类的,但这可能并不符合您的意图。您可以使用 Fluent API 进行修复。

Code First 从类中构建模型时,将应用 Fluent API 配置。通过重写这里显示的 DbContext 类的 OnModelCreating 方法,可以注入配置。

public class BlogContext : DbContext       {          public DbSet<Blog> Blogs { get; set; }          public DbSet<Post> Posts { get; set; }          protected override void OnModelCreating(DbModelBuilder modelBuilder)          {           //使用 Fluent API 配置模型          }

DbModelBuilder 提供了一个配置挂钩。在这里,我们可以告诉模型生成器我们需要影响其中一个实体,您可以使用泛型指定是哪个实体,这里是 Post。访问它后,可以使用 HasRequired 方法(特定于关系)指定需要一个导航属性,在本例中为 Blog 属性。

modelBuilder.Entity<Post>().HasRequired(p => p.Blog);

这会产生两方面影响。首先,数据库中的 Blog_Id 将再次变为不可为 Null。实体框架仍将根据需要或在将更改保存回数据库前执行验证,以确保满足此要求。

配置非常规外键名称

类中有外键时,属性名称必须始终符合 Code First 约定。约定是:键是它所指向的类的名称(例如 Blog)和 _Id 或 Id 的组合。这就是 Code First 能够使用类 BlogId 中的原始属性的原因。

如果属性不符合约定,会怎么样呢?也许您使用了“FK”+ 父级 +“Id”?

public int FKBlogId { get; set; }

Code first 根本不知道 FKBlogId 应是外键。它将创建一个标准列来显示该属性,还将创建 Blog_Id 外键,因为它根据 Blog 属性确定需要这个外键。

图 3

此外,在代码中使用 Blog 和 Post 时,根本不会将 FKBlogId 识别为指回 Blog 的外键。如果有修复双向关系的代码,它将不会使用 FKBlogId 属性执行其任务。

可以使用 Fluent API 修复此问题,告诉 Code First 您的真实意图,使用 FKBlogId 作为与 Blog 的关系中的外键属性。可以从现成的配置开始。

在此配置中,将定义关系的两端:Blog 中指向多关系 (Posts) 的属性以及 Post 中指回父级 (Blog) 的属性。

首先需要添加 WithMany 方法,使用该方法可以指示 Blog 中的哪个属性包含 Many 关系。

modelBuilder.Entity<Post>().HasRequired(p => p.Blog)                  .WithMany(b => b.Posts)

然后可以向其添加 HasForeignKey 方法,指示 Post 的哪个属性是指回 Blog 的外键。最后,完整的映射如下:

modelBuilder.Entity<Post>().HasRequired(p => p.Blog)                  .WithMany(b => b.Posts)                  .HasForeignKey(p => p.FKBlogId);

现在,Code First 获得了需要的信息,能够创建正确的数据库架构(或正确映射到现有数据库),可以在与双向关系有关的应用程序中提供预期的行为。

图 4

在多对多关系中定义联接表架构

在类中,可以通过指向彼此的属性方便地描述多对多关系。例如,如果在模型中添加了一个 Tag 类来跟踪文章的标记,您需要它们之间有多对多关系。

下面是新的 Tag 类:

public class Tag      {          public int TagId{ get; set; }          public string Name { get; set; }          public ICollection<Post> Posts { get; set; }      }

下面是为 Post 类添加的新属性:

public ICollection<Tag> Tags { get; set; }

Code first 需要联接表的命名是将这两个类的名称组合在一起并包含外键属性,其中每个属性都是类名称和键属性的组合。在本例中,我使用 Post.Id 和 Tag.TagId。如果使用 Code First 创建数据库,使用 Code First 约定的表应类似下面这样:

图 5

使用 Code First 构建数据库时,这可能不是问题。但如果映射到现有数据库,这样的命名可能根本无法对齐表。可以使用 Fluent API 描述表名和两个列名,可以显式命名一个、两个或全部三个。

下面介绍如何实现此映射。我们使用示例,其中需要定义所有三个名称。表应为 PostJoinTag,列应为 TagId 和 PostId。

您需要从 Entity 映射方法开始,可以选择从 Post 或 Tag 开始。这里我将使用 Post。然后需要指定关系的两端。与在上例中指定一对多关系的方式类似,可以使用 HasMany 和 WithMany 进行。在这里,我通过 Post 实体的 Tags 属性指示它具有“多”关系,因此,Tag 通过其 Posts 属性与其“多”关系有关系。

modelBuilder.Entity<Post>()                  .HasMany(p => p.Tags)                  .WithMany(t => t.Posts)                  .Map(mc =>                     {                         mc.ToTable("PostJoinTag");                         mc.MapLeftKey("PostId");                         mc.MapRightKey("TagId");                     });

图 6

指定哪个是 MapLeftKey 和哪个是 MapRightKey 时需要小心。左键应为所指向的第一个类的键,即 Post,右键为关系的另一侧。如果搞反了方向,数据将不能正确存储,用户会很迷惑。

摘要

您已经了解了使用 Code First 的关系 Fluent API 可以描述的关系映射的一些可能情况。这里我用一对多关系和多对多关系修复了约定对我意图的误解。您还可以使用其他映射,可以单独使用或组合使用。尽管它们开始看起来可能令人迷惑和多余,例如 IsRequired 和 HasRequired 或 WithMany 和 HasMany。但现在您已经了解到这些映射有非常明确的作用,它们的不同是有原因的。

请查阅实体框架 4.1 MSDN 文档  http://msdn.microsoft.com/library/gg696172(v=VS.10).aspx 和实体框架团队博客 ( http://blogs.msdn.com/adonet ),了解可以使用 Fluent API 实现的更多关系映射。

Code First 关系 Fluent API的更多相关文章

  1. Code First约定-Fluent API配置

    转自:http://blog.163.com/m13864039250_1/blog/static/2138652482015283397609/ 用Fluent API 配置/映射属性和类型 简介 ...

  2. code First 三 Fluent API

    Entity Framework Fluent API用于配置域类以覆盖约定. 在实体框架6中,DbModelBuilder类充当Fluent API,我们可以使用它来配置许多不同的东西.它提供了比数 ...

  3. 关于CodeFirst异常:无法确定类型'XXX'和类型‘YYY’之间的关联的主体端,必须使用关系 Fluent API 或数据注释显式配置此关联的主体端。

    此错误的原因是,你配置两个实体间的关系为一对一 然而我认为的一对一关系是,两者之间必须存在一个主体, 也就是说,你不能表1的外键是表2的主键并且表1的主键是表2的外键, 这样不符合数据库式吧? 我想多 ...

  4. Code First:Fluent API

    DbContext类有一个OnModelCreating方法,可以在这里配置模型,该方法接收一个类型为DbModelBuilder的建造者,本文介绍的为Data Anotation的等价方法,这些代码 ...

  5. 1.【使用EF Code-First方式和Fluent API来探讨EF中的关系】

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/relationship-in-entity-framework-using-code-firs ...

  6. Entity Framework Code First关系映射约定

    本篇随笔目录: 1.外键列名默认约定 2.一对多关系 3.一对一关系 4.多对多关系 5.一对多自反关系 6.多对多自反关系 在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的.两个 ...

  7. 使用Fluent API 配置/映射属性和类型

    Code First约定-Fluent API配置 使用Fluent API 配置/映射属性和类型 简介 通常通过重写派生DbContext 上的OnModelCreating 方法来访问Code F ...

  8. Entity Framework Code First关系映射约定【l转发】

    本篇随笔目录: 1.外键列名默认约定 2.一对多关系 3.一对一关系 4.多对多关系 5.一对多自反关系 6.多对多自反关系 在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的.两个 ...

  9. Fluent API 配置

    EF里实体关系配置的方法,有两种: Data Annotation方式配置 也可以 Fluent API 方式配置 Fluent API 配置的方法 EF里的实体关系 Fluent API 配置分为H ...

随机推荐

  1. java内存空间详解

    Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识.一般 ...

  2. Lyaer 单弹出层获取数据

    案例完整代码如下 var cls = layer.open({                title: "请选择被换班人",                type: 2,   ...

  3. 【C++实现python字符串函数库】一:分割函数:split、rsplit

    [C++实现python字符串函数库]split()与rsplit()方法 前言 本系列文章将介绍python提供的字符串函数,并尝试使用C++来实现这些函数.这些C++函数在这里做单独的分析,最后我 ...

  4. Leetcode 131. Palindrome Partitioning

    Given a string s, partition s such that every substring of the partition is a palindrome. Return all ...

  5. 【BZOJ-1565】植物大战僵尸 拓扑排序 + 最小割

    1565: [NOI2009]植物大战僵尸 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1972  Solved: 917[Submit][Statu ...

  6. 【poj1985】 Cow Marathon

    http://poj.org/problem?id=1985 (题目链接) 题意 求树上两点间最长距离.题目背景以及输入描述请见poj1984. Solution 树的直径. 代码 // poj198 ...

  7. 更改codeblocks的配色方案

    codeblocks默认只有一种配色方案, 不过我们可以手动添加. 在终端下输入如下命令: cd ~/.codeblocks sudo gedit default.conf 在打开的配置文件中, 找到 ...

  8. lua中的table、stack和registery

    ok,前面准备给一个dll写wrapper,写了篇日志,看似写的比较明白了,但是其实有很多米有弄明白的.比如PIL中使用的element,key,tname,field这些,还是比较容易混淆的.今天正 ...

  9. java循环遍历map

    import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class MapTest { pu ...

  10. RabbitMQ Exchange中的fanout类型

    fanout 多播 在之前都是使用direct直连类型的交换机,通过routingkey来决定把消息推到哪个queue中. 而fanout则是把拿到消息推到与之绑定的所有queue中. 分析业务,怎样 ...