在使用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. jquery获取哪一个下拉框被选中

    var val = $("select[name='type_irb'] option:selected").val();

  2. axios中的qs

    qs是一个npm仓库所管理的包,可通过npm install qs命令进行安装. 1. qs.parse()将URL解析成对象的形式 const Qs = require('qs'); let url ...

  3. AngularJS - Directive Restrictions

    While it’s cool to make a custom element like we did the the previous cast, it’s actually more commo ...

  4. MySQL mysqldump数据导出基本操作

    mysqldump mysqldump命令是mysql数据库中备份工具,用于将MySQL服务器中的数据库以标准的sql语言的方式导出,并保存到文件中. 选项 --all-databases, -A:导 ...

  5. jquery操作select(选中,取值)

    最近工作中总出现select 和 option问题,整理一下,内容大部分源于网络资料 一.基础取值问题 例如<select class="selector"></ ...

  6. Angular入门教程二

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

  7. python 描述符 上下文管理协议 类装饰器 property metaclass

    1.描述符 #!/usr/bin/python env # coding=utf-8 # 数据描述符__get__ __set__ __delete__ ''' 描述符总结 描述符是可以实现大部分py ...

  8. 基于Vue的WebApp项目开发(三)

    实现根组件通用的头部和底部样式 明白由webpack搭建起来的Vue项目的执行流程,那么就可以知道实现这个需要只要在根组件和入口文件上做“手脚”即可 <!--以后项目的根组件--> < ...

  9. springboot中filter的配置和顺序执行

    项目结构 springboot版本 <parent> <groupId>org.springframework.boot</groupId> <artifac ...

  10. Linux常用命令(三)————创建+删除+设置权限

    1. mkdir mkdir [选项] DirName 命令中的[选项]: -m    用于对新建目录设置存取权限,也可以用 chmod 命令进行设置. -p     需要时创建上层文件夹(或目录), ...