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

    一.AngularJs简介 1.AngularJS使用了不同的方法,它尝试去补足HTML本身在构建应用方面的缺陷.AngularJS通过使用我们称为标识符(directives)的结构,让浏览器能够识 ...

  2. [vue案例的知识点]todo-list

    文章的原材料来自于vue的官方示例:https://cn.vuejs.org/v2/examples/todomvc.html,我们在学习过程中,试着对其中的一些知识点进行记录: 一.浏览器数据存储, ...

  3. 【面试题】M

    一面: 1.介绍实习项目: 2.计算二叉树叶子节点的数量: 3.排序算法有哪些,手写快排: 4.长度为100的数组,值为1~100,乱序,将其中一个值改为0,找出被更改的值以及位置: 5.输入数值0~ ...

  4. VS2013发布网站,vs2013发布

    转自:http://www.bkjia.com/Asp_Netjc/1018876.html 本文讲解网站建好之后,如何发布在服务器上面.这也是阿辉最近遇到的问题,经过不停的查找资料终于解决了,但是有 ...

  5. .net 开发---windows服务

    因为想把quartz.net自动run的程式挂到windows服务中去,遇到问题记录 1.创建windows服务后,利用C:\Windows\Microsoft.NET\Framework\v4.0. ...

  6. WeX5学习笔记

    目录 WeX5学习笔记... 1 1.轻松看透WeX5产品能力和技术... 1 2.WeX5可以怎么玩?... 3 一.纯本地App. 3 二.关联一个网站,希望默认就打开某页... 4 三.UI设计 ...

  7. STM32之DAC君

    如花说得好:呃呃呃.是俗话说得好:有了ADC,怎可少了DAC..我觉得奇怪.今天我开头就直奔主题了.我想了想,总结了一句话:孙悟空纵然有七十二变.无论是变成猫也好,变成狗也罢.始终还是会变回他本身.所 ...

  8. PHP的数组排序函数

    <?php class order{ /** * * 数组排序 * @param array $arr 例如: * array ( array ( 'deskId' => '460646' ...

  9. [IOS]使用了cocoapods 抱错Pods was rejected as an implicit dependency for ‘libPods.a’ because its architectures ......

    Pods was rejected as an implicit dependency for ‘libPods.a’ because its architectures ‘i386’ didn’t ...

  10. String对象方法扩展

    /** *字符串-格式化 */ String.prototype.format = function(){ var args = arguments;//获取函数传递参数数组,以便在replace回调 ...