翻译的初衷以及为什么选择《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方法

  protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); modelBuilder.Entity<Person>()
.HasMany(p => p.Fans)
.WithOptional(p => p.Hero)
.Map(m => m.MapKey("HeroId")); modelBuilder.Entity<Person>()
.Map<Firefighter>(m => m.Requires("Role").HasValue("f"))
.Map<Teacher>(m => m.Requires("Role").HasValue("t"))
.Map<Retired>(m => m.Requires("Role").HasValue("r"));
}

原理

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

  using (var context = new Recipe4Context())
{
var teacher = new Teacher
{
Name = "Susan Smith",
School = "Custer Baker Middle School"
};
var firefighter = new Firefighter
{
Name = "Joel Clark",
FireStation = "Midtown"
};
var retired = new Retired
{
Name = "Joan Collins",
FullTimeHobby = "Scapbooking"
};
context.People.Add(teacher);
context.People.Add(firefighter);
context.People.Add(retired);
context.SaveChanges();
firefighter.Hero = teacher;
teacher.Hero = retired;
retired.Hero = firefighter;
context.SaveChanges();
} using (var context = new Recipe4Context())
{
foreach (var person in context.People)
{
if (person.Hero != null)
Console.WriteLine("\n{0}, Hero is: {1}", person.Name,
person.Hero.Name);
else
Console.WriteLine("{0}", person.Name);
if (person is Firefighter)
Console.WriteLine("Firefighter at station {0}",
((Firefighter)person).FireStation);
else if (person is Teacher)
Console.WriteLine("Teacher at {0}", ((Teacher)person).School);
else if (person is Retired)
Console.WriteLine("Retired, hobby is {0}",
((Retired)person).FullTimeHobby);
Console.WriteLine("Fans:");
foreach (var fan in person.Fans)
{
Console.WriteLine("\t{0}", fan.Name);
}
}
}

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

  [Table("Category", Schema = "Chapter6")]
public class Category
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; set; }
public string Name { get; set; } public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> SubCategories { get; set; }
}

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

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

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

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); modelBuilder.Entity<Category>()
.HasOptional(c => c.ParentCategory)
.WithMany(c => c.SubCategories)
.Map(m => m.MapKey("ParentCategoryId"));
}

  在我们的模型中,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方法

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

    我们使用上下文中定义的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("\t{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/

《Entity Framework 6 Recipes》中文翻译系列 (31) ------ 第六章 继承与建模高级应用之自引用关联的更多相关文章

  1. 《Entity Framework 6 Recipes》中文翻译系列 (37) ------ 第六章 继承与建模高级应用之独立关联与外键关联

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-13  在基类中应用条件 问题 你想从一个已存在的模型中的实体派生一个新的实体, ...

  2. 《Entity Framework 6 Recipes》中文翻译系列 (30) ------ 第六章 继承与建模高级应用之多对多关联

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第六章  继承与建模高级应用 现在,你应该对实体框架中基本的建模有了一定的了解,本章 ...

  3. 《Entity Framework 6 Recipes》中文翻译系列 (32) ------ 第六章 继承与建模高级应用之TPH与TPT (1)

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-6  映射派生类中的NULL条件 问题 你的表中,有一列允许为null.你想使用 ...

  4. 《Entity Framework 6 Recipes》中文翻译系列 (33) ------ 第六章 继承与建模高级应用之TPH与TPT (2)

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-8  嵌套的TPH建模 问题 你想使用超过一层的TPH继承映射为一张表建模. 解 ...

  5. 《Entity Framework 6 Recipes》中文翻译系列 (34) ------ 第六章 继承与建模高级应用之多条件与QueryView

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-10  创建一个多条件过滤 问题 你想使用多个条件为实体过滤表中的行. 解决方案 ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (35) ------ 第六章 继承与建模高级应用之TPH继承映射中使用复合条件

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-11  TPH继承映射中使用复合条件 问题 你想使用TPH为一张表建模,建模中使 ...

  7. 《Entity Framework 6 Recipes》中文翻译系列 (36) ------ 第六章 继承与建模高级应用之TPC继承映射

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-12  TPC继承映射建模 问题 你有两张或多张架构和数据类似的表,你想使用TP ...

  8. 《Entity Framework 6 Recipes》中文翻译系列 (27) ------ 第五章 加载实体和导航属性之关联实体过滤、排序、执行聚合操作

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-9  关联实体过滤和排序 问题 你有一实体的实例,你想加载应用了过滤和排序的相关 ...

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

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

随机推荐

  1. <HTML>菜鸟入门基础须知

    将持续更新-- 一,基础常用标签and属性 既然要学习这门知识,那必须得先知道这是什么能做什么,HTML:是一种超文本标记语言,什么意思呢,我拆开看一下,超(超链接)文本(犹如TXT)标记(改变成自己 ...

  2. Scrum项目7.0

    队友: 郭志豪:http://www.cnblogs.com/gzh13692021053/ 杨子健:http://www.cnblogs.com/yzj666/ 刘森松:http://www.cnb ...

  3. 【oracle】union、union all、intersect、minus 的用法及区别

    一.union与union all 首先建两个view create or replace view test_view_1 as as c from dual union as c from dua ...

  4. [VijosP1656]萌萌赶考 题解

    题目大意: 有一个地图,有障碍,不能重复经过一点(但起点可以),判断能否恰好在t时刻从起点到达终点. 思路: 一开始DFS一遍,30分,于是要有优化减枝.最重要的是从起点到终点的距离的奇偶性是与起点与 ...

  5. WP8解析JSON格式(使用DataContractJsonSerializer类)(推荐)

    DataContractJsonSerializer是.NET自带的类,在解析JSON格式的时候使用起来方便快捷,至于生成方面由于暂时没用到就没去看了.使用需要引用System.Runtime.Ser ...

  6. HTTP 使用期及新鲜度算法

    使用期算法: /* * age_value 当代理服务器用自己的头部去响应请求时,Age标明实体产生到现在多长时间了. * date_value HTTP 服务器应答中的Date字段 原始服务器 * ...

  7. Exchange环境搭建心得

    不知道是思维模式的问题,还是智商不够用,从3月1日开始准备自学Exchange,到今天还是有好多东西稀里糊涂的.不说别的,就搭建环境,前期的报错折腾了好一阵.现在回顾一下,用Server08R2,一台 ...

  8. 【第三课】WEBIX 入门自学-Hello World

    在看官网教程时,入门的例子就是dataTable这个空间.So,遵循官网,一起来看一下入门的DataTable组件: WEB使用时固然是先引入相应的库文件: 代码如下 <html> < ...

  9. LintCode 77: 最长公共子序列

    public class Solution { /** * @param A, B: Two string. * @return: the length of the longest common s ...

  10. 扩展BindingList,防止增加、删除项时自动更新界面而不出现“跨线程操作界面控件 corss thread operation”异常

    在做界面程序时,常常需要一些数据类,界面元素通过绑定等方式显示出数据,然而由于UI线程不是线程安全的,一般都需要通过Invoke等方式来调用界面控件.但对于数据绑定bindingList而言,没法响应 ...