Reputation: 13
First time posting around here. When dealing with EF Core in my current project, I saw something funny on a specific scenario I have to implement.
The main deal goes as the following:
The issue I've found is that I have to perform that logic before calling SaveChanges()
, so when I queried the parent entity, the child entities that were flagged for deletion are still within the reach of the parent entity, and as a result of not being detected as empty it can't be deleted.
So moving to the second scenario, I decided to try this only with the child entities.
So if I query a set of entities, I delete them and I try to retrieve it again before SaveChanges
, I get the same list. Is there a way to avoid this behavior? Is it normal? What are my options? So far we can't use the TransactionScope
(mostly due to design issues) or EF's transaction.
Here is a bit of a sample code I made that reproduces this behavior.
// Test class
namespace EfCoreTest
{
[Table("MyEntity")]
public class MyEntity
{
[Required]
public Guid Id { get; set; }
}
}
// DB Context
namespace EfCoreTest
{
public class EfCoreTestDbContext : DbContext
{
public static string SQL_CONNECTION_STRING = @"<YOUR_DB_STRING_HERE>";
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(SQL_CONNECTION_STRING);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Seed();
}
public DbSet<MyEntity> MyEntities { get; set; }
}
}
// Program.cs, it's a net core 3.1 console application
namespace EfCoreTest
{
public static class Program
{
static void Main(string[] args)
{
using (var context = new EfCoreTestDbContext())
{
context.Set<MyEntity>().AddRange(new[]
{
new MyEntity { Id = Guid.NewGuid() },
new MyEntity { Id = Guid.NewGuid() }
});
var entitiesBeforeConfirmation = context.Set<MyEntity>().ToList();
context.SaveChanges();
var entitiesAfterConfirmation = context.Set<MyEntity>().ToList();
Console.WriteLine($"Entities retrieved before SaveChanges: { entitiesBeforeConfirmation.Count }"); // 0
Console.WriteLine($"Entities retrieved after SaveChanges: { entitiesAfterConfirmation.Count }"); // 2
var entities = context.Set<MyEntity>().ToList();
context.Set<MyEntity>().RemoveRange(entities);
var entitiesAgain = context.Set<MyEntity>().ToList();
Console.WriteLine($"Entities retrieved: { entities.Count }"); // 2
Console.WriteLine($"Entities retrieved after deletion without SaveChanges: { entitiesAgain.Count }"); // 2
context.SaveChanges();
var entitiesAgainAfterConfirmation = context.Set<MyEntity>().ToList();
Console.WriteLine($"Entities retrieved after SaveChanges: { entitiesAgainAfterConfirmation.Count }"); // 0
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
}
Upvotes: 1
Views: 2580
Reputation: 5261
You have a few options:
SaveChanges
in-between your deletions. "Queries" in EF are sent to the DB and not checked against the currently tracked entities, for good reason: you can never be sure that another process have not changed data in the DB which means the change tracker will never give you the full picture.context.MyEntity.Select(me => new { me.Id, me.ParentId })
to only include those columns/properties. But still, if you have a very large table, this is probably still problematic.SaveChanges
the database trigger will recursively check if a parent should also be deletedI believe option 4 will be the most performant if that's you focus.
Upvotes: 3