Search code examples
entity-framework-core

Can't delete from many-to-many relationship


I have what appears to be a textbook example of an indirect many-to-many relationship.

I can retrieve and add new entities to the relationship but cannot remove them.

From all the examples I've followed it just doesn't work.

Data models:

public class Job
{
    public int JobId { get; set; }
    public int AccountId { get; set; }
    public string Name  { get; set; }

    public virtual Account PrimaryAccount { get; set; }
    public virtual ICollection<AccountAlert>? AccountAlerts { get; set; }
}

public class Account
{
    public int AccountId { get; set; }
    public string Name  { get; set; }

    public virtual ICollection<AccountAlert>? AccountAlerts { get; set; }
}

public class AccountAlert
{
    public int AccountId { get; set; }
    public int JobId { get; set; }
    
    public virtual Account Account { get; set; }
    public virtual Job Job { get; set; }
}

DbContext:

public class DataContext : DbContext
{
    public DbSet<Account> Accounts { get; set; }
    public DbSet<Job> Jobs { get; set; }
    public DbSet<AccountAlert> AccountAlerts { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSnakeCaseNamingConvention();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Account>().ToTable("account");
        modelBuilder.Entity<Job>().ToTable("job");
        modelBuilder.Entity<AccountAlert>().ToTable("account_alert").HasKey(x => new { x.JobId, x.AccountId });
    }
}

Remove code:

var job = _dataContext.Set<Job>()
                      .Where(x => x.JobId == jobId)
                      .Include("AccountAlerts")
                      .Include("AccountAlerts.Account")
                      .AsNotTracked()
                      .Single();

foreach(var alert in job.AccountAlerts)
{
    // Doesn't work
    job.AccountAlerts.Remove(alert);
    
    // Doesn't work
    job.AccountAlerts.Remove(new AccountAlert { JobId = alert.JobId, AccountId = alert.AccountId });
    
    // Doesn't work
    job.AccountAlerts.Remove(new AccountAlert { AccountId = alert.AccountId });
}

await _dataContext.SaveChangesAsync();

Solution

  • Another solution to the problem besides the one suggested by Steve Py(which should work) is to delete directly from the mapping table:

    // you can take accountAlerts by AccountId or JobId
    var accountAlerts = await _dataContext.AccountAlerts
                       .Where(x => accountIds.Contains(x.AccountId))
                       .ToListAsync();
    
    context2.RemoveRange(accountAlerts);
    await context2.SaveChangesAsync();
    

    Or if you want to clear all alerts in the job just use Clear()

    var job = await _dataContext.Set<Job>()
                      .Where(x => x.JobId == jobId)
                      .Include(x => x.AccountAlerts)
                      .FirstOrDefaultAsync();
    
    if (job is not null)
    {
        job.AccountAlerts.Clear();
        await context2.SaveChangesAsync();
    }