现在我们在SQL Server数据库中有Person表如下:

CREATE TABLE [dbo].[Person](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Code] [nvarchar](50) NULL,
[Name] [nvarchar](50) NULL,
[ParentCode] [nvarchar](50) NULL,
[CreateTime] [datetime] NULL,
[UpdateTime] [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]
) ON [PRIMARY]
GO ALTER TABLE [dbo].[Person] ADD CONSTRAINT [DF_Person_CreateTime] DEFAULT (getdate()) FOR [CreateTime]
GO ALTER TABLE [dbo].[Person] ADD CONSTRAINT [IX_Person] UNIQUE NONCLUSTERED
(
[Code] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO ALTER TABLE [dbo].[Person] WITH CHECK ADD CONSTRAINT [FK_Person_Person] FOREIGN KEY([ParentCode])
REFERENCES [dbo].[Person] ([Code])
GO ALTER TABLE [dbo].[Person] CHECK CONSTRAINT [FK_Person_Person]
GO

语句中有一个外键FK_Person_Person,关系是[Person].[ParentCode]指向[Person].[Code],这是一个典型的父子关系表,[Person]表的一行数据属于另一行数据,关系可以无限递归。

现在我们使用EF Core的Scaffold-DbContext指令自动生成实体和DbContext,生成Person实体代码如下:

using System;
using System.Collections.Generic; namespace FFCoreView.Entities
{
public partial class Person
{
public Person()
{
InverseParentCodeNavigation = new HashSet<Person>();
} public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string ParentCode { get; set; }
public DateTime? CreateTime { get; set; }
public DateTime? UpdateTime { get; set; } public Person ParentCodeNavigation { get; set; }
public ICollection<Person> InverseParentCodeNavigation { get; set; }
}
}

我们可以看到Person实体中的属性public Person ParentCodeNavigation { get; set; }表示的是实体的父级Person。

Person实体中的属性public ICollection<Person> InverseParentCodeNavigation { get; set; }表示的是实体的所有子级Person。

再来看看Scaffold-DbContext指令自动生成的DbContext类TestDBContext:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata; namespace FFCoreView.Entities
{
public partial class TestDBContext : DbContext
{
public TestDBContext()
{
} public TestDBContext(DbContextOptions<TestDBContext> options)
: base(options)
{
} public virtual DbSet<Person> Person { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=Dtt!123456;Database=TestDB");
}
} protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(entity =>
{
entity.HasIndex(e => e.Code)
.HasName("IX_Person")
.IsUnique(); entity.Property(e => e.Id).HasColumnName("ID"); entity.Property(e => e.Code)
.IsRequired()
.HasMaxLength(); entity.Property(e => e.CreateTime)
.HasColumnType("datetime")
.HasDefaultValueSql("(getdate())"); entity.Property(e => e.Name).HasMaxLength(); entity.Property(e => e.ParentCode).HasMaxLength(); entity.Property(e => e.UpdateTime).HasColumnType("datetime"); //设置了Person实体ParentCodeNavigation属性和InverseParentCodeNavigation属性的父子关系(一对多关系,一个Person对多个Person)
entity.HasOne(d => d.ParentCodeNavigation)
.WithMany(p => p.InverseParentCodeNavigation)
.HasPrincipalKey(p => p.Code)
.HasForeignKey(d => d.ParentCode)
.HasConstraintName("FK_Person_Person");
});
}
}
}

可以看到TestDBContext类的OnModelCreating方法中,已经用Fluent API设置了Person实体ParentCodeNavigation属性和InverseParentCodeNavigation属性的父子关系(一对多关系,一个Person对多个Person)。

然后我们在.NET Core控制台项目的Program类中定义了两个静态方法:

  • AddPersonFromParentNavigation通过Person实体的ParentCodeNavigation属性插入父子关系数据到Person表
  • AddPersonFromChildrenNavigation通过Person实体的InverseParentCodeNavigation集合属性插入父子关系数据到Person表

还有个DeleteAllPersons静态方法,用于清除Person表的数据。在Main方法中我们还通过循环逐层显示了Person数据的父子关系。

我们先调用AddPersonFromParentNavigation方法通过Person实体的ParentCodeNavigation属性插入父子关系数据到Person表:

using FFCoreView.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq; namespace FFCoreView
{
class Program
{
/// <summary>
/// 通过Person实体的ParentCodeNavigation属性插入父子关系数据到Person表
/// </summary>
static void AddPersonFromParentNavigation()
{
//通过ParentCodeNavigation属性插入父子关系数据:James<-Tom<-Sam
using (TestDBContext dbContext = new TestDBContext())
{
Person james = new Person()
{
Code = "P001",
Name = "James"
}; Person tom = new Person()
{
Code = "P002",
Name = "Tom",
ParentCodeNavigation = james//通过ParentCodeNavigation属性指定Tom的父级是James
}; Person sam = new Person()
{
Code = "P003",
Name = "Sam",
ParentCodeNavigation = tom//通过ParentCodeNavigation属性指定Sam的父级是Tom
}; dbContext.Person.Add(james);
dbContext.Person.Add(tom);
dbContext.Person.Add(sam); dbContext.SaveChanges();
}
} /// <summary>
/// 通过Person实体的InverseParentCodeNavigation集合属性插入父子关系数据到Person表
/// </summary>
static void AddPersonFromChildrenNavigation()
{
//通过InverseParentCodeNavigation集合属性插入父子关系数据:James<-Tom<-Sam
using (TestDBContext dbContext = new TestDBContext())
{
Person james = new Person()
{
Code = "P001",
Name = "James",
InverseParentCodeNavigation = new List<Person>()//通过InverseParentCodeNavigation集合属性指定James的子级是Tom
{
new Person()
{
Code = "P002",
Name = "Tom",
InverseParentCodeNavigation = new List<Person>()//通过InverseParentCodeNavigation集合属性指定Tom的子级是Sam
{
new Person()
{
Code = "P003",
Name = "Sam"
}
}
}
}
}; dbContext.Person.Add(james);
dbContext.SaveChanges();
}
} /// <summary>
/// 删除数据库Person表的所有数据
/// </summary>
static void DeleteAllPersons()
{
using (TestDBContext dbContext = new TestDBContext())
{
dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Person]");
}
} static void Main(string[] args)
{
DeleteAllPersons();
AddPersonFromParentNavigation();//AddPersonFromParentNavigation和AddPersonFromChildrenNavigation方法调用一个就可以了
//AddPersonFromChildrenNavigation();//AddPersonFromParentNavigation和AddPersonFromChildrenNavigation方法调用一个就可以了 Person rootPerson;
using (TestDBContext dbContext = new TestDBContext())
{
//查询根级的Person "James",并使用EF Core中EagerLoading的Include方法查询下两级Person的InverseParentCodeNavigation集合
rootPerson = dbContext.Person.Where(p => p.Name == "James").Include("InverseParentCodeNavigation.InverseParentCodeNavigation").First();
} int level = ;//用于计算当前显示到第几级的Person实体了 //从rootPerson开始,通过Person实体的集合属性InverseParentCodeNavigation逐层遍历所有子Person集合,来显示Person的Name
while (true)
{
//显示当前Person的层级数和Name
Console.WriteLine($"Person level {level.ToString()} : {rootPerson.Name}"); if(rootPerson.InverseParentCodeNavigation.Count==)
{
//如果rootPerson.InverseParentCodeNavigation.Count为0表示父子关系已经循环完毕,没有子级的Person了,跳出循环
break;
} //设置rootPerson为子级Person,以便在循环中继续遍历
rootPerson = rootPerson.InverseParentCodeNavigation.First();
level++;//当前Person实体的层级数加1
} Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}
}
}

执行上面代码后,数据库Person表的数据如下所示:

控制台输出的内容如下:

如果我们在Main方法中注释掉AddPersonFromParentNavigation方法,改为使用AddPersonFromChildrenNavigation方法插入数据,会得到相同的结果。

使用EF Core删除父子关系数据

我们现在更改Program类Main方法的逻辑,来尝试删除父子关系的根级Person "James",代码如下:

static void Main(string[] args)
{
DeleteAllPersons();
AddPersonFromParentNavigation();//AddPersonFromParentNavigation和AddPersonFromChildrenNavigation方法调用一个就可以了
//AddPersonFromChildrenNavigation();//AddPersonFromParentNavigation和AddPersonFromChildrenNavigation方法调用一个就可以了 using (TestDBContext dbContext = new TestDBContext())
{
//查询根级的Person "James",不使用EF Core中EagerLoading的Include方法查询子级InverseParentCodeNavigation集合
Person james = dbContext.Person.Where(p => p.Name == "James").First(); dbContext.Person.Remove(james);
dbContext.SaveChanges();//尝试删除Person "James",结果报错:The DELETE statement conflicted with the SAME TABLE REFERENCE constraint "FK_Person_Person". 违反了外键的强制约束
} Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}

执行上面代码,会在DbContext.SaveChanges方法调用时抛出异常,如下所示:

使用EF Core的日志功能我们可以看到在执行DbContext.SaveChanges方法时,EF Core执行的SQL语句如下:

=============================== EF Core log started ===============================
Failed executing DbCommand (29ms) [Parameters=[@p0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p0;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================

该SQL语句相当于是执行了:

DELETE FROM [Person]
WHERE [Name]=N'James'

很明显这个删除操作违反了我们在数据库中Person表上定义的外键[FK_Person_Person]的强制约束,[FK_Person_Person]的外键定义如下:

--注意下面写了关键字WITH CHECK表示该外键是强制约束
ALTER TABLE [dbo].[Person] WITH CHECK ADD CONSTRAINT [FK_Person_Person] FOREIGN KEY([ParentCode])
REFERENCES [dbo].[Person] ([Code])
GO

所以很明显我们如果直接使用Delete语句删除Person表中父子关系的根级Person “Jame”,SQL Server会报错,当然有人可能会说如果我们设置外键不强制约束就不会报错了,但是这并不是一个好办法,我们来看看在强制约束下如何使用EF Core来删除Person表的根级Person。

下面我们继续更改Program类Main方法的逻辑,现在我们在读取Person表的根级Person “Jame”时,加上EF Core中EagerLoading的Include方法将Person "James"的子节点Person "Tom"也查询出来:

static void Main(string[] args)
{
DeleteAllPersons();
AddPersonFromParentNavigation();//AddPersonFromParentNavigation和AddPersonFromChildrenNavigation方法调用一个就可以了
//AddPersonFromChildrenNavigation();//AddPersonFromParentNavigation和AddPersonFromChildrenNavigation方法调用一个就可以了 using (TestDBContext dbContext = new TestDBContext())
{
//查询根级的Person "James",使用EF Core中EagerLoading的Include方法,将Person "James"的子节点Person "Tom"也查询出来
Person james = dbContext.Person.Where(p => p.Name == "James").Include("InverseParentCodeNavigation").First(); dbContext.Person.Remove(james);
dbContext.SaveChanges();//尝试删除Person "James",现在就不会报错了,数据在数据库中被成功删除
} Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}

我们看到这一次删除成功执行了,没有报错,那么为什么这次没有违反数据库外键 [FK_Person_Person]的强制约束呢?我们查看EF Core生成的后台日志如下:

=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Unchanged' to 'Deleted'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
SaveChanges starting for 'TestDBContext'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
DetectChanges starting for 'TestDBContext'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
DetectChanges completed for 'TestDBContext'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Unchanged' to 'Modified'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Foreign key property 'Person.ParentCode' detected as changed. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see property values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Opening connection to database 'TestDB' on server 'CNGDCAAITSQL01'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Opened connection to database 'TestDB' on server 'CNGDCAAITSQL01'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Beginning transaction with isolation level 'ReadCommitted'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executing DbCommand [Parameters=[@p1='?' (DbType = Int32), @p0='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [Person] SET [ParentCode] = @p0
WHERE [ID] = @p1;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executed DbCommand (23ms) [Parameters=[@p1='?' (DbType = Int32), @p0='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [Person] SET [ParentCode] = @p0
WHERE [ID] = @p1;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
A data reader was disposed.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executing DbCommand [Parameters=[@p2='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p2;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executed DbCommand (25ms) [Parameters=[@p2='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p2;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
A data reader was disposed.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Committing transaction.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Closing connection to database 'TestDB' on server 'CNGDCAAITSQL01'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Closed connection to database 'TestDB' on server 'CNGDCAAITSQL01'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Disposing transaction.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Deleted' to 'Detached'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Modified' to 'Unchanged'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
SaveChanges completed for 'TestDBContext' with 2 entities written to the database.
=============================== EF Core log finished ===============================

我们看到这次EF Core在执行DbContext.SaveChanges方法时执行了两个SQL语句,在执行Delete语句前还执行了一个Update语句,这个Update语句就是关键所在,它会先更新Person表中父子关系根级Person "James"的子节点Person "Tom"的ParentCode列为NULL,相当于下面的SQL语句:

UPDATE [Person] SET [ParentCode] = NULL
WHERE [Name] = N'Tom'

这样后面再用Delete语句删除Person "James"时,就不会违反数据库外键 [FK_Person_Person]的强制约束了,所以我们看到这次执行完Program类Main方法的代码后,数据库Person表的数据如下所示:

我们可以看到Person "Tom"的ParentCode列为NULL,这就是Update语句的效果。

那么有没有办法在删除Person表中父子关系的根级Person "James"时,让EF Core也删除"James"所有的子孙节点呢(也就是将Tom和Sam也同时删除掉)?

首先我们更改Scaffold-DbContext指令自动生成的DbContext类TestDBContext,将其OnModelCreating方法更改如下:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(entity =>
{
entity.HasIndex(e => e.Code)
.HasName("IX_Person")
.IsUnique(); entity.Property(e => e.Id).HasColumnName("ID"); entity.Property(e => e.Code)
.IsRequired()
.HasMaxLength(); entity.Property(e => e.CreateTime)
.HasColumnType("datetime")
.HasDefaultValueSql("(getdate())"); entity.Property(e => e.Name).HasMaxLength(); entity.Property(e => e.ParentCode).HasMaxLength(); entity.Property(e => e.UpdateTime).HasColumnType("datetime"); //设置了Person实体ParentCodeNavigation属性和InverseParentCodeNavigation属性的父子关系(一对多关系,一个Person对多个Person)
entity.HasOne(d => d.ParentCodeNavigation)
.WithMany(p => p.InverseParentCodeNavigation)
.HasPrincipalKey(p => p.Code)
.HasForeignKey(d => d.ParentCode)
.OnDelete(DeleteBehavior.Cascade)//设置父子表的级联删除
.HasConstraintName("FK_Person_Person");
});
}

和前面不一样的地方用绿色高亮标记出来了,这次我们使用了Fluent API的OnDelete方法声明Person实体的父子外键关系是DeleteBehavior.Cascade级联删除的。

现在我们再将Program类Main方法改为如下,我们这次在查询Person表父子关系的根级节点Person "James"时,用EF Core中EagerLoading的Include方法查询了下两级Person的InverseParentCodeNavigation集合,相当于还查询出了Person "Tom"和Person "Sam",然后删除Person "James:

static void Main(string[] args)
{
DeleteAllPersons();
AddPersonFromParentNavigation();//AddPersonFromParentNavigation和AddPersonFromChildrenNavigation方法调用一个就可以了
//AddPersonFromChildrenNavigation();//AddPersonFromParentNavigation和AddPersonFromChildrenNavigation方法调用一个就可以了 using (TestDBContext dbContext = new TestDBContext())
{
//查询根级的Person "James",并使用EF Core中EagerLoading的Include方法查询下两级Person的InverseParentCodeNavigation集合,相当于还查询出了Person "Tom"和Person "Sam"
Person james = dbContext.Person.Where(p => p.Name == "James").Include("InverseParentCodeNavigation.InverseParentCodeNavigation").First(); dbContext.Person.Remove(james);
dbContext.SaveChanges();//这次,父子关系的根级Person "James"及子孙级Person "Tom"和Person "Sam"被一起删除了
} Console.WriteLine("Press any key to quit...");
Console.ReadKey();
}

我们看看在执行DbContext.SaveChanges方法时,EF Core生成的后台日志:

=============================== EF Core log started ===============================
SaveChanges starting for 'TestDBContext'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
DetectChanges starting for 'TestDBContext'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
DetectChanges completed for 'TestDBContext'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Cascade state change of 'Person' entity to 'Deleted' due to deletion of parent 'Person' entity. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Unchanged' to 'Deleted'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Cascade state change of 'Person' entity to 'Deleted' due to deletion of parent 'Person' entity. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Unchanged' to 'Deleted'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Opening connection to database 'TestDB' on server 'CNGDCAAITSQL01'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Opened connection to database 'TestDB' on server 'CNGDCAAITSQL01'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Beginning transaction with isolation level 'ReadCommitted'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executing DbCommand [Parameters=[@p0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p0;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executed DbCommand (27ms) [Parameters=[@p0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p0;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
A data reader was disposed.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executing DbCommand [Parameters=[@p1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p1;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executed DbCommand (26ms) [Parameters=[@p1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p1;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
A data reader was disposed.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executing DbCommand [Parameters=[@p2='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p2;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executed DbCommand (25ms) [Parameters=[@p2='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [ID] = @p2;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
A data reader was disposed.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Committing transaction.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Closing connection to database 'TestDB' on server 'CNGDCAAITSQL01'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Closed connection to database 'TestDB' on server 'CNGDCAAITSQL01'.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Disposing transaction.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Deleted' to 'Detached'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Deleted' to 'Detached'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
An 'Person' entity tracked by 'TestDBContext' changed from 'Deleted' to 'Detached'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
SaveChanges completed for 'TestDBContext' with 3 entities written to the database.
=============================== EF Core log finished ===============================

我们看到由于我们在DbContext类TestDBContext的OnModelCreating方法中开启了级联删除,这次在执行DbContext.SaveChanges方法时,EF Core后台生成了三个Delete语句,其相当于如下SQL语句的效果:

DELETE FROM [Person]
WHERE [Name]=N'Sam' DELETE FROM [Person]
WHERE [Name]=N'Tom' DELETE FROM [Person]
WHERE [Name]=N'James'

所以Person “James”连同其子孙节点Person “Tom”和Person “Sam”被一起从数据库中删除了,最后数据库中Person表的数据如下所示:

因此我们看到在EF Core级联删除父子表的数据是可以的,但是有个最大的缺点,那就是必须先用EF Core中EagerLoading的Include方法查询出所有要删除的子孙节点,试想一下我们怎么知道Person表中的父子关系有多少级呢,在本例中因为我们知道Person表只有三级数据:James<-Tom<-Sam,所以我们在Program类Main方法中是这么写的查询:

//查询根级的Person "James",并使用EF Core中EagerLoading的Include方法查询下两级Person的InverseParentCodeNavigation集合,相当于还查询出了Person "Tom"和Person "Sam"
Person james = dbContext.Person.Where(p => p.Name == "James").Include("InverseParentCodeNavigation.InverseParentCodeNavigation").First();

但是实际场景中,我们很有可能不知道Person表的父子关系到底有多少级,所以我们无法知道上面的Include("InverseParentCodeNavigation.InverseParentCodeNavigation")中InverseParentCodeNavigation集合属性该写多少次。所以删除父子关系表数据最好的办法还是使用EF Core调用SQL Server中的存储过程,在存储过程中用递归算法删除父子关系数据。

EF Core 2.0 已经支持自动生成父子关系表的实体的更多相关文章

  1. EF Core 2.0中如何手动映射数据库的视图为实体

    由于Scaffold-DbContext指令目前还不支持自动映射数据库中的视图为实体,所以当我们想使用EF Core来读取数据库视图数据的时候,我们需要手动去做映射,本文介绍如何在EF Core中手动 ...

  2. EntityFramework Core 2.x (ef core) 在迁移中自动生成数据库表和列说明

    在项目开发中有没有用过拼音首字母做列名或者接手这样的项目? 看见xmspsqb(项目审批申请表)这种表名时是否有一种无法抑制的想肛了取名的老兄的冲动? 更坑爹的是这种数据库没有文档(或者文档老旧不堪早 ...

  3. ASP.NET Boilerplate 学习 AspNet Core2 浏览器缓存使用 c#基础,单线程,跨线程访问和线程带参数 wpf 禁用启用webbroswer右键菜单 EF Core 2.0使用MsSql/MySql实现DB First和Code First ASP.NET Core部署到Windows IIS QRCode.js:使用 JavaScript 生成

    ASP.NET Boilerplate 学习   1.在http://www.aspnetboilerplate.com/Templates 网站下载ABP模版 2.解压后打开解决方案,解决方案目录: ...

  4. ASP.NET Core 开发-Entity Framework (EF) Core 1.0 Database First

    ASP.NET Core 开发-Entity Framework Core 1.0 Database First,ASP.NET Core 1.0 EF Core操作数据库. Entity Frame ...

  5. EF Core 1.0中使用Include的小技巧

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:由于EF Core暂时不支持Lazy Loading,所以利用Include来加载额外 ...

  6. EF Core 2.0 新特性

    前言 目前 EF Core 的最新版本为 2.0.0-priview1-final,所以本篇文章主要是针对此版本的一些说明. 注意:如果你要在Visual Studio 中使用 .NET Core 2 ...

  7. EF Core 3.0 Preview 9 的2个小坑

    之前我们的数据库服务器使用的是 SQL Server 2008 R2 ,由于从 EF Core 3.0 Preview 6 开始不支持 UseRowNumberForPaging ,只能停留在 EF ...

  8. EF Core 1.0 和 SQLServer 2008 分页的问题

    EF Core 1.0 在sqlserver2008分页的时候需要指定用数字分页. EF Core1.0 生成的分页语句中使用了 Featch Next.这个语句只有在SqlServer2012的时候 ...

  9. EF Core 2.0使用MsSql/Mysql实现DB First和Code First

    参考地址 EF官网 ASP.NET Core MVC 和 EF Core - 教程系列 环境 Visual Studio 2017 最新版本的.NET Core 2.0 SDK 最新版本的 Windo ...

随机推荐

  1. 回顾经典问题算法:LIS, LCS-(DP类别)

    LIS,最长递增子序列说明见:http://blog.csdn.net/sdjzping/article/details/8759870 #include <iostream> #incl ...

  2. 洛谷P4716 【模板】最小树形图(朱刘算法)

    题意 题目链接 Sol 朱刘算法?感觉又是一种神仙贪心算法 大概就是每次贪心的用每个点边权最小的入边更新答案,如果不行的话就缩起来找其他的边 不详细说了,丢链接走人.. #include<bit ...

  3. 使用javascript调用android代码

    1.使用webview对象的addJavascriptInterface方法 2.addJavascriptInterface方法有两个参数,第一个参数就是我们一般会实现一个自己的类,类里面提供我们要 ...

  4. JavaScript的进阶之路(五)理解数组2

    数组方法 //定义一个测试数组 var array1 = [1,2,5,null,"a"]; //join()方法是String.split()方法的逆操作,后者是将字符串分割成若 ...

  5. centos配置网络

    [root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0DEVICE="eth0"BOOTPROTO=&qu ...

  6. Unable to update index for central http://repo1.maven.org/maven2/ 解决方法

    不知道什么原因 MyEclipse(eclipse) 中的 maven 插件突然不能用了,修改 pom.xml 无任何反应 控制台报 Unable to update index for centra ...

  7. MySQL 备份恢复(导入导出)单个 innodb表

    MySQL 备份恢复单个innodb表呢,对于这种恢复我们我们很多朋友都不怎么了解了,下面一起来看一篇关于MySQL 备份恢复单个innodb表的教程 在实际环境中,时不时需要备份恢复单个或多个表(注 ...

  8. ORM注意点

    add:是追加 set:是覆盖

  9. Python实例---抽屉热搜榜前端代码分析

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. MP4