zoukankan      html  css  js  c++  java
  • 《Entity Framework 6 Recipes》中文翻译系列 (6) 第二章 实体数据建模基础之使用Code First建模自引用关系

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

    问题

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

    解决方案

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

    图2-14 一张自引用表

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

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

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

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

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

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

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

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

     1 public class EF6RecipesContext : DbContext {
     2         public DbSet<PictureCategory> PictureCategories { get; set; }
     3         public EF6RecipesContext()   //原文这里有误,被写成PictureContext()
     4             : base("name=EF6CodeFirstRecipesContext") {
     5         }
     6         protected override void OnModelCreating(DbModelBuilder modelBuilder) {
     7             base.OnModelCreating(modelBuilder);
     8             modelBuilder.Entity<PictureCategory>()
     9             .HasMany(cat => cat.SubCategories)
    10             .WithOptional(cat => cat.ParentCategory);
    11         }
    12     }

        

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

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

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

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

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

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

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

     1         static void RunExample() {
     2             using (var context = new EF6RecipesContext()) {
     3                 var louvre = new PictureCategory { Name = "Louvre" };
     4                 var child = new PictureCategory { Name = "Egyptian Antiquites" };
     5                 louvre.Subcategories.Add(child);
     6                 child = new PictureCategory { Name = "Sculptures" };
     7                 louvre.Subcategories.Add(child);
     8                 child = new PictureCategory { Name = "Paintings" };
     9                 louvre.Subcategories.Add(child);
    10                 var paris = new PictureCategory { Name = "Paris" };
    11                 paris.Subcategories.Add(louvre);
    12                 var vacation = new PictureCategory { Name = "Summer Vacation" };
    13                 vacation.Subcategories.Add(paris);
    14                 context.PictureCategories.Add(paris);
    15                 context.SaveChanges();
    16             }
    17             using (var context = new EF6RecipesContext()) {
    18                 var roots = context.PictureCategories.Where(c => c.ParentCategory == null);
    19                 roots.ForEach(root => Print(root, 0));              }
    21         }
    22         static void Print(PictureCategory cat, int level) {
    23             StringBuilder sb = new StringBuilder();
    24             Console.WriteLine("{0}{1}", sb.Append(' ', level).ToString(), cat.Name);
    25             cat.Subcategories.ForEach(child => Print(child, level + 1));
    26         }

       代码清单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/

    版权所有,转载请注明出处 付灿的技术博客
  • 相关阅读:
    主机连接不上虚拟机或虚拟机桥接没有网络
    asp web 报表
    heart or house?
    CPointer
    Raid
    Dos for by 随风
    不支持uri格式
    FabEdge V0.4 新特性:支持多集群通讯
    运维监控
    /etc缩写
  • 原文地址:https://www.cnblogs.com/VolcanoCloud/p/4487504.html
Copyright © 2011-2022 走看看