在使用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. 常用SEO优化

  2. django基础一之web框架的本质

    一 web框架的本质及自定义web框架 我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响 ...

  3. Angular入门教程二

    4 功能介绍 4.1数据绑定 AngularJS的双向数据绑定,意味着你可以在Mode(JS)中改变数据,而这些变动立刻就会自动出现在View上,反之亦然.即:一方面可以做到model变化驱动了DOM ...

  4. JS全国城市三级联动

    HTML <select id="s_province" name="s_province"></select> <select ...

  5. javascript实现数据结构: 树和森林

    树的3种常用链表结构 1 双亲表示法(顺序存储结构) 优点:parent(tree, x)操作可以在常量时间内实现 缺点:求结点的孩子时需要遍历整个结构 用一组连续的存储空间来存储树的结点,同时在每个 ...

  6. java中静态代码块的用法和static用法(转)

    (一)java 静态代码块 静态方法区别 一般情况下,如果有些代码必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的;需要在项目启动的时候就初始化,在不创建对象的情况下,其他程 ...

  7. js 判断 复选框全选、全不选、反选、必选一个

    一个挺 使用的 js 代码片段,  判断  复选框全选.全不选.反选.必选一个 记录下, 搬来的 思路: 修改数据的 选中与否状态, 拿到所有的输入框,看是否有选中的状态 <html> & ...

  8. Benefits of encapsulation

    ①:通过方法来控制成员变量的操作,提高了代码的安全性. ②:把代码用方法进行封装,提高了代码的复用性.

  9. OpenStack 学习笔记 (一)

    后续的文章都贴在:臭蛋上 这一系列笔记已经记录很长一段时间了,种种原因没有贴出来,现在陆陆续续的贴出来.可能由于自己理解的 错误和疏忽,导致存在错误,欢迎大家指正,交流. 所有的源码分析都是基于Ope ...

  10. 哪个HTML5内建对象用于在画布上绘制?()

    哪个HTML5内建对象用于在画布上绘制?() getContent getContext getGraphics getCanvas 我的理解: A.C.D不存在HTML5,,js方法中 HTML 5 ...