Skip to content

Orphans are deleted when overlapping composite alternate key is mutated #28961

@mrpmorris

Description

@mrpmorris

Given the following simple Parent/Child model, where Child is an aggregate part of Parent (and cannot exist without it).

internal class Parent
{
    public Guid Id { get; private set; } = Guid.NewGuid();
    public virtual List<Child> Children { get; private set; } = new();
}

internal class Child
{
    public Guid Id { get; private set; } = Guid.NewGuid();
    public Guid ParentId { get; set; }
    public virtual Parent Parent { get; set; } = null!;
}

I want to prohibit a Parent from being deleted if it has Children, and I also want any Childthat is removed fromParent.Children` to be automatically deleted on save.

internal class ApplicationDbContext : DbContext
{
    public DbSet<Parent> Parents { get; set; } = null!;
    private DbSet<Child> Children { get; set; } = null!;

    public ApplicationDbContext(DbContextOptions options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Child>()
            .HasOne(x => x.Parent)
            .WithMany(x => x.Children)
            .OnDelete(DeleteBehavior.Restrict);
    }
}

As per the documentation, this code throws an exception because Child.Parent cannot be null.

var options = new DbContextOptionsBuilder<ApplicationDbContext>()
    .UseInMemoryDatabase("Test")
    .Options;

using (var context1 = new ApplicationDbContext(options))
{
    var parent = new Parent();
    parent.Children.Add(new Child());

    context1.Parents.Add(parent);
    context1.SaveChanges();
}

using (var context2 = new ApplicationDbContext(options))
{
    var parent = context2.Parents.Include(x => x.Children).First();
    parent.Children.RemoveAt(0);
    context2.SaveChanges();
}

It seems that modelBuilder.Entity<Child>().HasAlternateKey(x => new { x.Id, x.ParentId }) has the behaviour I expected, but @ajcvickers said "I certainly wouldn't do it that way" - https://twitter.com/ajcvickers/status/1565633141880102914

What is the recommended practice to ensure the child is deleted instead?

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions