《Entity Framework 6 Recipes》中文翻译系列 (7) -----第二章 实体数据建模基础之拆分实体到多表以及拆分表到多实体
2-6 拆分实体到多表
问题
你有两张或是更多的表,他们共享一样的主键,你想将他们映射到一个单独的实体。
解决方案
让我们用图2-15所示的两张表来演示这种情况。

图 2-15,两张表,Prodeuct 和ProductWebInfo,拥有共同的主键
按下面的步骤为这两张表建模一个单独实体:
1、在你的项目中,创建一个继承至DbContext的上下文对象EF6RecipesContext;
2、使用代码清单2-8创建一个POCO实体Product;
代码清单2-8:创建一个POCO实体Product
public class Product {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int SKU { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string ImageURL { get; set; }
}
3、在EF6RecipesContext中添加类型为DbSet<Product>的属性Products;
4、使用代码清单2-9在EF6RecipesContext中重写OnModelCreating()方法;
代码清单2-9 重写OnModelCreating()方法
public class EF6RecipesContext : DbContext {
public DbSet<Product> Products { get; set; }
public EF6RecipesContext()
: base("name=EF6CodeFirstRecipesContext") {
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Product>()
.Map(m => {
m.Properties(p => new { p.SKU, p.Description, p.Price });
m.ToTable("Product", "Chapter2");
})
.Map(m => {
m.Properties(p => new { p.SKU, p.ImageURL });
m.ToTable("ProductWebInfo", "Chapter2");
});
}
}
原理
这种情况常见于遗留系统中,一个表中的每一行都包含额外的,本该属于另一张表的信息。随着数据库变化,这样情况经常发生。没有人愿意去打破现有的代码,而是通过在一个关键的表中添加一些列来解决问题。处理这种情况的答案是,建一张新表来“移植”这对额外的列。
通合并两张或多张并到一个单独的实体,通常也被叫作分拆一个实体到两张或多张数据库表,我可以把每个组成部分当成一个逻辑实体。这过程叫做垂直分拆。
垂直分拆的缺点在,我们获取实体类型实例时,分拆的表需要一个额外的join(连接)来构建实体类型。这个额外的join如清单2-10所示:
清单2-10 垂直分拆需要额外的Join连接
SELECT
[Extent1].[SKU] AS [SKU],
[Extent2].[Description] AS [Description],
[Extent2].[Price] AS [Price],
[Extent1].[ImageURL] AS [ImageURL]
FROM [dbo].[ProductWebInfo] AS [Extent1]
INNER JOIN [dbo].[Product] AS [Extent2] ON [Extent1].[SKU] = [Extent2].[SKU]
插入和获取Product实体没有特别的要求。代码清单2-11演示了操作被垂直分拆的Product实体类型
using (var context = new EF6RecipesContext()) {
var product = new Product {
SKU = ,
Description = "Expandable Hydration Pack",
Price = 19.97M,
ImageURL = "/pack147.jpg"
};
context.Products.Add(product);
product = new Product {
SKU = ,
Description = "Rugged Ranger Duffel Bag",
Price = 39.97M,
ImageURL = "/pack178.jpg"
};
context.Products.Add(product);
product = new Product {
SKU = ,
Description = "Range Field Pack",
Price = 98.97M,
ImageURL = "/noimage.jp"
};
context.Products.Add(product);
product = new Product {
SKU = ,
Description = "Small Deployment Back Pack",
Price = 29.97M,
ImageURL = "/pack202.jpg"
};
context.Products.Add(product);
context.SaveChanges();
}
using (var context = new EF6RecipesContext()) {
foreach (var p in context.Products) {
Console.WriteLine("{0} {1} {2} {3}", p.SKU, p.Description,
p.Price.ToString("C"), p.ImageURL);
}
}
代码清单2-11的输出如下:
147 Expandable Hydration Pack $19.97 /pack147.jpg
178 Rugged Ranger Duffel Bag $39.97 /pack178.jpg
186 Range Field Pack $98.97 /noimage.jpg
202 Small Deployment Back Pack $29.97 /pack202.jpg
2-7 分拆一张表到多个实体
问题
你有这样的一张数据库表,里面包含经常使用的字符,一些不常用的大字段。为了性能,需要避免每个查询都去加载这些字段。你需要将这张表分拆成两个或是更多的实体。
解决方案
我们假设你有一张如图2-16的表,它存储照片的信息,以及照片的缩略图和全分辨率图。

图2-16 Photograph表,有一个二进制的大对象字段,保存图像数据
按下面的步骤创建一个包含成本合理且经常使用列的实体,同时创建一个包含成本高且极少使用的高分辨位列的实体:
1、在你的项目中创建一个继承自DbContext的上下文对象EF6RecipesContext;
2、使用代码清单2-12创建一个POCO实体Photograph;
代理清单2-12 创建一个POCO实体Photograph
public class Photograph {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PhotoId { get; set; }
public string Title { get; set; }
public byte[] ThumbnailBits { get; set; }
[ForeignKey("PhotoId")]
public virtual PhotographFullImage PhotographFullImage { get; set; }
}
3、使用代码清单2-13创建一个POCO实体PhotographFullImage;
代理清单2-13 创建一个POCO实体PhotographFullImage
public class PhotographFullImage {
[Key]
public int PhotoId { get; set; }
public byte[] HighResolutionBits { get; set; }
[ForeignKey("PhotoId")]
public virtual Photograph Photograph { get; set; }
}
4、在上下文对象EF6RecipesContext中添加DbSet<Photograph>属性;
5、在上下文对象EF6RecipesContext中添加另一个DbSet<PhotographFullImage>属性;
6、使用代码清单2-14重写上下文对象中的OnModelCreating()方法;
代码清单2-14 重写上下文对象中的OnModelCreating()方法
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Photograph>()
.HasRequired(p => p.PhotographFullImage)
.WithRequiredPrincipal(p => p.Photograph);
modelBuilder.Entity<Photograph>().ToTable("Photograph", "Chapter2");
modelBuilder.Entity<PhotographFullImage>().ToTable("Photograph", "Chapter2");
}
原理
实体框架不直接支持延迟加载某个单一的实体属性。为了得到延迟加载成本昂贵属性的好处,利用实体框架延迟加载关联实体的特性,我们创建一个新的,包含成本昂贵的保存完整图像列的实体PhotographFullImage,一个Photograph实体和PhotographFullImange实体之单的关联。并且我们在概念层添加一个跟数据库引用约束相似的约束,告诉实体框架一个PhotographFullImage不能离开Photograph而独立存在。
由于引用约束的存在,在模型中,我们有两件需要注意的事:一个是,当我们新建一个PhotographFullImage实体的实例或者调用SaveChages()方法之前,Photogrpah的实例必须存在上下文中。第二个是,如果我删除一个photograph,与之关联的photographFullImage也会被删除,这有点像是数据库中引用约束的级联删除。
代码清单2-15 演示从模型中插入和获取数据。
代码清单2-15 插入和延迟加载成本昂贵的字段
byte[] thumbBits = new byte[];
byte[] fullBits = new byte[];
using (var context = new EF6RecipesContext()) {
var photo = new Photograph {
Title = "My Dog",
ThumbnailBits = thumbBits
};
var fullImage = new PhotographFullImage { HighResolutionBits = fullBits };
photo.PhotographFullImage = fullImage;
context.Photographs.Add(photo);
context.SaveChanges();
}
using (var context = new EF6RecipesContext()) {
foreach (var photo in context.Photographs) {
Console.WriteLine("Photo: {0}, ThumbnailSize {1} bytes",
photo.Title, photo.ThumbnailBits.Length); //显式加载存储完整图像的字段
context.Entry(photo).Reference(p => p.PhotographFullImage).Load();
Console.WriteLine("Full Image Size: {0} bytes",
photo.PhotographFullImage.HighResolutionBits.Length);
}
}
代码清单2-15的输出如下:
Photo: My Dog, Thumbnail Size: 100 bytes
Full Image Size: 2000 bytes
代码清单2-15创建并初始化实体Photograph和PhotographFullmage的实例对象,并将他们添加到上下文对象中,然后调用方法SaveChanges()保存。
在查询中,我们获取数据库中每一个photograph,打印它们的信息,并显示加载与之关系的实体PhotographFullImage。注意,我们没有关闭上下文中默认的延迟加载选项,这正是我们需要的。我们可以选择不去加载PhotographFullImage的实例,如果获取成百上千张的照片,这将为我们节约大量的时间和带宽。
本篇到此结束,如果你在坚持看本系列话,请点推荐以示支持。谢谢~
实体框架交流QQ群: 458326058,欢迎有兴趣的朋友加入一起交流
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/
《Entity Framework 6 Recipes》中文翻译系列 (7) -----第二章 实体数据建模基础之拆分实体到多表以及拆分表到多实体的更多相关文章
- 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型
第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...
- 《Entity Framework 6 Recipes》翻译系列 (5) -----第二章 实体数据建模基础之有载荷和无载荷的多对多关系建模
2-3 无载荷(with NO Payload)的多对多关系建模 问题 在数据库中,存在通过一张链接表来关联两张表的情况.链接表仅包含连接两张表形成多对多关系的外键,你需要把这两张多对多关系的表导入到 ...
- 《Entity Framework 6 Recipes》翻译系列 (4) -----第二章 实体数据建模基础之从已存在的数据库创建模型
不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多.不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题. 2-2 从已存在的数据库创 ...
- 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述
微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...
- 《Entity Framework 6 Recipes》翻译系列(2) -----第一章 开始使用实体框架之使用介绍
Visual Studio 我们在Windows平台上开发应用程序使用的工具主要是Visual Studio.这个集成开发环境已经演化了很多年,从一个简单的C++编辑器和编译器到一个高度集成.支持软件 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (8) -----第二章 实体数据建模基础之继承关系映射TPT
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 2-8 Table per Type Inheritance 建模 问题 你有这样一 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (9) -----第二章 实体数据建模基础之继承关系映射TPH
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 2-10 Table per Hierarchy Inheritance 建模 问题 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (10) -----第二章 实体数据建模基础之两实体间Is-a和Has-a关系建模、嵌入值映射
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 2-11 两实体间Is-a和Has-a关系建模 问题 你有两张有Is-a和Has-a ...
- 《Entity Framework 6 Recipes》中文翻译系列 (6) -----第二章 实体数据建模基础之使用Code First建模自引用关系
2-5 使用Code First建模自引用关系 问题 你的数据库中一张自引用的表,你想使用Code First 将其建模成一个包含自关联的实体. 解决方案 我们假设你有如图2-14所示的数据库关系图的 ...
随机推荐
- JS隐形数据类型转换注意事项
alert('2'===2); //这个结果是false,首先三个等号要比较数据类型,字符串和整数不相等 alert('2'==2); //这个结果是true,因为是两个等号,所以不会比较数据类 ...
- ExecutorService中submit()和execute()的区别
在使用java.util.concurrent下关于线程池一些类的时候,相信很多人和我一样,总是分不清submit()和execute()的区别,今天从源码方面分析总结一下. 通常,我们通过Execu ...
- Maven的配置和使用(二)
Maven仓库 使用Maven带给我们一个比较直接的好处是管理jar包,这些jar包默认地址是在C:\Users\用户名.m2 目录下,我们来修改下这个地址: 现在自己想建立的仓库地址的路径下新建文件 ...
- iOS 为键盘添加隐藏按钮
// 为键盘添加隐藏按钮 UIToolbar * backView = [[UIToolbar alloc]initWithFrame:CGRectMake(, , , )]; [backView s ...
- BZOJ1565 [NOI2009]植物大战僵尸(拓扑排序 + 最大权闭合子图)
题目 Source http://www.lydsy.com/JudgeOnline/problem.php?id=1565 Description Input Output 仅包含一个整数,表示可以 ...
- windows 共享文件夹 给 mac
假设我要共享文件夹A (1)在windows上, 共享该文件夹 (2)然后macbook 和 pc 连接同一个网络 (3)在finder选择连接服务器 (4)输入服务器地址smb://**** ...
- JavaScript中严格模式"use strict";需注意的几个雷区:
1.with语句会抛错误 2.未声明的变量被赋值会报错 3.arguments在严格模式下变为静态,传入的参数与arguments无关系 4.delete会报错 5.对象的重复属性名会报错 6.禁止八 ...
- 最大流-最小割 MAXFLOW-MINCUT ISAP
简单的叙述就不必了. 对于一个图,我们要找最大流,对于基于增广路径的算法,首先必须要建立反向边. 反向边的正确性: 我努力查找了许多资料,都没有找到理论上关于反向边正确性的证明. 但事实上,我们不难理 ...
- MVC Html.BeginForm 与 Ajax.BeginForm 使用总结
最近采用一边工作一边学习的方式使用MVC5+EF6做一个Demo项目, 期间遇到不少问题, 一直处于研究状态, 没能来得及记录. 今天项目进度告一段落, 得以有空记录学习中遇到的一些问题. 由于MVC ...
- intellij 调试spark scala 程序 报错
spark用的是cdh spark-2.0.1 package main.scala import org.apache.spark.rdd.RDD import org.apache.spark.{ ...