• 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中的存储过程,在存储过程中用递归算法删除父子关系数据。

  • 相关阅读:
    ASP.NET Core 2.2 : 二十七. JWT与用户授权(细化到Action)
    ASP.NET Core 2.2 : 二十六. 应用JWT进行用户认证及Token的刷新
    ASP.NET Core 发布到Linux需要注意的地方
    小程序根据数字做for循环
    Visual Studio 2019 正式版 更新内容
    CodeSmith 二、多模板按目录树批量自动生成代码
    CodeSmith 一、连接Mysql
    ASP.NET Core 2.2 十九. Action参数的映射与模型绑定
    ASP.NET Core 2.2 十八.各种Filter的内部处理机制及执行顺序
    ASP.NET Core 2.2 : 十七.Action的执行(Endpoint.RequestDelegate后面的故事)
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9808026.html
Copyright © 2020-2023  润新知