2-3 无载荷(with NO Payload)的多对多关系建模

  问题

  在数据库中,存在通过一张链接表来关联两张表的情况。链接表仅包含连接两张表形成多对多关系的外键,你需要把这两张多对多关系的表导入到实体框架模型中。

  解决方案

  我们设想,你数据库中的表与图2-10一样。

  

图2-10 艺术家和专辑多对多关系

  按下面的步骤将这些表和关系导入到模型中:

    1、右键你的项目,选择Add(增加) ➤New Item(新建项),然后选择Visual C#条目下的Data模板下的ADO.NET Entity Data Model(ADO.NET实体数据模型)。

    2、选择Generate from database 从一个已存在的数据库创建模型,点击Next(下一步)。

    3、可以选择一个已存在的数据库连接,也可以选择新建一个数据库连接。

    4、在选择数据库窗口,选择表Album,LinkTable,以及Artist。然后勾选上确定所生成对象名称的单复数形式、在模型中包含外键列复选框。点击Finish(完成)。(译注:步骤有省略,因为前面的小节已经有详细步骤)

  向导将创建如图2-11所示的模型。

图2-1 多对多关系模型

  Album和Artist之间的多对多关系被表示成两端带字符*的直线。因为一份专辑会包含多位艺术家,而一位艺术家可能负责多份专辑。Album和Artist之间的导航属性类型为EntityCollection.

原理

  在图2-11中,一个artit能关联多个albums,反之,一个album也可能关联多个artists。请注意,图2-10中的链接表没有出现在模型中。因为他没有标量属性(也就是说,它没有载荷),实体框架认为它存在的唯一目的是关联Album和Artist.如果这张链接表有标量属性,实体框架将创建一个不同的模型,如下节所示。

  代码清单2-3演示,如何在我们的模型中插入albums和artists,以及如何从模型中查询出artists和他们的albums,albums和他们的artists。

代码清单2-3. 通过模型中的多对多关系插入和查询我们的Artists和Albums

 using (var context = new EF6RecipesContext()) {

                 // 添加一个拥有两张专辑的艺术家
var artist = new Artist { FirstName = "Alan", LastName = "Jackson" };
var album1 = new Album { AlbumName = "Drive" };
var album2 = new Album { AlbumName = "Live at Texas Stadium" };
artist.Albums.Add(album1);
artist.Albums.Add(album2);
context.Artists.Add(artist); //添加两个艺术家的专辑
var artist1 = new Artist { FirstName = "Tobby", LastName = "Keith" };
var artist2 = new Artist { FirstName = "Merle", LastName = "Haggard" };
var album = new Album { AlbumName = "Honkytonk University" };
artist1.Albums.Add(album);
artist2.Albums.Add(album);
context.Albums.Add(album);
                  context.Artists.Add(artist1); //译注:需要加上下面两句(原书示例中,没有这两句),否则artist1和artist2不会保存
                  context.Artists.Add(artist2);
context.SaveChanges();
} using (var context = new EF6RecipesContext()) {
Console.WriteLine("Artists and their albums...");
var artists = context.Artists;
foreach (var artist in artists) {
Console.WriteLine("{0} {1}", artist.FirstName, artist.LastName);
foreach (var album in artist.Albums) {
Console.WriteLine("\t{0}", album.AlbumName);
}
}
Console.WriteLine("\nAlbums and their artists...");
var albums = context.Albums;
foreach (var album in albums) {
Console.WriteLine("{0}", album.AlbumName);
foreach (var artist in album.Artists) {
Console.WriteLine("\t{0} {1}", artist.FirstName, artist.LastName);
}
}
}

代码清单2-3输出:

Artists and their albums...
Alan Jackson
    Drive
    Live at Texas Stadium
Tobby Keith
    Honkytonk University
Merle Haggard
    Honkytonk University
Albums and their artists...
Drive
    Alan Jackson
Live at Texas Stadium
    Alan Jackson
Honkytonk University
    Tobby Keith
    Merle Haggard

  创建数据库上下文后,我们创建并初始化一个Artist的实例和两个Album的实例。然后将albums增加到artist的导航属性,并将artist实例添加到数据库上下文中。

  接下来,我们创建并初始化两个实体类型Artist的实例和一个实体类型Album的实例。因为两个艺术家合创一张专辑,所以,我们将album分别添加到两个atists的导航属性(它的类型为EntityCllection)中。将album添加到上下文对象,同时也会将两个artists也添加到上下文。

  现在对象图已经是上下文中的一部份,只需要调用SaveChanges()方法保存到数据库即可。

  在新的上下文对象中查询,获取艺术家并显示出他们的专辑。然后获取专辑并打印出创作他们的艺术家。

  注意,我们未提到图2-10中的链接表,其实,它没有作为一个实体出现在我们的模型中。链接表代表的多对多关联,我们通过访问Artists和Albums导航属性即可完成。

2-4 有载荷的多对多关系建模

问题

  数据库中有多对多关系的表,它们通过拥有载荷数据(除外键外的任何列)的表进行关联,你想创建一个代表这种多对多关系的模型,它将创建成两个表示一对多的关联。

解决方案

  实体框架不支持带有属性的关联,所以创建上一节那样的模型,将不能实现我们的需求。正如上一节中所说的,如果一个链接表只拥有表示关系的外键,实体框架会把链接表呈现为一个关联而不是一个实体类型。如果链接表包含额外的属性,实体框架将呈现两个单独实体类型来表示链接表。结果就是,模型中包含了两个一对多关联的实体类型来表示底层数据库中的链接表。

  假设我们拥有如图2-12所示的表及其关系

图2-12 有载荷的多对多关系

  一个订单可以拥有多个订单项,一个订单项可以属于多个订单,在连接Order、Item实例的关系上有一个Count属性,这个属性被称为一个有效载荷。

  按下面的步骤将这些表和关系导入到模型中:

    1、右键你的项目,选择Add(增加) ➤New Item(新建项),然后选择Visual C#条目下的Data模板下的ADO.NET Entity Data Model(ADO.NET实体数据模型)。

    2、选择Generate from database 从一个已存在的数据库创建模型,点击Next(下一步)。

    3、可以选择一个已存在的数据库连接,也可以选择新建一个数据库连接。

    4、在选择数据库窗口,选择表Order,OrderItem,以及Item。后勾选上确定所生成对象名称的单复数形式、在模型中包含外键列复选框。点击Finish(完成)。(译注:步骤有省略,因为前面的小节已经有详细步骤)

  向导将创建如图2-13所示的模型。

图2-13 从一个含有载荷的多对多关系建模成的两个一对多关联

原理

  正如在上一小节中所说的那样,有载荷的多对多关系,在模型中的导航简单明了。因为实体框架不支持在关联上附加载荷,链接表在模型中呈现为一个与其关联实体有两个一对多关联的实体。因此,OrderItem不是被呈现为一个关联,而是一个实体类型,该实体类型与实体类型Order有一对多的关联,与实体类型Iterm有一对多的关联。在前一小节,提到的无载荷的链接表在模型中没有被转化为实体类型,而是被转化成了多对多关联的一部份。

  因为这个附加的载荷,订单需要通过额外的一级(代表链接表的实体类型)来获取与其相关联的项。如代码清单2-4所示。

代码清单2-4 从模型插入和获取 (下面的代码有些问题,你能找出来了吗?)

  

 using (var context = new EF6RecipesContext()) {
var order = new Order {
OrderId = ,
OrderDate = new DateTime(, , )
};
var item = new Item {
SKU = ,
Description = "Backpack",
Price = 29.97M
};
var oi = new OrderItem { Order = order, Item = item, Count = };
item = new Item {
SKU = ,
Description = "Water Filter",
Price = 13.97M
};
oi = new OrderItem { Order = order, Item = item, Count = };
item = new Item {
SKU = ,
Description = "Camp Stove",
Price = 43.99M
};
oi = new OrderItem { Order = order, Item = item, Count = };
context.Orders.Add(order);
context.SaveChanges();
}
using (var context = new EF6RecipesContext()) {
foreach (var order in context.Orders) {
Console.WriteLine("Order # {0}, ordered on {1}",
order.OrderId.ToString(),
order.OrderDate.ToShortDateString()); Console.WriteLine("SKU\tDescription\tQty\tPrice");
Console.WriteLine("---\t-----------\t---\t-----");
foreach (var oi in order.OrderItems) {
Console.WriteLine("{0}\t{1}\t{2}\t{3}", oi.Item.SKU,
oi.Item.Description, oi.Count.ToString(),
oi.Item.Price.ToString("C"));
}
}
}

经网友反映,原书中的示例有问题(具体可见后面的评论),从这里可以看出实践的重要,有句话是这么说:“纸上得来终觉浅,绝知此事要躬行”。调整后的代码如下:

 using (var context = new Recipe4Context())
{
var order = new Order
{
OrderId = ,
OrderDate = new DateTime(, , )
};
var item = new Item
{
SKU = ,
Description = "Backpack",
Price = 29.97M
};
var oi1 = new OrderItem { Order = order, Item = item, Count = };
item = new Item
{
SKU = ,
Description = "Water Filter",
Price = 13.97M
};
var oi2 = new OrderItem { Order = order, Item = item, Count = };
item = new Item
{
SKU = ,
Description = "Camp Stove",
Price = 43.99M
};
var oi3 = new OrderItem { Order = order, Item = item, Count = };
context.OrderItems.Add(oi1);
context.OrderItems.Add(oi2);
context.OrderItems.Add(oi3);
context.SaveChanges();
} using (var context = new Recipe4Context())
{
foreach (var order in context.Orders)
{
Console.WriteLine("Order # {0}, ordered on {1}",
order.OrderId.ToString(),
order.OrderDate.ToShortDateString());
Console.WriteLine("SKU\tDescription\tQty\tPrice");
Console.WriteLine("---\t-----------\t---\t-----");
foreach (var oi in order.OrderItems)
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}", oi.Item.SKU,
oi.Item.Description, oi.Count.ToString(),
oi.Item.Price.ToString("C"));
}
}
} }

代码清单2-4的输入如下:

Order # , ordered on //
SKU Description Qty Price
---- ----------- --- ------
Backpack $29.97
Camp Stove $43.99
Water Filter $13.97

  创建数据库上下文实例之后,我们创建了一个订单(Order)实例、订单项(Iterm)实例、(OrderIterm)订单关联项实例,我们使用OrderItem实体的实例连接order和iterms。最后,我们调用Add()方法,将orderItem添加到上下文中。

  随着orderItem被添加到上下文中,对象图创建完成,我们调用SaveChanges()方法将其更新到数据库。

  为了从数据库中获取实体,我创建了一个新的数据库上下文对象实例,然后迭代contntx.Orders集合,对于每个订单(order)(当然,我们在示例中只有一个),通过迭代OrderItems导航属性,打印出订单的详细信息。 OrderItem实体的实例可以让我们直接访问Count标量属性(载荷),订单上的项可以通过导航属性Items访问。通过OrderItems实体获取订单项items增加了一级访问层次,这是有载荷链接表(示例中的OrderIterms)在多对多关系的代价。

最佳实践

  不幸的是,项目往往是以无载荷的多对多关系开始,却以多载荷的多对多关系结束。重构一个模型,特别是开发周期的晚期,解决这种问题特别郁闷,不光是增加一个实体,查询和关联中导航模式也要改变。因此有一些开发人建议,每一个多对多关系一开始就包含一些载荷,作为一个合成键。以此减少对项目的影响。

  所以,这里有这样一个最佳实践:如果你有一个无载荷的多对多关系时,你可以考虑通过增加一标识列将其改变为有载荷的多对多关系。当你导入表到你的模型时,你将得到两个包含一对多关系的实体,这意味着,你的代码为将来有可能出现的多载荷做好了准备。增加一整型标识列的代价通常很小,但给模型带来了更大的灵活性。

  这篇就这里吧。感谢你的阅读!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    2-5 使用Code First建模自引用关系 问题 你的数据库中一张自引用的表,你想使用Code First 将其建模成一个包含自关联的实体. 解决方案 我们假设你有如图2-14所示的数据库关系图的 ...

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

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

  8. Entity Framework 6 Code First 系列:无需修改实体和配置-在MySql中使用和SqlServer一致的并发控制

    无需修改实体和配置,在MySql中使用和SqlServer一致的并发控制.修改RowVersion类型不可取,修改为Timestamp更不可行.Sql Server的RowVersion生成一串唯一的 ...

  9. 《Entity Framework 6 Recipes》中文翻译系列 目录篇 -持续更新

    为了方便大家的阅读和学习,也是响应网友的建议,在这里为这个系列做一个目录.在目录开始这前,我先来回答之前遇到的几个问题. 1.为什么要学习EF? 这个问题很简单,项目需要.这不像学校,没人强迫你学习! ...

随机推荐

  1. python爬虫实战

    http://www.jb51.net/article/57161.htm python在线编程:http://www.pythontip.com/coding/run c语言在线编程:http:// ...

  2. html学习第三天—— 第12章——css布局模型

    清楚了CSS 盒模型的基本概念. 盒模型类型, 我们就可以深入探讨网页布局的基本模型了.布局模型与盒模型一样都是 CSS 最基本. 最核心的概念. 但布局模型是建立在盒模型基础之上,又不同于我们常说的 ...

  3. 【转】C/S,B/S区别

    C/S结构,即Client/Server(客户机/服务器)结构,是大家熟知的软件系统体系结构,通过将任务合理分配到Client端和Server端,降低了系统的通讯开销,可以充分利用两端硬件环境的优势. ...

  4. LNMP虚拟机开发环境配置--vagrant+virtualbox+ubuntu14.04

    工作一直用的是别人打包好的虚拟机开发环境,感觉确实很酷.所以准备自己配个开发环境,为之后自己开发一些有趣的东西做准备. ok,开始~~~ 一.安装软件 vagrant和virtualbox 此处需注意 ...

  5. 【滤波】标量Kalman滤波的过程分析和证明及C实现

    摘要: 标量Kalman滤波的过程分析和证明及C实现,希望能够帮助入门的小白,同时得到各位高手的指教.并不涉及其他Kalman滤波方法. 本文主要参考自<A Introduction to th ...

  6. elasticsearch-1.3.0 之索引代码粗略梳理

    elasticsearch-1.3.0 发送请求 创建 [root@centos ~]# curl -XPUT 172.16.136.159:9200/customer?pretty { " ...

  7. CentOS6.5 – Iptables配置文件

    编辑防火墙配置文件: vi /etc/sysconfig/iptables 防火墙启动: service iptables restart 如果提示不存在配置文件,那就手动添加一个. 主要配置如下: ...

  8. 伪Textatea的构建(div+table),以及相应的滚动条问题与safari上的优化

    在页面中创建一个不可编辑的文本块,并且文本块的篇幅较大,第一反应是创建一个textarea,并将它的disabled="disabled",并设置相应的scroll属性,就可以构建 ...

  9. 弱省互测#1 t3

    题意 给出一棵n个点的树,求包含1号点的第k小的连通块权值和.(\(n<=10^5\)) 分析 k小一般考虑堆... 题解 堆中关键字为\(s(x)+min(a)\),其中\(s(x)\)表示\ ...

  10. web存储

    1. cookie: 如果想将cookie取到,可以通过document.cookie;取到的是所有的cookie数据 他是一直保存在网页中的:他有一个时间的限制,如果时间过期,则删除 写入:docu ...