Simple Code
Simple Code

Reputation: 2574

Unable to delete entity which has owned entity in EntityFramework Core

I have the following entity:

public class Employee
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}
public class Address
{
    public string City { get; set; }
    public string State { get; set; }
}

And using fluent API I have configured the owned entity as following:

 private void ConfigureEmployee(EntityTypeBuilder<Employee> builder)
{
    builder.OwnsOne(x => x.Address, w =>
    {
        w.Property(x => x.City).HasMaxLength(100);
        w.Property(x => x.State).HasMaxLength(100);
    });
}

And when I try to delete Employee entity :

var employee = dbContext.Employees.AsNoTracking().FirstOrDefault();
dbContext.Entry(employee).State = EntityState.Deleted;
dbContext.SaveChanges();

I got the following exception:

The entity of type 'Employee' is sharing the table 'Employees' with entities of type 'Employee.Address#Address', but there is no entity of this type with the same key value '{Id: 1ad382d7-4064-49a3-87ee-633578175247}' that has been marked as 'Deleted'.

I have tried the workaround specified Here but it didn't work.

I am using EntityFrameworkCore v2.2.4

Upvotes: 1

Views: 3480

Answers (2)

jpgrassi
jpgrassi

Reputation: 5742

The problem you are facing is due to the way you load the entity. When you do:

var employee = dbContext.Employees.AsNoTracking().FirstOrDefault();

You're basically saying to EF: Load this Employee for me but forget about it. Treat it as if you never had loaded it in the first place.

Next, you want to delete it. You know that it's not being tracked by DbContext so you do:

dbContext.Entry(employee).State = EntityState.Deleted;

This is the "key" to the issue. This line tells the DbContext: Hey, Please start tracking this entity and mark it as to be deleted.

The issue: The Employee entity owns an address but the DbContext is not aware that you also want to delete it.

The error message you get offers a lot of insight on the actual error, but it might not be that clear at first sight:

The entity of type 'Employee' is sharing the table 'Employees' with entities of type 'Employee.Address#Address', but there is no entity of this type with the same key value '{Id: 1ad382d7-4064-49a3-87ee-633578175247}' that has been marked as 'Deleted'.

This is saying: The entity Employee with id 4 is marked as Deleted but it also has an entity of type Address which was not added to be deleted. Although you don't have Address declared as a DbSet in your context, it is still an actual Entity as far as EF is concerned.

To fix it while keeping the same code as you have you need to also add the Address to be deleted:

context.Entry(employee.Address).State = EntityState.Deleted;

But:

I'm not sure if this is just a sample or if it's your real code. But, I personally (and I see many also against it) try to avoid as much as possible manipulating the entity states manually. This can get nasty pretty quickly and produce, as you already experienced it not so obvious results. Instead, if you have a DbContext which you load the entity you want to delete, you can avoid messing with states and problems by just changing your code to this:

var employee = dbContext.Employees.First();

// .Remove will also mark the related entities to be deleted
// If Employee is not tracked, it will also start tracking it
// So, it's not necessary to do .Attach()
dbContext.Remove(employee);

dbContext.SaveChanges();

This will work and the entity will be deleted as expected. Of course, if your code is not that and you are, in fact working with entities in a disconnected scenario, then you need to manually set it to be deleted as I showed above.

Edit:

As pointed out in the comments by @IvanStoev, the Remove method is what actually can fix the behavior you are facing. The Remove method will mark the entity itself plus the related ones as Deleted and if not previously tracked, will also start tracking them. From the docs: (emphasis from me)

If the entity is already tracked in the Added state then the context will stop tracking the entity (rather than marking it as Deleted) since the entity was previously added to the context and does not exist in the database.

Any other reachable entities that are not already being tracked will be tracked in the same way that they would be if Attach(Object) was called before calling this method. This allows any cascading actions to be applied when SaveChanges() is called.

DbContext.Remove Method

Upvotes: 3

SUNIL DHAPPADHULE
SUNIL DHAPPADHULE

Reputation: 2863

You have to use cascade delete like below:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Employee>()
        .HasOptional<Standard>(s => s.Address)
        .WithMany()
        .WillCascadeOnDelete(false);
}

Upvotes: 0

Related Questions