ef mysql 外键 一对一_EF Core 2.1 支持数据库一对一关系

本文介绍了如何在EF Core 2.1中处理数据库的一对一关系,通过示例展示了如何创建和配置SQL Server数据库的一对一外键关系,并展示了在.NET Core控制台应用中如何添加、删除数据以及使用Fluent API进行关系配置。
摘要由CSDN通过智能技术生成

在使用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表的一对一关系:

usingSystem;usingSystem.Collections.Generic;namespaceFFCoreOneToOne.Entities

{///

///Person实体,对应数据库中的Person表,可以看到其中有一个IdentificationCard属性,表示Person实体对应一个IdentificationCard实体///

public partial classPerson

{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表的一对一关系:

usingSystem;usingSystem.Collections.Generic;namespaceFFCoreOneToOne.Entities

{///

///IdentificationCard实体,对应数据库中的IdentificationCard表,可以看到其中有一个PersonCodeNavigation属性,表示IdentificationCard实体对应一个Person实体///

public partial classIdentificationCard

{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代码,我用注释详细阐述了每一步的含义:

usingSystem;usingFFCoreOneToOne.Logger;usingMicrosoft.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore.Metadata;namespaceFFCoreOneToOne.Entities

{public partial classTestDBContext : DbContext

{publicTestDBContext()

{

}public TestDBContext(DbContextOptionsoptions)

:base(options)

{

}public virtual DbSet IdentificationCard { get; set; }public virtual DbSet Person { get; set; }protected override voidOnConfiguring(DbContextOptionsBuilder optionsBuilder)

{if (!optionsBuilder.IsConfigured)

{

optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=1qaz!QAZ;Database=TestDB");

}

}protected override voidOnModelCreating(ModelBuilder modelBuilder)

{

modelBuilder.Entity(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(50);

entity.Property(e=> e.PersonCode).HasMaxLength(50);//设置IdentificationCard实体和Person实体的一对一关系

entity.HasOne(d => d.PersonCodeNavigation)//HasOne设置IdentificationCard实体中有一个Person实体,可以通过IdentificationCard实体的PersonCodeNavigation属性访问到

.WithOne(p => p.IdentificationCard)//WithOne设置Person实体中有一个IdentificationCard实体,可以通过Person实体的IdentificationCard属性访问到

.HasPrincipalKey(p => p.PersonCode)//设置数据库中Person表的PersonCode列是一对一关系的主表键

.HasForeignKey(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(entity =>{

entity.HasIndex(e=>e.PersonCode)

.HasName("IX_Person")

.IsUnique();

entity.Property(e=> e.Id).HasColumnName("ID");

entity.Property(e=> e.City).HasMaxLength(50);

entity.Property(e=>e.CreateTime)

.HasColumnType("datetime")

.HasDefaultValueSql("(getdate())");

entity.Property(e=> e.Name).HasMaxLength(50);

entity.Property(e=>e.PersonCode)

.IsRequired()

.HasMaxLength(50);

});

}

}

}

示例代码

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

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

usingFFCoreOneToOne.Entities;usingMicrosoft.EntityFrameworkCore;usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;namespaceFFCoreOneToOne

{classProgram

{///

///删除数据库Person表和IdentificationCard表的所有数据///

static voidDeleteAllPersons()

{using (TestDBContext dbContext = newTestDBContext())

{

dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[IdentificationCard]");

dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Person]");

}

}///

///通过添加Person来添加IdentificationCard///

static voidAddPersonWithIdentificationCard()

{//通过添加Person实体来添加IdentificationCard实体,将Person实体的IdentificationCard属性设置为对应的IdentificationCard实体即可

using (TestDBContext dbContext = newTestDBContext())

{var james = new Person() { Name = "James", Age = 30, PersonCode = "P001", City = "Beijing"};

james.IdentificationCard= new IdentificationCard() { IdentificationNo = "510100197512305607"};var tom = new Person() { Name = "Tom", Age = 35, PersonCode = "P002", City = "Shanghai"};

tom.IdentificationCard= new IdentificationCard() { IdentificationNo = "510100197512305609"};var sam = new Person() { Name = "Sam", Age = 25, PersonCode = "P003", City = "Chongqing"};

sam.IdentificationCard= new IdentificationCard() { IdentificationNo = "510100197512305605"};

dbContext.Person.Add(james);

dbContext.Person.Add(tom);

dbContext.Person.Add(sam);

dbContext.SaveChanges();

}

}///

///通过添加IdentificationCard来添加Person,从EF Core的日志中可以看到使用这种方式还是先执行的插入Person表数据的SQL,再执行的插入IdentificationCard表数据的SQL///

static voidAddIdentificationCardWithPerson()

{//通过添加IdentificationCard实体来添加Person实体,将IdentificationCard实体的PersonCodeNavigation属性设置为对应的Person实体即可

using (TestDBContext dbContext = newTestDBContext())

{var jamesCard = new IdentificationCard() { IdentificationNo = "510100197512305607"};

jamesCard.PersonCodeNavigation= new Person() { Name = "James", Age = 30, PersonCode = "P001", City = "Beijing"};var tomCard = new IdentificationCard() { IdentificationNo = "510100197512305609"};

tomCard.PersonCodeNavigation= new Person() { Name = "Tom", Age = 35, PersonCode = "P002", City = "Shanghai"};var samCard = new IdentificationCard() { IdentificationNo = "510100197512305605"};

samCard.PersonCodeNavigation= new Person() { Name = "Sam", Age = 25, PersonCode = "P003", City = "Chongqing"};

dbContext.IdentificationCard.Add(jamesCard);

dbContext.IdentificationCard.Add(tomCard);

dbContext.IdentificationCard.Add(samCard);

dbContext.SaveChanges();

}

}///

///通过设置Person实体的IdentificationCard属性为null来删除IdentificationCard表的数据///

static voidRemoveIdentificationCardFromPerson()

{//先用DbContext从数据库中查询出Person实体,然后设置其IdentificationCard属性为null,来删除IdentificationCard表的数据//注意在查询Person实体的时候,记得要用EF Core中Eager Loading的Include方法也查询出IdentificationCard实体,这样我们在设置Person实体的IdentificationCard属性为null后,DbContext才能跟踪到变更,才会在下面调用DbContext.SaveChanges方法时,生成删除IdentificationCard表数据的SQL语句

using (TestDBContext dbContext = newTestDBContext())

{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();

}

}///

///本来这个方法是想用来通过设置IdentificationCard实体的PersonCodeNavigation属性为null,来删除Person表的数据,但是结果是还是删除的IdentificationCard表数据///

static voidRemovePersonFromIdentificationCard()

{//原本我想的是,先用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 = newTestDBContext())

{var jamesCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "510100197512305607");

jamesCard.PersonCodeNavigation= null;var tomCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "510100197512305609");

tomCard.PersonCodeNavigation= null;var samCard = dbContext.IdentificationCard.Include(e => e.PersonCodeNavigation).First(e => e.IdentificationNo == "510100197512305605");

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表的数据如下:

5c30ee5d30d5a5ef65afbc5e197014c2.png

IdentificationCard表的数据如下:

15393bedcfbdc7e0f6db0799ad080f12.png

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表的数据如下:

fc57b37e7a1b2de252d159b5277af931.png

IdentificationCard表的数据如下:

bff84a9c27e638016026fc005e20c760.png

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表的数据如下:

4f4fb9aac7342646582eb15bcae2aa90.png

IdentificationCard表的数据如下:

e60fccd1180503d2ee831d0be24125ba.png

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表的数据如下:

43ffe1ab96e5f3af319468fd106aec1f.png

IdentificationCard表的数据如下:

4e99178dc031d89d4d9a1f5483402849.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值