zoukankan      html  css  js  c++  java
  • 《Entity Framework 6 Recipes》中文翻译系列 (31) ------ 第六章 继承与建模高级应用之自引用关联

    翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇

    6-4  使用TPH建模自引用关系

    问题

      你有一张自引用的表,它代表数据库上不同类型但关联的对象。你想使用TPH为此表建模。

    解决方案

      假设你有一张如图6-5所示的表,它描述了关于人的事,人通常会有一个心中英雄,他最能激发自己。我们用一个指向Person表中的另一个行的引用来表示心中的英雄。

    图6-5  包含不同角色的Person表

      在现实中,每个人都会有一个角色,有的是消防员,有的是教师,有的已经退休,当然,这里可能会有更多的角色。每个人的信息会指出他们的角色。一个消防员驻扎在消防站,一位教师在学校任教。退休的人通常会有一个爱好。

      在我们示例中,可能角色有firefighter(f),teacher(t)或者retired(r)。role列用一个字符来指定人的角色。

      按下面的步骤创建一个模型:

        1、创建一个派生至DbContext的上下文对象Recipe4Context;

        2、使用代码清单6-8的代码,创建一个抽象的POCO实体Person;

    代码清单6-8. 创建一个抽象的POCO实体类Person

    [Table("Person", Schema = "Chapter6")]
        public abstract class Person
        {
            [Key]
            [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public int PersonId { get; protected set; }
            public string Name { get; set; }
    
            public virtual Person Hero { get; set; }
            public virtual ICollection<Person> Fans { get; set; } 
        }
       

        3、在上下文对象Recipe4Context中添加一个类型为DbSe<Person>的属性;

        4、使用代码清单6-9中的代码,添加具体的POCO实体类,Fierfighter,Teacher和Retired;

    代码清单6-9.创建具体的POCO实体类,Fierfighter,Teacher和Retired

     public class Firefighter : Person
        {
            public string FireStation { get; set; }
        }
    
        public class Teacher : Person
        {
            public string School { get; set; }
        }
    
        public class Retired : Person
        {
            public string FullTimeHobby { get; set; }
        }

        5、在上下文对象Recipe4Context中重写OnModelCreating方法,以此配置HeroId外键和类型层次结构。如代码清单6-10所示;

    代码清单6-10.重写OnModelCreating方法

     1  protected override void OnModelCreating(DbModelBuilder modelBuilder)
     2         {
     3             base.OnModelCreating(modelBuilder);
     4 
     5             modelBuilder.Entity<Person>()
     6                         .HasMany(p => p.Fans)
     7                         .WithOptional(p => p.Hero)
     8                         .Map(m => m.MapKey("HeroId"));
     9 
    10             modelBuilder.Entity<Person>()
    11                         .Map<Firefighter>(m => m.Requires("Role").HasValue("f"))
    12                         .Map<Teacher>(m => m.Requires("Role").HasValue("t"))
    13                         .Map<Retired>(m => m.Requires("Role").HasValue("r"));
    14         }

    原理

      代码清单6-11演示了从我们的模型中获取和插入Person实体,我们为每个派生类型创建了一个实例,并构造了一些英雄关系。我们有一位教师,它是消防员心中的英雄,一位退休的职工,他是这位教师心中的英雄。当我们把消防员设为退休职工的英雄时,我们便引入了一个循环,此时,实体框架会产生一个运行时异常(DbUpdatexception),因为它无法确定合适的顺序来将数据插入到数据库中。在代码中,我们采用在设置英雄关系之前调用SaveChanges()方法,来绕开这个问题。一旦数据提交到数据库,实体框架会把数据库中产生的键值带回到对象图中,这样我们就不会为更新关系图而付出什么代价。当然,这些更新最终仍要调用SaveChages()方法来保存。

     1  using (var context = new Recipe4Context())
     2             {
     3                 var teacher = new Teacher
     4                 {
     5                     Name = "Susan Smith",
     6                     School = "Custer Baker Middle School"
     7                 };
     8                 var firefighter = new Firefighter
     9                 {
    10                     Name = "Joel Clark",
    11                     FireStation = "Midtown"
    12                 };
    13                 var retired = new Retired
    14                 {
    15                     Name = "Joan Collins",
    16                     FullTimeHobby = "Scapbooking"
    17                 };
    18                 context.People.Add(teacher);
    19                 context.People.Add(firefighter);
    20                 context.People.Add(retired);
    21                 context.SaveChanges();
    22                 firefighter.Hero = teacher;
    23                 teacher.Hero = retired;
    24                 retired.Hero = firefighter;
    25                 context.SaveChanges();
    26             }
    27 
    28             using (var context = new Recipe4Context())
    29             {
    30                 foreach (var person in context.People)
    31                 {
    32                     if (person.Hero != null)
    33                         Console.WriteLine("
    {0}, Hero is: {1}", person.Name,
    34                                             person.Hero.Name);
    35                     else
    36                         Console.WriteLine("{0}", person.Name);
    37                     if (person is Firefighter)
    38                         Console.WriteLine("Firefighter at station {0}",
    39                                            ((Firefighter)person).FireStation);
    40                     else if (person is Teacher)
    41                         Console.WriteLine("Teacher at {0}", ((Teacher)person).School);
    42                     else if (person is Retired)
    43                         Console.WriteLine("Retired, hobby is {0}",
    44                                            ((Retired)person).FullTimeHobby);
    45                     Console.WriteLine("Fans:");
    46                     foreach (var fan in person.Fans)
    47                     {
    48                         Console.WriteLine("	{0}", fan.Name);
    49                     }
    50                 }
    51             }

    代码清单6-11的输出如下:

    Susan Smith, Hero is: Joan Collins
    Teacher at Custer Baker Middle School
    Fans:
            Joel Clark
    
    Joel Clark, Hero is: Susan Smith
    Firefighter at station Midtown
    Fans:
            Joan Collins
    
    Joan Collins, Hero is: Joel Clark
    Retired, hobby is Scapbooking
    Fans:
            Susan Smith

    6-5  使用TPH建模自引用关系

    问题

      你正在使用一张自引用的表来存储层级数据。给定一条记录,获取出所有与这关系的记录,这此记录可以是层级中任何深度的一部分。

    解决方案

      假设你有一张如图6-6所示的Category表。

    图6-6 自引用的Category表

      使用下面步骤,创建我们的模型:

        1、创建一个派生至DbContext的上下文对象Recipe5Context;

        2、使用代码清单6-12的代码,创建一个POCO实体Category;

    代码清单6-12.  创建一个POCO实体类Category

     1  [Table("Category", Schema = "Chapter6")]
     2     public class Category
     3     {
     4         [Key]
     5         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
     6         public int CategoryId { get; set; }
     7         public string Name { get; set; }
     8 
     9         public virtual Category ParentCategory { get; set; }
    10         public virtual ICollection<Category> SubCategories { get; set; }
    11     }

        3、在上下文对象Recipe5Context中添加一个类型为DbSe<Category>的属性;

        4、在上下文对象Recipe4Context中重写OnModelCreating方法,如代码清单6-13所示,我们创建了关联ParentCategory和SubCategories,并配置了外键约束。

    代码清单6-13.重写OnModelCreating方法

    1    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    2         {
    3             base.OnModelCreating(modelBuilder);
    4 
    5             modelBuilder.Entity<Category>()
    6                         .HasOptional(c => c.ParentCategory)
    7                         .WithMany(c => c.SubCategories)
    8                         .Map(m => m.MapKey("ParentCategoryId"));
    9         }

      在我们的模型中,Category实体有一个导航属性Subcategories,我们可以使用它获取到目录的直接子目录集合。然后,为了访问它们,我们需要使用方法Load()或者Include()显式加载它们。Load()方法需要额外的一次数据库交互,Include()方法只提供一个预先定义的深度确定的访问方式。

      我们需要尽可能有效地把整个层次结构全部加载到对象图中,我们采用了存储过程中的表表达式。

      按下面的步骤将存储过程添加到模型中:

        5、创建一个名为GetSubCategories的存储过程,它使用一个表表达式,通过递归为一个目录ID返回所有的子目录。存储过程如代码清单6-14所示:

    代码清单6-14. 存储过程GetSubCategories,为一个给定的目录ID返回所有的子目录

    create proc chapter6.GetSubCategories
    (@categoryid int)
    as
    begin
    with cats as
    (
    select c1.*
    from chapter6.Category c1
    where CategoryId = @categoryid
    union all
    select c2.*
    from cats join chapter6.Category c2 on cats.CategoryId = 
    c2.ParentCategoryId
    )
    select * from cats where CategoryId != @categoryid
    end

        6、在上下文对象Recipe5Context中添加一个接受整型类型参数的方法,它返回一个ICollection<Category>。如代码清单6-15所示。实体模型6中的Code First还不支持在设计器中导入存储过程。所以,在方法中,我们使用DbContext的属性Database中的SqlQuery方法。

    代码清单6-15. 在上下文对象中实现 GetSubCateories方法

    1  public ICollection<Category> GetSubCategories(int categoryId)
    2         {
    3             return this.Database.SqlQuery<Category>("exec Chapter6.GetSubCategories @catId",
    4                                                     new SqlParameter("@catId", categoryId)).ToList();
    5         }

        我们使用上下文中定义的GetSubCategoryes方法,实例化包含所有目录及子目录的对象图。 代码清单6-16演示了使用GetSubCategories()方法。

    代码清单6-16. 使用GetSubCategories()方法获取整个层次结构

     using (var context = new Recipe5Context())
                {
                    var book = new Category { Name = "Books" };
                    var fiction = new Category { Name = "Fiction", ParentCategory = book };
                    var nonfiction = new Category { Name = "Non-Fiction", ParentCategory = book };
                    var novel = new Category { Name = "Novel", ParentCategory = fiction };
                    var history = new Category { Name = "History", ParentCategory = nonfiction };
                    context.Categories.Add(novel);
                    context.Categories.Add(history);
                    context.SaveChanges();
                }
    
                using (var context = new Recipe5Context())
                {
                    var root = context.Categories.Where(o => o.Name == "Books").First();
                    Console.WriteLine("Parent category is {0}, subcategories are:", root.Name);
                    foreach (var sub in context.GetSubCategories(root.CategoryId))
                    {
                        Console.WriteLine("	{0}", sub.Name);
                    }
                }

    代码清单6-16输出如下:

    Parent category is Books, subcategories are:
            Fiction
            Non-Fiction
            History
            Novel    

    原理

      实体框架支持自引用的关联,正如我们在6.2和6.3小节中看到的那样。在这两节中,我们使用Load()方法直接加载实体引用和引用实体集合。然而,我们得小心,每一个Load()都会导致一次数据库交互才能获取实体或实体集合。对于一个大的对象图,这样会消耗很多数据库资源。

      在本节中,我们演示一种稍微不同的方法。相比Load()方法实例化每一个实体或实体集合这种方法,我们通过使用一个存储过程把工作放到数据存储层中,递归枚举所有的子目录并返回这个集合。在存储过程中,我们使用一个表表达式来实现递归查询。在我们的示例中,我们选择枚举所有的子目录。当然,你可以修改存储过程,有选择地枚举层次结构中的元素。

      为了使用这个存储过程,我们在上下文中添加了一个 通过DbContext.Database.SqlQuery<T>()调用存储过程的方法。我们使用SqlQuery<T>()而不是ExecuteSqlCommand()方法,是因为我们的存储过程要返回一个结果集。

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

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

  • 相关阅读:
    《构建之法》阅读笔记4
    《构建之法》阅读笔记3
    《构建之法》阅读笔记2
    《构建之法》阅读笔记1
    Android可折叠式菜单栏
    Material卡片式布局+下拉刷新+完整代码
    android悬浮按钮的使用
    androidStdio下载与安装以及安装过程问题解释
    html给图片划分区域添加链接
    UI进阶2-滑动菜单
  • 原文地址:https://www.cnblogs.com/VolcanoCloud/p/4532183.html
Copyright © 2011-2022 走看看