具有细节和时间的多对多

问题描述 投票:0回答:1

我有两个多对多关系的表。我使用时态表来跟踪记录的更改,以便我可以查看更新和删除以进行审核或比较更改。在这种情况下,我有很多老师和很多课程。每位教师可以教授多门课程,每门课程也可以由多位教师教授。对于每个

Teacher_Courses
,还有有关该特定班级的数据,例如 NumberOfStudents 或 ClassPreferences。在数据库中,仅仅因为课程被删除,我不想删除有关该课程的详细信息
Teacher_Courses
,因为系统中的其他对象依赖于该数据。因此,我将这些详细信息添加到
Teacher_CoursesDetails
表中。我想设计一组表,也使用时态表,这样:

  • 允许删除 CourseClass 记录(将添加到临时表中)
  • 删除与已删除的 CourseClass 记录关联的 Teacher_Courses 记录 保留 Teacher_CoursesDetails 记录

请勿使用“软删除”执行此操作。这将打破数据库设计其余部分的模式。

保留时态表,以便在各自的时态表中跟踪所有更新/删除

public class TeacherClass
{
    public int Id { get; set; }
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;   
    public ICollection<Teacher_Courses> Teacher_Courses { get; set; }
}

public class Teacher_Courses 
{    
    public int Id { get; set; }  // Add a primary key
    public int TeacherId { get; set; }
    public int CourseId { get; set; }     
    public TeacherClass TeacherClass { get; set; }
    public CourseClass CourseClass { get; set; }
    public Teacher_CoursesDetails Details { get; set; }
}

public class Teacher_CoursesDetails 
{      
    public int Id { get; set; }  // Add a primary key
    public string ClassPreferences { get; set; } = string.Empty;
    public int NumberOfStudents { get; set; }
    public int Teacher_CoursesId { get; set; }
    public Teacher_Courses Teacher_Courses { get; set; }
}

public class CourseClass 
{
    public int Id { get; set; }
    public string CourseName { get; set; } = string.Empty;
    public string CourseDescription { get; set; } = string.Empty;     
    public ICollection<Teacher_Courses> Teacher_Courses { get; set; } 
}

public class AppDbContext : DbContext
{
    public DbSet<TeacherClass> TeacherClass { get; set; }
    public DbSet<Teacher_Courses> Teacher_Courses { get; set; }
    public DbSet<CourseClass> CourseClass { get; set; }
    public DbSet<Teacher_CoursesDetails> Teacher_CoursesDetails { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<TeacherClass>().ToTable("TeacherClass", c => c.IsTemporal());
        builder.Entity<CourseClass>().ToTable("CourseClass", c => c.IsTemporal());
        
        builder.Entity<Teacher_Courses>(entity =>
        {
            entity.ToTable("Teacher_Courses", c => c.IsTemporal());
            entity.HasKey(e => e.Id);
            entity.HasOne(e => e.TeacherClass)
                .WithMany(s => s.Teacher_Courses)
                .HasForeignKey(e => e.TeacherId)
                .OnDelete(DeleteBehavior.Restrict);
            entity.HasOne(e => e.CourseClass)
                .WithMany(c => c.Teacher_Courses)
                .HasForeignKey(e => e.CourseId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        builder.Entity<Teacher_CoursesDetails>(entity =>
        {
            entity.ToTable("Teacher_CoursesDetails", c => c.IsTemporal());
            entity.HasKey(e => e.Id);
            entity.HasOne(d => d.Teacher_Courses)
                .WithOne(t => t.Details)
                .HasForeignKey<Teacher_CoursesDetails>(d => d.Teacher_CoursesId)
                .OnDelete(DeleteBehavior.NoAction);
        });
    }
}

这里的问题是,当我尝试删除教师时,如果 Teacher_Courses 中有记录,我将收到 FK 约束错误。我可以将 Teacher_CoursesDetails OnDelete 设置为 SetNull,但是当用户删除教师时,它会将 Teacher_CoursesDetailsId 设置为 null,然后我将无法查询数据。有没有办法解决这个问题或者有更好的方法吗?

.net sql-server entity-framework database-design
1个回答
0
投票

使用建议的软删除选项似乎与使用临时表的好处并不相符,我认为临时表应该跟踪所有更改,包括删除。 我最终做的是,在我想要防止删除的表中,将 DeleteBehavior 更改为 ClientSetNull。 这样,当父对象被删除时,子记录只是将 KF 设置为 null。 然后,我编写了中间件(使用 SaveChangesInterceptor),将另一个字段(我称为 ProxyFK)设置为已删除键的值。这样,我就可以根据它进行查询。 是否有理由认为这不是一种可接受的方法?

public class DeletedObjectInterceptor : SaveChangesInterceptor
 {
     public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
     {
         var context = eventData.Context;
         if (context is null) return result;

         HandleDeletedConnectors(context);
         return result;
     }

     public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
     {
         var context = eventData.Context;
         if (context is null) return ValueTask.FromResult(result);

         HandleDeletedConnectors(context);
         return ValueTask.FromResult(result);
     }

     private void HandleDeletedConnectors(DbContext context)
     {
         var deletedItems = context.ChangeTracker.Entries<CourseClass>()
             .Where(e => e.State == EntityState.Deleted)
             .Select(e => e.Entity)
             .ToList();

         foreach (var c in deletedItems)
         {
             var relatedItems = context.Set<Teacher_Courses>()
                 .Where(mc => mc.CourseId == c.Id)
                 .ToList();

             foreach (var item in relatedItems)
             {
                 c.ProxyId = item.ConnectorId;                  
                 context.Entry(c).State = EntityState.Modified;
             }           
         }
     }
 }

在 Program.cs 中:

services.AddDbContext<MyDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
    options.AddInterceptors(new DeletedObjectInterceptor());
});
© www.soinside.com 2019 - 2024. All rights reserved.