Efcore: DbContext Cache issue. Deleted entities are still referenced by another after removing them from the db.

Created on 23 May 2018  路  2Comments  路  Source: dotnet/efcore

I'm using the EntityFrameworkCore together with the Sqlite Provider and noticed an issue, which I'm currently unable to solve. I'm not sure if this is an issue of the provider or the EF itself.

To demonstrate the problem I created a Demo Project, which includes a Test.

The interesting part is the following:

```c#
private static async Task RemoveAsync(DemoContext ctx)
{
var server = new Server() { Id = "adf34-ww33ggg-dfgg", Name = "First Server", URI = "https://localhost/" };
var c1 = new Client() { Id = 1, Server = server, Name = "Demo Client" };
var c2 = new Client() { Id = 3, Server = server };
var c3 = new Client() { Id = 10, Server = server };

await ctx.Clients.AddRangeAsync(c1, c2, c3);
await ctx.SaveChangesAsync();

var m1 = new Message() { Id = 1, Client = c2, Content = "My first message" };
var m2 = new Message() { Id = 2, Client = c1, Content = "My second message" };

await ctx.Messages.AddRangeAsync(m1, m2);
m2 = new Message() { Id = 3, Client = c1, Content = "The third one" };
await ctx.Messages.AddAsync(m2);

await ctx.SaveChangesAsync();

var m1key = m1.GetKey();

ctx.Clients.Remove(c2);
await Assert.ThrowsAsync<DbUpdateException>(() => ctx.SaveChangesAsync());

m1 = await ctx.Messages.FindAsync(m1key);
Assert.NotNull(m1);

ctx.Messages.Remove(m1);
await ctx.SaveChangesAsync();

ctx.Clients.Remove(c3);
await ctx.SaveChangesAsync();

}

private static async Task PostRemoveAsync(DemoContext ctx)
{
var server = await ctx.Servers.FirstAsync();
var c3 = await ctx.Clients.FindAsync(new Client { Id = 10, Server = server }.GetKey());
Assert.Null(c3);

var c2 = await ctx.Clients.FindAsync(new Client { Id = 3, Server = server }.GetKey());
Assert.Null(c2);

ctx.Servers.Remove(server);
await Assert.ThrowsAsync<DbUpdateException>(() => ctx.SaveChangesAsync());

var c1 = await ctx.Clients.FindAsync(new Client { Id = 1, Server = server }.GetKey());
Assert.NotNull(c1);

    // THE TEST FAILS HERE, because the server.Clients collection still contains all 3 clients
Assert.Equal(1, server.Clients.Count);
Assert.Contains(c1, server.Clients);

}


If the code is used like
```c#
// RemoveSameContext
using (var ctx = new DemoContext(options))
{
    await RemoveAsync(ctx);
    await PostRemoveAsync(ctx);
}

then the test will fail.

[xUnit.net 00:00:05.5872973]     EF.Demo.Test.ReadWriteMessages.RemoveSameContext [FAIL]
Failed   EF.Demo.Test.ReadWriteMessages.RemoveSameContext
Error Message:
 Assert.Equal() Failure
Expected: 1
Actual:   3
Stack Trace:
   at EF.Demo.Test.ReadWriteMessages.<PostRemoveAsync>d__6.MoveNext() in /home/travis/build/faryu/EntityFrameworkCore-Demo/EF.DemoDemo.Test/ReadWriteMessages.cs:line 145

If the methods are called separately, then the test will succeed.
```c#
// RemoveNewContext
using (var ctx = new DemoContext(options))
{
await RemoveAsync(ctx);
}
using (var ctx = new DemoContext(options))
{
await PostRemoveAsync(ctx);
}


So what is happening is, that in the first case the context still thinks, that the server object has 3 Clients, which it doesn't. 

This issue only occures when I use DeleteBehavior.Restrict. If I change it to Cascade, then it will be removed correctly. (With removed correctly I mean within the program. It worked correctly in both cases inside the database)

```c#
public class DemoContext : DbContext
{
    public DemoContext(DbContextOptions options) : base(options)
    {

    }

    public DbSet<Message> Messages { get; set; }
    public DbSet<Client> Clients { get; set; }
    public DbSet<Server> Servers { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        var se = modelBuilder.Entity<Server>();
        se.Property(e => e.Id).HasMaxLength(255);

        var ce = modelBuilder.Entity<Client>();
        ce.Property<string>("ServerId");

        ce.HasKey(nameof(Client.Id), "ServerId");
        ce.HasOne(e => e.Server)
            .WithMany(s => s.Clients)
            .HasForeignKey("ServerId")
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        var me = modelBuilder.Entity<Message>();
        me.Property<int>("ClientId");
        me.Property<string>("ServerId");

        me.HasKey(nameof(Message.Id), "ClientId", "ServerId");
        me.HasOne(e => e.Client)
            .WithMany((string)null)
            .HasForeignKey("ClientId", "ServerId")
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

    }
}

Further technical details

EF Core version: 2.1.0-rc1-final
Database Provider: Microsoft.EntityFrameworkCore.Sqlite
Operating system:
IDE: Visual Studio 2017 15.7.2

closed-fixed customer-reported type-bug

All 2 comments

Notes for triage: This comes down to the behavior of "Restrict". Specifically, when a dependent is deleted while the relationship is configured as Restrict it results in no changes to the parent navigation property after the change is saved. This seems incorrect--if the save succeeds, then the related entities should be fixed up to reflect that, regardless of whether or not the not the behavior is set to Restrict. This could result in breaking changes if apps are relying on the current behavior, but I'm not sure how common this might be--probably not common.

I also found this behavior to be unexpected. When I deleted an entity I would expect it to be removed from all other relationships. When converting my unit testing framework for EF from EF 6 to EF Core I noticed this difference. Hopefully EF Core will be updated to fix this issue.

Was this page helpful?
0 / 5 - 0 ratings