You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于.NET Core 6 EF Core实现课程解绑ScenarioSet时级联删除SelectedScenarios

解决方案:EF Core 6中实现课程解除ScenarioSet关联时的级联删除逻辑

由于你需要的删除逻辑是跨表关联触发(SelectedScenarios关联Course和Scenario,而Scenario归属ScenarioSet),EF Core默认的外键级联删除无法直接覆盖这种场景,以下提供两种可行的实现方式:


一、先完善数据模型(添加导航属性)

为了更方便地关联查询,建议给实体添加导航属性,让EF Core能正确识别关联关系:

public class Course
{
    public int Id { get; set; }
    public int ScenarioSetId { get; set; }
    public virtual ScenarioSet ScenarioSet { get; set; }
    public virtual ICollection<SelectedScenarios> SelectedScenarios { get; set; } = new List<SelectedScenarios>();
}

public class ScenarioSet
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Scenario> Scenarios { get; set; } = new List<Scenario>();
}

public class Scenario
{
    public int Id { get; set; }
    public int ScenarioSetId { get; set; }
    public virtual ScenarioSet ScenarioSet { get; set; }
    public virtual ICollection<SelectedScenarios> SelectedScenarios { get; set; } = new List<SelectedScenarios>();
}

public class SelectedScenarios
{
    public int Id { get; set; }
    public int CourseId { get; set; }
    public int ScenarioId { get; set; }
    public virtual Course Course { get; set; }
    public virtual Scenario Scenario { get; set; }
}

二、实现方式一:手动处理删除逻辑

在更新课程的ScenarioSet关联时,手动查询并删除对应SelectedScenarios记录,适合场景单一的业务场景:

// 示例:更新课程关联的ScenarioSet
var targetCourseId = 1;
var newScenarioSetId = 3; // 新的ScenarioSet ID,设为0或其他值表示解除关联

var course = await context.Courses
    .Include(c => c.SelectedScenarios)
    .ThenInclude(ss => ss.Scenario)
    .FirstOrDefaultAsync(c => c.Id == targetCourseId);

if (course != null)
{
    var originalScenarioSetId = course.ScenarioSetId;
    // 仅当原关联与新关联不同时执行删除
    if (originalScenarioSetId != newScenarioSetId)
    {
        // 筛选该课程下属于原ScenarioSet的选中场景
        var recordsToDelete = course.SelectedScenarios
            .Where(ss => ss.Scenario.ScenarioSetId == originalScenarioSetId)
            .ToList();
        
        context.SelectedScenarios.RemoveRange(recordsToDelete);
    }

    // 更新课程的关联ID
    course.ScenarioSetId = newScenarioSetId;
    await context.SaveChangesAsync();
}

如果追求性能,可以直接执行SQL语句跳过实体加载:

await context.Database.ExecuteSqlInterpolatedAsync(
    @"DELETE FROM SelectedScenarios 
      WHERE CourseId = {targetCourseId} 
      AND ScenarioId IN (SELECT Id FROM Scenarios WHERE ScenarioSetId = {originalScenarioSetId})"
);

三、实现方式二:用SaveChanges拦截器自动处理

通过EF Core的拦截器,在保存更改时自动检测Course的ScenarioSet关联变化,执行删除逻辑,适合多处需要更新课程关联的场景:

1. 创建拦截器类

public class CourseScenarioSetChangeInterceptor : SaveChangesInterceptor
{
    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        if (context == null)
            return await base.SavingChangesAsync(eventData, result, cancellationToken);

        // 找出所有被修改的Course实体
        var modifiedCourses = context.ChangeTracker
            .Entries<Course>()
            .Where(e => e.State == EntityState.Modified && e.Property(nameof(Course.ScenarioSetId)).IsModified)
            .ToList();

        foreach (var courseEntry in modifiedCourses)
        {
            var originalScenarioSetId = (int)courseEntry.Property(nameof(Course.ScenarioSetId)).OriginalValue;
            var newScenarioSetId = (int)courseEntry.Property(nameof(Course.ScenarioSetId)).CurrentValue;

            if (originalScenarioSetId != newScenarioSetId)
            {
                var courseId = courseEntry.Entity.Id;
                // 查询并删除符合条件的SelectedScenarios记录
                var recordsToDelete = await context.SelectedScenarios
                    .Where(ss => ss.CourseId == courseId)
                    .Join(context.Scenarios,
                          ss => ss.ScenarioId,
                          s => s.Id,
                          (ss, s) => new { SelectedScenario = ss, Scenario = s })
                    .Join(context.ScenarioSets,
                          j => j.Scenario.ScenarioSetId,
                          ss => ss.Id,
                          (j, ss) => j.SelectedScenario)
                    .Where(ss => ss.Scenario.ScenarioSetId == originalScenarioSetId)
                    .ToListAsync(cancellationToken);

                context.SelectedScenarios.RemoveRange(recordsToDelete);
            }
        }

        return await base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

2. 在DbContext中注册拦截器

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer("你的数据库连接字符串")
        .AddInterceptors(new CourseScenarioSetChangeInterceptor());
}

两种方式对比

  • 手动处理:逻辑简单直接,无需额外配置,但需要在所有更新Course关联的业务代码中重复实现,容易遗漏。
  • 拦截器方式:自动触发逻辑,无需在业务代码中重复编写,维护性更强,但需要理解EF Core的变更追踪和拦截器机制。

内容的提问来源于stack exchange,提问作者Alt95

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 07:53:20