CodeFirst与EntityFramework【续】
在介绍一对多关系和多对多关系时,大家应该已经注意到了只要存在依赖关系的两个类的定义中包含对方的实例或实例的集合,Entity Framework Code First会自动推断出与之对应的数据库关系。这个方式对一对一关系也同样适用吗?先让我们来作一个实验。
假设我们的订单系统现在需要存储每个客户的银行账号信息。显然,在我们的订单系统中,银行账号并不是我们关注的重点,我只需要保存账号的号码,开户行以及账号名称,由此可见银行账号在我们这里只是一个值对象(Value Object)。
我们需要定义银行账号类:
public class BankAccount
{
public string AccountNumber { get; set; }
public DateTime CreatedDate { get; set; }
public string BankName { get; set; }
public string AccountName { get; set; }
}
接着,我们还需要在客户类当中包含一个银行账号类的实例:
public class Customer
{
public string IDCardNumber { get; set; }
public string CustomerName { get; set; }
public string Gender { get; set; }
public Address Address { get; set; }
public string PhoneNumber { get; set; }
public BankAccount Account { get; set; }
}
我们写一个单元测试程序,看看Entity Framework Code First会不是自动地根据两个类的依赖关系创建数据库中的一对一关系。
[TestMethod]
public void CanAddCustomerWithBankAccount()
{
OrderSystemContext unitOfWork = new OrderSystemContext();
CustomerRepository repository = new CustomerRepository(unitOfWork);
Customer newCustomer = new Customer() { IDCardNumber =
"120104198106072518", CustomerName = "Alex", Gender = "M", PhoneNumber =
"test" };
Address customerAddress = new Address { Country =
"China", Province = "Tianjin", City = "Tianjin", StreetAddress = "Crown
Plaza", ZipCode = "300308" };
BankAccount account = new
BankAccount { AccountNumber = "2012001001", BankName = "ICBC",
AccountName = "Alex", CreatedDate = DateTime.Parse("2012-1-21") };
newCustomer.Address = customerAddress;
newCustomer.Account = account;
repository.AddNewCustomer(newCustomer);
unitOfWork.CommitChanges();
}
我们运行一下我们的单元测试程序,程序会抛出异常:
Test method EntityFramework.CodeFirst.Demo1.UnitTest.CustomerRepositoryUnitTest.CanAddCustomerWithBankAccount threw exception:
System.Data.DataException:
An exception occurred while initializing the database. See the
InnerException for details. --->
System.Data.Entity.Infrastructure.DbUpdateException: Null value for
non-nullable member. Member: 'Account'. --->
System.Data.UpdateException: Null value for non-nullable member. Member:
'Account'.
这是为什么呢?因为Entity Framework Code First无法根据类之间的依赖关系推断并建立一对一关系,它根本搞不清楚在这两个存在依赖关系的类中,哪个是主表,哪个是子表,外键应该建立在哪个表中。一对多关系中非常容易分清主表和子表,哪个类中包含另一个的实例集合,它就是主表。多对多关系是通过连接表建立的,不需要分清主表和子表。但是到一对一关系时,这就是个问题了。
要想让Entity Framework Code First根据类之间的依赖关系推断并建立一对一关系,你必须帮助它,告诉他哪个是主表,哪个是子表。
这里做个备注:关于值对象(Value Object):
什么情况下用值对象?
i、定义的Model类中字段都是基本类型;
ii、所谓的值对象就是一些没有生命周期,也没有业务逻辑上唯一标识符的类(也就是说无关紧要、可有可无的Model类)。
iii、哪些类是Entity,哪些类是Value Object不是固定的,取决于具体的业务逻辑。
假设一个银行账号必须有对应的客户,但是客户可以没有银行账号,并且由于银行账号是个值对象,没有必要让它包含客户类的实例。
因为银行账号类的定义中并不包含客户类的实例,所以我们需要在客户类的配置方法中设定这个一对一关系。
public class CustomerEntityConfiguration:EntityTypeConfiguration<Customer>
{
public CustomerEntityConfiguration()
{
HasKey(c => c.IDCardNumber).Property(c =>
c.IDCardNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(c => c.IDCardNumber).HasMaxLength(20);
this.Property(c => c.CustomerName).IsRequired().HasMaxLength(50);
this.Property(c => c.Gender).IsRequired().HasMaxLength(1);
this.Property(c => c.PhoneNumber).HasMaxLength(20);
this.HasOptional(c => c.Account).WithOptionalDependent();
}
}
在客户类中的HasOptional意味着客户类可以有也可以没有银行账号。当我们通过HasOptional指定Customer与BankAccount类的关系时,Entity
Framework Code
First要求我们指定它们之间的依赖关系。这时,IntelliSense会让你在两个方法之间进行选择:WithOptionalDependent和WithOptionalPrincipal。如果你选择WithOptionalDependent则代表Customer表中有一个外键指向BankAccount表的主键,如果你选择WithOptionalPrincipal则相反,BankAccount拥有指向Customer表的外键。
执行一下我们的单元测试,这次就不会报错了。然后我们打开SQL Server,我们发现Entity Framework Code First映射到正确的一对一关系。
如图:

大家可以看到Customer表中的外键是可以为空的,这是由于我们使用了HasOptional。如果我们需要一对一关系中的外键不能为空,我们就需要使用HasRequired.
HasRequired(c => c.Account).WithRequiredDependent();
当我们使用HasRequired时候,IntelliSense会让你在WithRequiredDependent和WithRequiredPrinciple之间选择, 这里的dependent和principle也是用于决定主键在哪个表的。
Code First处理类之间的继承关系
1.Table Per Hierarchy(TPH): 只建立一个表,把基类和子类中的所有属性都映射为表中的列。
Code First默认会把基类和子类的所有属性都映射成一个表中的列,并且会增加一个Discriminator列标识存进去的是哪个类的实例。
如:

2.Table Per Type(TPT): 为基类和每个子类建立一个表,每个与子类对应的表中只包含子类特有的属性对应的列。
在这种处理方式中,Entity Framework Code First会为每个基类和子类建立一个表,子类的表中只包含子类特有的属性。
如:

3.Table Per Concrete Type(TPC):为每个子类建立一个表,每个与子类对应的表中包含基类的属性对应的列和子类特有属性对应的列。
Code First默认使用的是TPC方式。
在这种处理方式中,Entity Framework Code First为每一个子类建立一个表,在子类对应的表中除了子类特有的属性外还有基类的属性对应的表。
和TPT一样,我们也需要通过Map方法进行设置。
public class SalesPersonValueObjectConfiguration: EntityTypeConfiguration<SalesPerson>
{
public SalesPersonValueObjectConfiguration()
{
HasKey(p => p.EmployeeID).Property(p => p.EmployeeID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(p => p.Name).IsRequired().HasMaxLength(100);
Property(p => p.Gender).IsRequired().HasMaxLength(1);
Map<SalesMan>(salesman => { salesman.ToTable("SalesMan"); salesman.MapInheritedProperties(); });
Map<SalesManager>(manager => { manager.ToTable("Manager"); manager.MapInheritedProperties(); });
}
}
通过MapInheritedProperties方法就可以强制Code First使用TPC方式。
我们重新编译之后执行我们原来的测试方法,可以得到不同的数据表结构,Code First不会为基类建立表,而是为每个子类都建立一个表,将子类的内容和基类的内容都存储到各个子类对应的表中。
如:

PS:如果你的基类是abstract,效果也是一样的。
最后需要探讨的一个问题是我们在实际项目中应该使用哪种方式呢?
1.不推荐使用TPC(Type
Per Concrete
Type),因为在TPC方式中子类中包含的其他类的实例或实例集合不能被映射为表之间的关系。你必须通过手动地在类中添加依赖类的主键属性,从而让Code
First感知到它们之间的关系,而这种方式是和使用Code First的初衷相反的。
2.从查询性能上来说,TPH会好一些,因为所有的数据都存在一个表中,不需要在数据查询时使用join。
3.从存储空间上来说,TPT会好一些,因为使用TPH时所有的列都在一个表中,而表中的记录不可能使用所有的列,于是有很多列的值是null,浪费了很多存储空间。
4.从数据验证的角度来说,TPT好一些,因为TPH中很多子类属性对应的列是可为空的,就为数据验证增加了复杂性。
所以说具体的项目中选择哪种方式取决于你的实际项目需要。
本文内容来源:http://www.cnblogs.com/lk8167/archive/2013/01/22/2871011.html
EF Code First弊端(自己总结):
虽然微软从EF4.1版本开始支持“Code First”这种编程方式,但是这种方式存在以下弊端:
1、配置数据表关联关系复杂。虽然用Fluent API可以动态设置数据表之间的关联关系,如主键、外键、一对多、多对多等,但是设置代码相对复杂,如果代码层次设计不好的话很难读懂和维护;
2、数据迁移麻烦;
3、项目改造时,与现有系统整合性差;
CodeFirst与EntityFramework【续】的更多相关文章
- CodeFirst与EntityFramework
项目添加EntityFramework命令:Install-Package EntityFramework CodeFirst默认规则1. 数据库映射:Code First 默认会在本地的SQL Ex ...
- CodeFirst 的编程方式
第一步:创建控制台项目第二步:添加新建项目→Ado.Net空实体模型第三步:添加实体:Customer,添加几个必要的测试字段第四步:添加实体之间的联系第五步:根据模型生成数据库脚本,并执行sql脚本 ...
- CodeFirst命令
CodeFirst get-help entityFramework NuGet命令 Add-Migration Adds a new mig ...
- Entity Framework 5.0系列之Code First数据库迁移
我们知道无论是"Database First"还是"Model First"当模型发生改变了都可以通过Visual Studio设计视图进行更新,那么对于Cod ...
- [1] Entity Framework / Code First
CodeFirst是EntityFramework的一种技术手段,因为传统编程方式都是先建立数据库,然后根据数据库模型为应用程序建模,再进行开发:CodeFirst从字面上理解就是代码先行,先在程序中 ...
- 第三篇:Entity Framework CodeFirst & Model 映射 续篇 EntityFramework Power Tools 工具使用
上一篇 第二篇:Entity Framework CodeFirst & Model 映射 主要介绍以Fluent API来实作EntityFramework CodeFirst,得到了大家一 ...
- EntityFramework CodeFirst SQLServer转Oracle踩坑笔记
接着在Oracle中使用Entity Framework 6 CodeFirst这篇博文,正在将项目从SQLServer 2012转至Oracle 11g,目前为止遇到的问题在此记录下. SQL Se ...
- EntityFramework系列:SQLite.CodeFirst自动生成数据库
http://www.cnblogs.com/easygame/p/4447457.html 在Code First模式下使用SQLite一直存在不能自动生成数据库的问题,使用SQL Server C ...
- EntityFramework 5.0 CodeFirst 教程04-查询,插入,更新,和删除数据
---------------------目录-------------------------- EntityFramework 5.0 CodeFirst 教程04-查询,插入,更新,和删除数据 ...
随机推荐
- LightGBM两种使用方式
原生形式使用lightgbm(import lightgbm as lgb) import lightgbm as lgb from sklearn.metrics import mean_squar ...
- Node.js与VUE安装及环境配置之Windows篇
Node.js安装及环境配置之Windows篇 https://www.cnblogs.com/zhouyu2017/p/6485265.html Node.js安装及环境配置之Windows篇htt ...
- RecyclerView下拉刷新和上拉加载更多实现
RecyclerView下拉刷新和上拉加载更多实现 转 https://www.jianshu.com/p/4ea7c2d95ecf 在Android开发中,RecyclerView算是使用频率非 ...
- HTTP的响应协议
响应行介绍,响应状态码 1XX: 客户端请求服务器,但是请求未完成,服务器什么事也没干 2XX: 表示响应成功,代表性的状态码就是200 3XX: 请求重定向,代表性的状态码302 4XX: 客户端发 ...
- <iframe>和<frame>标签属性详解
iframe>元素会创建包含另外一个文档的内联框架(即行内框架): 一.align 属性(不赞成) align属性规定iframe相对于周围元素的水平和垂直对齐方式,因为iframe元素是行内元 ...
- 5G 与 MEC 边缘计算
目录 文章目录 目录 前言 参考文献 通信网络 核心网演进之路 早古时期 2G 网络架构 3G 网络架构 4G 网络架构 5G 5G 网络的需求 5G 网络架构的设计原则 5G 网络的逻辑架构 5G ...
- Qt编写自定义控件24-图片轮播控件
一.前言 上一篇文章写的广告轮播控件,采用的传统widget堆积设置样式表做的,这次必须要用到更高级的QPainter来绘制了,这个才是最高效的办法,本控件参考雨田哥的轮播控件,经过大规模的改造而成, ...
- [Scikit-learn] 1.5 Generalized Linear Models - SGD for Classification
NB: 因为softmax,NN看上去是分类,其实是拟合(回归),拟合最大似然. 多分类参见:[Scikit-learn] 1.1 Generalized Linear Models - Logist ...
- logback 和 log4j对比,及相关配置
Logback 一.logback的介绍 Logback是由log4j创始人设计的又一个开源日志组件.logback当前分成三个模块:logback-core,logback- classic和log ...
- tmpfs使用完毕导致数据库无法正常工作
df -h 查看 重新启动服务器就可以了