EF Core 2.0 已經支持自動生成父子關系表的實體


現在我們在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(50);

                entity.Property(e => e.CreateTime)
                    .HasColumnType("datetime")
                    .HasDefaultValueSql("(getdate())");

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

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

                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 = 1;//用於計算當前顯示到第幾級的Person實體了

            //從rootPerson開始,通過Person實體的集合屬性InverseParentCodeNavigation逐層遍歷所有子Person集合,來顯示Person的Name
            while (true)
            {
                //顯示當前Person的層級數和Name
                Console.WriteLine($"Person level {level.ToString()} : {rootPerson.Name}");

                if(rootPerson.InverseParentCodeNavigation.Count==0)
                {
                    //如果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(50);

        entity.Property(e => e.CreateTime)
            .HasColumnType("datetime")
            .HasDefaultValueSql("(getdate())");

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

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

        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中的存儲過程,在存儲過程中用遞歸算法刪除父子關系數據。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM