在使用EF Core和设计数据库的时候,通常一对多、多对多关系使用得比较多,但是一对一关系使用得就比较少了。最近我发现实际上EF Core很好地支持了数据库的一对一关系。

数据库


我们先来看看SQL Server数据库中的表:

Person表代表的是一个人,表中有些字段来简单描述一个人,其建表语句如下:

CREATE TABLE [dbo].[Person](
[ID] [int] IDENTITY(1,1) NOT NULL,
[PersonCode] [nvarchar](50) NULL,
[Name] [nvarchar](50) NULL,
[Age] [int] NULL,
[City] [nvarchar](50) NULL,
[CreateTime] [datetime] NULL,
CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [IX_Person] UNIQUE NONCLUSTERED
(
[PersonCode] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO ALTER TABLE [dbo].[Person] ADD CONSTRAINT [DF_Person_CreateTime] DEFAULT (getdate()) FOR [CreateTime]
GO

从上面可以看出,除了主键ID外,我们还设置了列PersonCode为唯一键IX_Person。

然后数据库中还有张表IdentificationCard,其代表的是一个人的身份证,其中列IdentificationNo是身份证号码,其建表语句如下:

CREATE TABLE [dbo].[IdentificationCard](
[ID] [int] IDENTITY(1,1) NOT NULL,
[IdentificationNo] [nvarchar](50) NULL,
[PersonCode] [nvarchar](50) NULL,
[CreateTime] [datetime] NULL,
CONSTRAINT [PK_IdentificationCard] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [IX_IdentificationCard] UNIQUE NONCLUSTERED
(
[PersonCode] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO ALTER TABLE [dbo].[IdentificationCard] ADD CONSTRAINT [DF_IdentificationCard_CreateTime] DEFAULT (getdate()) FOR [CreateTime]
GO ALTER TABLE [dbo].[IdentificationCard] WITH CHECK ADD CONSTRAINT [FK_IdentificationCard_Person] FOREIGN KEY([PersonCode])
REFERENCES [dbo].[Person] ([PersonCode])
ON UPDATE CASCADE
ON DELETE CASCADE
GO ALTER TABLE [dbo].[IdentificationCard] CHECK CONSTRAINT [FK_IdentificationCard_Person]
GO

其中设置外键关系FK_IdentificationCard_Person:通过IdentificationCard表的PersonCode列来关联Person表的PersonCode列,从而指明一张身份证属于哪个Person。

然后我们同样设置了IdentificationCard表的PersonCode列为唯一键IX_IdentificationCard,这样外键FK_IdentificationCard_Person表示的实际上就是一对一关系了,因为IdentificationCard表的一行数据通过列PersonCode只能找到一行Person表数据,而现在IdentificationCard表的PersonCode列又是唯一键,所以反过来Person表在IdentificationCard表中最多也只能找到一行数据,所以这是个典型的一对一关系。

我们还在FK_IdentificationCard_Person外键关系上使用了CASCADE设置了级联删除和级联更新。

EF Core实体


接着我们新建了一个.NET Core控制台项目,使用EF Core的Scaffold-DbContext指令自动从数据库中生成实体,可以看到通过我们在数据库中设置的唯一键和外键,EF Core自动识别出了Person表和IdentificationCard表之间是一对一关系,生成的代码如下:

Person实体,对应的是数据库中的Person表,注意其中包含一个属性IdentificationCard,表示Person表和IdentificationCard表的一对一关系:

using System;
using System.Collections.Generic; namespace FFCoreOneToOne.Entities
{
/// <summary>
/// Person实体,对应数据库中的Person表,可以看到其中有一个IdentificationCard属性,表示Person实体对应一个IdentificationCard实体
/// </summary>
public partial class Person
{
public int Id { get; set; }
public string PersonCode { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
public string City { get; set; }
public DateTime? CreateTime { get; set; } public IdentificationCard IdentificationCard { get; set; }
}
}

IdentificationCard实体,对应的是数据库中的IdentificationCard表,注意其中包含一个属性PersonCodeNavigation,表示IdentificationCard表和Person表的一对一关系:

using System;
using System.Collections.Generic; namespace FFCoreOneToOne.Entities
{
/// <summary>
/// IdentificationCard实体,对应数据库中的IdentificationCard表,可以看到其中有一个PersonCodeNavigation属性,表示IdentificationCard实体对应一个Person实体
/// </summary>
public partial class IdentificationCard
{
public int Id { get; set; }
public string IdentificationNo { get; set; }
public string PersonCode { get; set; }
public DateTime? CreateTime { get; set; } public Person PersonCodeNavigation { get; set; }
}
}

最后是Scaffold-DbContext指令生成的DbContext类TestDBContext,其中比较重要的地方是OnModelCreating方法中,设置IdentificationCard实体和Person实体间一对一关系的Fluent API代码,我用注释详细阐述了每一步的含义:

using System;
using FFCoreOneToOne.Logger;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata; namespace FFCoreOneToOne.Entities
{
public partial class TestDBContext : DbContext
{
public TestDBContext()
{
} public TestDBContext(DbContextOptions<TestDBContext> options)
: base(options)
{
} public virtual DbSet<IdentificationCard> IdentificationCard { get; set; }
public virtual DbSet<Person> Person { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=1qaz!QAZ;Database=TestDB");
}
} protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IdentificationCard>(entity =>
{
entity.HasIndex(e => e.PersonCode)
.HasName("IX_IdentificationCard")
.IsUnique(); entity.Property(e => e.Id).HasColumnName("ID"); entity.Property(e => e.CreateTime)
.HasColumnType("datetime")
.HasDefaultValueSql("(getdate())"); entity.Property(e => e.IdentificationNo).HasMaxLength(); entity.Property(e => e.PersonCode).HasMaxLength(); //设置IdentificationCard实体和Person实体的一对一关系
entity.HasOne(d => d.PersonCodeNavigation)//HasOne设置IdentificationCard实体中有一个Person实体,可以通过IdentificationCard实体的PersonCodeNavigation属性访问到
.WithOne(p => p.IdentificationCard)//WithOne设置Person实体中有一个IdentificationCard实体,可以通过Person实体的IdentificationCard属性访问到
.HasPrincipalKey<Person>(p => p.PersonCode)//设置数据库中Person表的PersonCode列是一对一关系的主表键
.HasForeignKey<IdentificationCard>(d => d.PersonCode)//设置数据库中IdentificationCard表的PersonCode列是一对一关系的从表外键
.OnDelete(DeleteBehavior.Cascade)//由于我们在数据库中开启了IdentificationCard表外键FK_IdentificationCard_Person的级联删除,所以这里也生成了实体级联删除的Fluent API
.HasConstraintName("FK_IdentificationCard_Person");//设置IdentificationCard实体和Person实体的一对一关系采用的是数据库外键FK_IdentificationCard_Person
}); modelBuilder.Entity<Person>(entity =>
{
entity.HasIndex(e => e.PersonCode)
.HasName("IX_Person")
.IsUnique(); entity.Property(e => e.Id).HasColumnName("ID"); entity.Property(e => e.City).HasMaxLength(); entity.Property(e => e.CreateTime)
.HasColumnType("datetime")
.HasDefaultValueSql("(getdate())"); entity.Property(e => e.Name).HasMaxLength(); entity.Property(e => e.PersonCode)
.IsRequired()
.HasMaxLength();
});
}
}
}

示例代码


接着我们在.NET Core控制台项目的Program类中定义了些示例代码,其中AddPersonWithIdentificationCard和AddIdentificationCardWithPerson方法使用DbContext来添加数据到数据库,RemoveIdentificationCardFromPerson和RemovePersonFromIdentificationCard方法用来演示如何通过实体的导航属性来删除数据,最后DeleteAllPersons是清表语句,删除数据库中IdentificationCard表和Person表的所有数据。

这里先把示例代码全部贴出来:

using FFCoreOneToOne.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq; namespace FFCoreOneToOne
{
class Program
{ /// <summary>
/// 删除数据库Person表和IdentificationCard表的所有数据
/// </summary>
static void DeleteAllPersons()
{
using (TestDBContext dbContext = new TestDBContext())
{
dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[IdentificationCard]");
dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Person]");
}
} /// <summary>
/// 通过添加Person来添加IdentificationCard
/// </summary>
static void AddPersonWithIdentificationCard()
{
//通过添加Person实体来添加IdentificationCard实体,将Person实体的IdentificationCard属性设置为对应的IdentificationCard实体即可
using (TestDBContext dbContext = new TestDBContext())
{
var james = new Person() { Name = "James", Age = , PersonCode = "P001", City = "Beijing" };
james.IdentificationCard = new IdentificationCard() { IdentificationNo = "" }; var tom = new Person() { Name = "Tom", Age = , PersonCode = "P002", City = "Shanghai" };
tom.IdentificationCard = new IdentificationCard() { IdentificationNo = "" }; var sam = new Person() { Name = "Sam", Age = , PersonCode = "P003", City = "Chongqing" };
sam.IdentificationCard = new IdentificationCard() { IdentificationNo = "" }; dbContext.Person.Add(james);
dbContext.Person.Add(tom);
dbContext.Person.Add(sam); dbContext.SaveChanges();
}
} /// <summary>
/// 通过添加IdentificationCard来添加Person,从EF Core的日志中可以看到使用这种方式还是先执行的插入Person表数据的SQL,再执行的插入IdentificationCard表数据的SQL
/// </summary>
static void AddIdentificationCardWithPerson()
{
//通过添加IdentificationCard实体来添加Person实体,将IdentificationCard实体的PersonCodeNavigation属性设置为对应的Person实体即可
using (TestDBContext dbContext = new TestDBContext())
{
var jamesCard = new IdentificationCard() { IdentificationNo = "" };
jamesCard.PersonCodeNavigation = new Person() { Name = "James", Age = , PersonCode = "P001", City = "Beijing" }; var tomCard = new IdentificationCard() { IdentificationNo = "" };
tomCard.PersonCodeNavigation = new Person() { Name = "Tom", Age = , PersonCode = "P002", City = "Shanghai" }; var samCard = new IdentificationCard() { IdentificationNo = "" };
samCard.PersonCodeNavigation = new Person() { Name = "Sam", Age = , PersonCode = "P003", City = "Chongqing" }; dbContext.IdentificationCard.Add(jamesCard);
dbContext.IdentificationCard.Add(tomCard);
dbContext.IdentificationCard.Add(samCard); dbContext.SaveChanges();
}
} /// <summary>
/// 通过设置Person实体的IdentificationCard属性为null来删除IdentificationCard表的数据
/// </summary>
static void RemoveIdentificationCardFromPerson()
{
//先用DbContext从数据库中查询出Person实体,然后设置其IdentificationCard属性为null,来删除IdentificationCard表的数据
//注意在查询Person实体的时候,记得要用EF Core中Eager Loading的Include方法也查询出IdentificationCard实体,这样我们在设置Person实体的IdentificationCard属性为null后,DbContext才能跟踪到变更,才会在下面调用DbContext.SaveChanges方法时,生成删除IdentificationCard表数据的SQL语句
using (TestDBContext dbContext = new TestDBContext())
{
var james = dbContext.Person.Include(e => e.IdentificationCard).First(e => e.Name == "James");
james.IdentificationCard = null; var tom = dbContext.Person.Include(e => e.IdentificationCard).First(e => e.Name == "Tom");
tom.IdentificationCard = null; var sam = dbContext.Person.Include(e => e.IdentificationCard).First(e => e.Name == "Sam");
sam.IdentificationCard = null; dbContext.SaveChanges();
}
} /// <summary>
/// 本来这个方法是想用来通过设置IdentificationCard实体的PersonCodeNavigation属性为null,来删除Person表的数据,但是结果是还是删除的IdentificationCard表数据
/// </summary>
static void RemovePersonFromIdentificationCard()
{
//原本我想的是,先用DbContext从数据库中查询出IdentificationCard实体,并用EF Core中Eager Loading的Include方法也查询出Person实体,然后设置IdentificationCard实体的PersonCodeNavigation属性为null,来删除Person表的数据
//结果这样做EF Core最后还是删除的IdentificationCard表的数据,原因是IdentificationCard表是一对一外键关系的从表,设置从表实体的外键属性PersonCodeNavigation为null,EF Core认为的是从表的数据作废,所以删除了从表IdentificationCard中的数据,主表Person的数据还在。。。
using (TestDBContext dbContext = new TestDBContext())
{
var jamesCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "");
jamesCard.PersonCodeNavigation = null; var tomCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "");
tomCard.PersonCodeNavigation = null; var samCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "");
samCard.PersonCodeNavigation = null; dbContext.SaveChanges();
}
} static void Main(string[] args)
{
DeleteAllPersons(); AddPersonWithIdentificationCard();
AddIdentificationCardWithPerson();
RemoveIdentificationCardFromPerson();
RemovePersonFromIdentificationCard(); Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}
}
}

AddPersonWithIdentificationCard

首先我们测试AddPersonWithIdentificationCard方法,其通过添加Person实体到数据库来添加IdentificationCard表的数据,更改Main方法的代码如下,并执行程序:

static void Main(string[] args)
{
DeleteAllPersons(); AddPersonWithIdentificationCard();
//AddIdentificationCardWithPerson();
//RemoveIdentificationCardFromPerson();
//RemovePersonFromIdentificationCard(); Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}

执行后数据库中Person表的数据如下:

IdentificationCard表的数据如下:

AddIdentificationCardWithPerson

然后我们测试AddIdentificationCardWithPerson方法,其通过添加IdentificationCard实体到数据库来添加Person表的数据,从EF Core的日志中可以看到使用这种方式还是先执行的插入Person表数据的SQL,再执行的插入IdentificationCard表数据的SQL。更改Main方法的代码如下,并执行程序:

static void Main(string[] args)
{
DeleteAllPersons(); //AddPersonWithIdentificationCard();
AddIdentificationCardWithPerson();
//RemoveIdentificationCardFromPerson();
//RemovePersonFromIdentificationCard(); Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}

执行后数据库中Person表的数据如下:

IdentificationCard表的数据如下:

RemoveIdentificationCardFromPerson

然后我们测试RemoveIdentificationCardFromPerson方法,其通过设置Person实体的IdentificationCard属性为null,来删除IdentificationCard表的数据,更改Main方法的代码如下,并执行程序:

static void Main(string[] args)
{
DeleteAllPersons(); AddPersonWithIdentificationCard();
//AddIdentificationCardWithPerson();
RemoveIdentificationCardFromPerson();
//RemovePersonFromIdentificationCard(); Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}

执行后数据库中Person表的数据如下:

IdentificationCard表的数据如下:

RemovePersonFromIdentificationCard

最后我们测试RemovePersonFromIdentificationCard方法,本来这个方法我是设计用来通过设置IdentificationCard实体的PersonCodeNavigation属性为null,来删除Person表的数据,但是测试后发现结果还是删除的IdentificationCard表的数据,原因可以看下上面示例代码中RemovePersonFromIdentificationCard方法中的注释。更改Main方法的代码如下,并执行程序:

static void Main(string[] args)
{
DeleteAllPersons(); AddPersonWithIdentificationCard();
//AddIdentificationCardWithPerson();
//RemoveIdentificationCardFromPerson();
RemovePersonFromIdentificationCard(); Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}

执行后数据库中Person表的数据如下:

IdentificationCard表的数据如下:

EF Core 2.1 支持数据库一对一关系的更多相关文章

  1. .NET 5/.NET Core使用EF Core 5连接MySQL数据库写入/读取数据示例教程

    本文首发于<.NET 5/.NET Core使用EF Core 5(Entity Framework Core)连接MySQL数据库写入/读取数据示例教程> 前言 在.NET Core/. ...

  2. EF Core中如何设置数据库表自己与自己的多对多关系

    本文的代码基于.NET Core 3.0和EF Core 3.0 有时候在数据库设计中,一个表自己会和自己是多对多关系. 在SQL Server数据库中,现在我们有Person表,代表一个人,建表语句 ...

  3. 在ef core中使用postgres数据库的全文检索功能实战之中文支持

    前言 有关通用的postgres数据库全文检索在ef core中的使用方法,参见我的上一篇文章. 本文实践了zhparser中文插件进行全文检索. 准备工作 安装插件,最方便的方法是直接使用安装好插件 ...

  4. 最新版的EF Core对UWP支持的怎么样

    为啥写这篇帖子呢?其实是因为翻微软的文档中心偶然翻到的,于是就出于好奇就试试了,看看用着怎么样. 以前没注意图片,所以我今天发现的时候,显示EF Core3.1支持standard2.0,于是就想试试 ...

  5. 在ef core中使用postgres数据库的全文检索功能实战

    起源 之前做的很多项目都使用solr/elasticsearch作为全文检索引擎,它们功能全面而强大,但是对于较小的项目而言,构建和维护成本显然过高,尤其是从关系数据库/文档数据库到全文检索引擎的数据 ...

  6. asp.net Core EF core ( Entity Framework 7 ) 数据库更新维护

    CreateData­baseIfNotExists等之前的API已经废弃,现在采用的是微软封装好,简化.高效的API,migrations 因为,旧API,要付出高昂的代价,以及局限性 打开VS20 ...

  7. EF Core 中处理 1对1 关系

    最近在开发记录感想功能的时候用到了1对1的数据关系,具体情况是这样的,有这样两个1对1的类型 public class Item { public int Id { get; set; } publi ...

  8. 基于ef core 2.0的数据库增删改审计系统

    1.首先是建审计存储表 CREATE TABLE [dbo].[Audit] ( [Id] [uniqueidentifier] NOT NULL, [EntityName] [nvarchar](1 ...

  9. EF Core 根据已有的数据库来生成 EF 领域模型

    1. 如图: 2. 命令 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrame ...

随机推荐

  1. ES6学习笔记(七)-对象扩展

    可直接访问有道云笔记分享链接查看es6所有学习笔记 http://note.youdao.com/noteshare?id=b24b739560e864d40ffaab4af790f885

  2. HTML绝对路径和相对路径

    HTML路径: 绝对路径:从根目录开始 相对路径:../ 相对于html文件,上一级 ./ 相对于html文件,当前路径(可以省略) 文件夹名 相对于html文件,往文件里面走

  3. WinForm实现Rabbitmq官网6个案例-Hello World

    先上代码 namespace RabbitMQDemo { public partial class HelloWorld : Form { string queueName1 = "hel ...

  4. 强化学习系列之:Deep Q Network (DQN)

    文章目录 [隐藏] 1. 强化学习和深度学习结合 2. Deep Q Network (DQN) 算法 3. 后续发展 3.1 Double DQN 3.2 Prioritized Replay 3. ...

  5. The formal parameters of the method

    package basic.java; public class ParametersOfTheMethod { public static void main(String[] args) { in ...

  6. spring boot(3)-Rest风格接口

    Rest接口 虽然现在还有很多人在用jsp,但是其实这种动态页面早已过时,现在前端流行的是静态HTML+ rest接口(json格式).当然,如果是单台服务器,用动态还是静态页面可能没什么很大区别,但 ...

  7. vs2015新建web应用程序空模板和添加webapi的模板生成文件的比较

    文件名为全红色的,是使用webapi模板生成的新文件夹或文件

  8. SQL Server ->> MSDB.DBO.AGENT_DATETIME函数从整型转时间日期格式

    SELECT MSDB.DBO.AGENT_DATETIME(20170101,0), CAST(CAST(20170101 AS NVARCHAR(50)) AS DATETIME) 返回 2017 ...

  9. Java BigDecimal初探

    更新时间:2016-03-17 一.引言 <Effactive Java>中有这样的描述:float和double类型的主要设计目标是为了科学计算和工程计算.他们执行二进制浮点运算,这是为 ...

  10. Java学习---Excel读写操作

    1.1.1. 简介 Apache POI 使用Apache POI 完成Excel读写操作 Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API ...