2-5 使用Code First建模自引用关系

问题

  你的数据库中一张自引用的表,你想使用Code First 将其建模成一个包含自关联的实体。

解决方案

  我们假设你有如图2-14所示的数据库关系图的自引用表。

图2-14 一张自引用表

  按下面的步骤为这张自引用的表及关系建模:

    1、在项目中创建一个继承至DbContext上下文的类EF6RecipesContext。

    2、使用代码清单2-5创建一个PictureCategoryPOCO(简单CLR对象)实体。

      代码单清2-5 创建一个POCO实体 PictureCategory

   [Table("PictureCategory",Schema="Chapter2")]   //书中没有这一句,示例会把数据保存到dbo.PictureCategory表里,
                                //而不是Chapter2.PictureCategory里,以致不少朋友认为没有保存成功,加上这一句就不会有问题了
    public class PictureCategory {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; private set; }
public string Name { get; set; }
public int? ParentCategoryId { get; private set; }
[ForeignKey("ParentCategoryId")]
public virtual PictureCategory ParentCategory { get; set; } //书中没有virtual关键字,这会导致导航属性不能加载,后面的输出就只有根目录!!
public virtual List<PictureCategory> Subcategories { get; set; }
public PictureCategory() {
Subcategories = new List<PictureCategory>();
}
}

    3、在创建的上下文对象EF6RecipesContext中添加一个DbSet<PictureCategory>属性。

    4、在EF6RecipesContext中重写方法OnModelCreating配置双向关联(ParentCategory 和 SubCategories),如代码清单2-6所示。

      代理清单2-6 重写方法OnModelCreating

 public class EF6RecipesContext : DbContext {
public DbSet<PictureCategory> PictureCategories { get; set; }
public EF6RecipesContext() //原文这里有误,被写成PictureContext()
: base("name=EF6CodeFirstRecipesContext") {
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PictureCategory>()
.HasMany(cat => cat.SubCategories)
.WithOptional(cat => cat.ParentCategory);
}
}

    

原理
  数据库的关系有以下特征:维度(degree)、多重性(multiplicity)以及方向(derection)。维度是指关系中的实体(表)的数量。一维和二维关系是常见的。三维和N维(n-Place)关系只存在于理论上。

  多重性,是指表示关系的线段两端的实体类型(译注:这里应该是指表,因为实体类型用于模型中)数量。你可能已经看到这样的多重性表示,0...1(零或者一),1(一)和*(很多)。

  最后,方向可以是双向,也可以是单向。

  实体数据模型支持当前流行数据库的数据库关系,它通过一个名为关联的类型来表示。一个关联类型可以是一维或者二维的,多重性可以是0...1,1和*,方向是双向的。

  示例中的维度是一维(只涉及PictureCategory实体),多重性是0...1和*,方向当然是双向。

  示例中的情况,自引用表一般指父子关系,每个父亲有多个孩子,同时,一个孩子只有一个父亲。因为父亲这端的关系多重性是0...1而不是1.这对于孩子来说意味着它可能没有父亲。这正好可以被利用来表示根节点。一个没有父亲的节点,它是整个继承层次的顶端。

  代码清单2-7演示,通过递归从根节点开始枚举图片目录。当然根节点是一个没有父亲的节点。

         static void RunExample() {
using (var context = new EF6RecipesContext()) {
var louvre = new PictureCategory { Name = "Louvre" };
var child = new PictureCategory { Name = "Egyptian Antiquites" };
louvre.Subcategories.Add(child);
child = new PictureCategory { Name = "Sculptures" };
louvre.Subcategories.Add(child);
child = new PictureCategory { Name = "Paintings" };
louvre.Subcategories.Add(child);
var paris = new PictureCategory { Name = "Paris" };
paris.Subcategories.Add(louvre);
var vacation = new PictureCategory { Name = "Summer Vacation" };
vacation.Subcategories.Add(paris);
context.PictureCategories.Add(paris);
context.SaveChanges();
}
using (var context = new EF6RecipesContext()) {
var roots = context.PictureCategories.Where(c => c.ParentCategory == null);
roots.ForEach(root => Print(root, )); }
}
static void Print(PictureCategory cat, int level) {
StringBuilder sb = new StringBuilder();
Console.WriteLine("{0}{1}", sb.Append(' ', level).ToString(), cat.Name);
cat.Subcategories.ForEach(child => Print(child, level + ));
}

  代码清单2-7输出显示,根结点为Summer Vacation. 它的第一个(只有一个)孩子是Paris。 Paris有孩子Louver。最后,我在Louver照片目录访问到了目录集合。

Summer Vacation
    Paris
        Louvre
            Egyptian Antiquities
            Sculptures
            Paintings

  显然,代码稍微有点点复杂了,我们最开始创建并初始化多个实体类型的实例,通过将这些照片目录一起增加到目录louver来将它们添加进对象图,然后中我们将louver目录添加到paris目录,最后我们将paris目录添加到summer vacation目录。我们从下到上构建了整个继承体系。

  一旦调用SaveChange()方法,所有的目录都将插入到数据库,我可以查询表中的数据,看是不是所有的行都被正确地插入了。

  对于获取部分代码,我们最开始获取一个根实体,它是一个没有父亲的目录,在例中,我们创建了一个summer vacation实体,但并没有把它设置成任何实体的孩子。这让它成为整个继承体系的根节点。

  现在,从根节点开始,我们调用另一个我们编写的方法:Print(),Print()方法接受一对参数,第一个参数是一个PicturCategory实例对象,第二个参数是一个在继承体系中表示层级或深度的整型。对于根目录,summer vacation,它在继承体系的顶端,我们传0给print()方法. 方法调用会是这样 Prin(root,0)。

  在Print()方法中,我们输出目录的名称,并在名称前根据目录在继承体系所处的深度,加上相应数量的前导空格。StringBuilder类的方法Append()接受两个参数,一个是字符和一个整型,他创建一个StringBuilder实例,并附加整型参数指定数量的字符。在我们的调用中,我们使用空格和目录的深度(level)作为参数,他返回一个目录深度数量的空格字符串。我们调用StringBuilder的Sostring()方法,将StringBuilder实例转换成一个string实例。

  现在到了递归部分,我们通过children迭代孩子目录,为每一个孩子目录调用Print()方法,并确保Levle递增。 当遍历完children,我们就返回。最终的结果如前面的输出。

  在6-5中,我们会展示另一个方法,存储过程中使用表表达式,在存储端能过关系图迭代,然后返回一个扁平化的结果集。

  本篇主题到此结束,希望你有收获。 转载请注明出处。谢谢。

实体框架交流QQ群:  458326058,欢迎有兴趣的朋友加入一起交流

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

《Entity Framework 6 Recipes》中文翻译系列 (6) -----第二章 实体数据建模基础之使用Code First建模自引用关系的更多相关文章

  1. 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型

    第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...

  2. 《Entity Framework 6 Recipes》翻译系列 (5) -----第二章 实体数据建模基础之有载荷和无载荷的多对多关系建模

    2-3 无载荷(with NO Payload)的多对多关系建模 问题 在数据库中,存在通过一张链接表来关联两张表的情况.链接表仅包含连接两张表形成多对多关系的外键,你需要把这两张多对多关系的表导入到 ...

  3. 《Entity Framework 6 Recipes》翻译系列 (4) -----第二章 实体数据建模基础之从已存在的数据库创建模型

    不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多.不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题. 2-2 从已存在的数据库创 ...

  4. 《Entity Framework 6 Recipes》中文翻译系列 (8) -----第二章 实体数据建模基础之继承关系映射TPT

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 2-8 Table per Type Inheritance 建模 问题 你有这样一 ...

  5. 《Entity Framework 6 Recipes》中文翻译系列 (9) -----第二章 实体数据建模基础之继承关系映射TPH

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 2-10 Table per Hierarchy Inheritance 建模 问题 ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (10) -----第二章 实体数据建模基础之两实体间Is-a和Has-a关系建模、嵌入值映射

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 2-11 两实体间Is-a和Has-a关系建模 问题 你有两张有Is-a和Has-a ...

  7. 《Entity Framework 6 Recipes》中文翻译系列 (7) -----第二章 实体数据建模基础之拆分实体到多表以及拆分表到多实体

    2-6 拆分实体到多表 问题 你有两张或是更多的表,他们共享一样的主键,你想将他们映射到一个单独的实体. 解决方案 让我们用图2-15所示的两张表来演示这种情况. 图 2-15,两张表,Prodeuc ...

  8. 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述

    微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...

  9. 《Entity Framework 6 Recipes》翻译系列(2) -----第一章 开始使用实体框架之使用介绍

    Visual Studio 我们在Windows平台上开发应用程序使用的工具主要是Visual Studio.这个集成开发环境已经演化了很多年,从一个简单的C++编辑器和编译器到一个高度集成.支持软件 ...

随机推荐

  1. 追踪记录每笔业务操作数据改变的利器——SQLCDC

    对于大部分企业应用来用,有一个基本的功能必不可少,那就是Audit Trail或者Audit Log,中文翻译为追踪检查.审核检查或者审核记录.我们采用Audit Trail记录每一笔业务操作的基本信 ...

  2. 一些关于 checkbox的前台 jquery 操作 记录

    $(function() { //页面载入函数 var partList = jQuery.parseJSON( '${KeyWordsList}'); $.each(partList,functio ...

  3. Arc GIS engine10.2与VS2012的安装及匹配步骤

      本文章已收录于:   .embody { padding: 10px 10px 10px; margin: 0 -20px; border-bottom: solid 1px #ededed } ...

  4. Gulp常用前端流程自动化配置

    前言 近期的项目全部由Grunt + LESS 转向改用Gulp + SASS 进行前端开发,也就奔着Gulp那比较好用的自定义函数而来的. 一.package.json文件配置如下: { " ...

  5. three.js贴图

    使用图像作为材质.这时候,就需要导入图像作为纹理贴图,并添加到相应的材质中 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//E ...

  6. BZOJ4583 : 购物

    首先,如果一家店的区间完全包含了另一家,那么可以删掉另一家,中间的可以用组合数计算方案数. 那么现在将所有店按$l$排序,那么$l$和$r$都严格递增. 设$f[i][j][k]$表示当前是第$i$天 ...

  7. javascript系列:NaN类型

    NaN,即非数值是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况下. ECMAScript中任何数值除以0会返回NaN,因此不影响其他代码运行.   NaN特点:     ...

  8. 接口测试SoapUI参数化

    上次和大家一起完成了soapui的参数之一properties,今天我们一起交流另外一种参数化的方法,跟着一起练习,不懂不要紧,练习多了就会慢慢懂的: 1.准备excle(目前soapui只支持xls ...

  9. python中__init__问题

    python中安装目录中的Lib文件夹模块中的__init__.py文件(文件内容可以为空),用于标识此文件夹是一个模块 python编程中的__init__表示初始化

  10. Java的四种引用方式

    一.引用基本概念 从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期.这四种级别由高到低依次为:强引用.软引用.弱引用.虚引用. 1.强引用(StrongRef ...