3、    实现一对一的关系。
在介绍一对多关系和多对多关系时,大家应该已经注意到了只要存在依赖关系的两个类的定义中包含对方的实例或实例的集合,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【续】的更多相关文章

  1. CodeFirst与EntityFramework

    项目添加EntityFramework命令:Install-Package EntityFramework CodeFirst默认规则1. 数据库映射:Code First 默认会在本地的SQL Ex ...

  2. CodeFirst 的编程方式

    第一步:创建控制台项目第二步:添加新建项目→Ado.Net空实体模型第三步:添加实体:Customer,添加几个必要的测试字段第四步:添加实体之间的联系第五步:根据模型生成数据库脚本,并执行sql脚本 ...

  3. CodeFirst命令

    CodeFirst get-help entityFramework          NuGet命令 Add-Migration                     Adds a new mig ...

  4. Entity Framework 5.0系列之Code First数据库迁移

    我们知道无论是"Database First"还是"Model First"当模型发生改变了都可以通过Visual Studio设计视图进行更新,那么对于Cod ...

  5. [1] Entity Framework / Code First

    CodeFirst是EntityFramework的一种技术手段,因为传统编程方式都是先建立数据库,然后根据数据库模型为应用程序建模,再进行开发:CodeFirst从字面上理解就是代码先行,先在程序中 ...

  6. 第三篇:Entity Framework CodeFirst & Model 映射 续篇 EntityFramework Power Tools 工具使用

    上一篇 第二篇:Entity Framework CodeFirst & Model 映射 主要介绍以Fluent API来实作EntityFramework CodeFirst,得到了大家一 ...

  7. EntityFramework CodeFirst SQLServer转Oracle踩坑笔记

    接着在Oracle中使用Entity Framework 6 CodeFirst这篇博文,正在将项目从SQLServer 2012转至Oracle 11g,目前为止遇到的问题在此记录下. SQL Se ...

  8. EntityFramework系列:SQLite.CodeFirst自动生成数据库

    http://www.cnblogs.com/easygame/p/4447457.html 在Code First模式下使用SQLite一直存在不能自动生成数据库的问题,使用SQL Server C ...

  9. EntityFramework 5.0 CodeFirst 教程04-查询,插入,更新,和删除数据

    ---------------------目录-------------------------- EntityFramework 5.0 CodeFirst 教程04-查询,插入,更新,和删除数据  ...

随机推荐

  1. 使用 pip wheel 实现 Python 依赖包的离线安装

    pip python 依赖 安装 有时候, 需要部署 Python 应用的服务器没有网络连接, 这时候, 你就要把整个 Python 应用做成离线安装包. 借助 wheel, 很容易就可以实现. 首先 ...

  2. Python Docstring 风格和写法学习

    什么是Python Docstring 和Java类似,Python也通过注释形式的Docstring给程序.类.函数等建立文档.通过Docstring建立的文档不仅对人来说有更好的可读性,也能够让I ...

  3. python3 django项目从项目中导出依赖包

    1. 在项目的根目录中使用mac终端执行命令, pip3 freeze > requirements.txt #requirements.txt只是个名字可以随便起,一般默认为requireme ...

  4. RotateDrawable

    用来对Drawable进行旋转,也是通过setLevel来控制旋转的,最大值也是:10000 相关属性如下: fromDegrees:起始的角度,,对应最低的level值,默认为0 toDegrees ...

  5. @Value()读取配置文件属性,读出值为null的问题

    一.问题描述 自定义一个Filter如下: @Component public class JwtFilter extends GenericFilterBean{ @Value("${jw ...

  6. prometheus监控插件mysqld_exporter

    1,首先需要增加授权 mysql> GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost' IDEN ...

  7. js或者jquery直接下载网页上的图片代码

    1.jquery方式 使用jquery直接下载图片 function downloadImage(src) { var a = $("<a></a>").a ...

  8. ubuntu16.04 下通过rc.d(rc.local)实现开机启动(未登录)anydesk

    先编辑anydesk-X.X.X/init/anydesk文件,将"DAEMON=//usr/bin$NAME"改成"DAEMON=/XXX/anydesk-5.1.1/ ...

  9. ffmpeg 编译集成libfdk-aac

    目录 1. 编译libfdk-aac 2. 编译ffmpeg 1. 编译libfdk-aac 下载源码:$ git clone https://github.com/mstorsjo/fdk-aac. ...

  10. Java基础——值传递

    值传递? 参数传递的值的拷贝,改变拷贝不影响原参数. 引用传递? 参数传递的是参数本身,改变形参,实参也改变. Java中是什么传递? Java中只有值传递 实际情况为什么不对呢? 1. 基本数据类型 ...