Open Closed

ISoftDelete not applied post delete in same execution even after flushing cache #9286


User avatar
0
AutosledDBP created

I'm encountering an issue related to ISoftDelete not working as expected in a parent-child relationship within the same execution context. I have an ABP 8.2.1 solution with multiple microservices. In this issue it is contained in one microservice application which is my core service.

Setup I have a CoreServiceDbContext configured as follows:

[ConnectionStringName(CoreServiceDbProperties.ConnectionStringName)]
public class CoreServiceDbContext : MyDbContext<CoreServiceDbContext>
{
    public CoreServiceDbContext(DbContextOptions<CoreServiceDbContext> options)
        : base(options)
    {
    }

    public DbSet<Shipment> Shipments { get; set; }
    public DbSet<Vehicle> Vehicles { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.ConfigureCoreService();
    }
}

Extension method used in model creation:

public static class CoreServiceDbContextModelCreatingExtensions
{
    public static void ConfigureCoreService(this ModelBuilder builder)
    {
        Check.NotNull(builder, nameof(builder));

        if (builder.IsHostDatabase())
        {
            builder.Entity<Shipment>(b =>
            {
                b.ToTable("Shipments");
                b.HasMany(x => x.Vehicles)
                 .WithOne(x => x.Shipment)
                 .OnDelete(DeleteBehavior.Cascade);
                b.ConfigureByConvention();
            });

            builder.Entity<Vehicle>(b =>
            {
                b.ToTable("Vehicles");
                b.ConfigureByConvention();
            });
        }
    }
}

The parent entity Shipment looks like this:

public class Shipment : FullAuditedAggregateRoot<Guid>
{
    public virtual ICollection<Vehicle> Vehicles { get; set; }

    public virtual int VehicleCount { get; set; }
}

and Vehicle:

public class Vehicle : FullAuditedAggregateRoot<Guid>
{
   // More properties here
}

Repository Structure

The Vehicle entity is managed via a custom repository chain:

public interface IVehicleRepository : ICustomRepository<Vehicle, Guid> { }

public interface ICustomRepository<TEntity, TKey> : IRepository<TEntity, TKey>
    where TEntity : class, IEntity<TKey>
{
    Task<TEntity> GetAsync(TKey id, bool includeDetails, params Expression<Func<TEntity, object>>[] propertySelectors);

    Task<IEnumerable<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails, params Expression<Func<TEntity, object>>[] propertySelectors);
}

EF Core Implementation:

public abstract class EfCoreServiceRepository<TDbContext, TEntity, TKey> : EfCoreRepository<TDbContext, TEntity, TKey>, ICustomRepository<TEntity, TKey>
    where TDbContext : IEfCoreDbContext
    where TEntity : class, IEntity<TKey>
{
    public EfCoreServiceRepository(IDbContextProvider<TDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }
}

public class EfCoreVehicleRepository : EfCoreServiceRepository<CoreServiceDbContext, Vehicle, Guid>, IVehicleRepository
{
    public EfCoreVehicleRepository(IDbContextProvider<CoreServiceDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }
}

The repository structure for Shipment is the same.

Problem

When I delete a Vehicle like this:

await this.VehicleRepository.DeleteAsync(vehicleId);

And then I fetch it's parent entity, the parent still includes the soft-deleted vehicle if the GetAsync from shipment repository is executed right after the delete, in the same async execution.

var shipment = await _shipmentRepository.GetAsync(shipmentId);
var count = shipment.Vehicles.Count; // Still includes recently soft-deleted vehicle, therefore this number is always 1 more than what it should be.

What I tried:

  • I flushed the Redis cache entirely with redis-cli flushall, to rule out distributed caching — no effect.
  • I enabled autoSave in the DeleteAsync call — no change.

It appears that EF Core combined with ABP does not re-evaluate global query filter ISoftDelete for navigation properties.

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

9 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you call the await UnitOfWorkManager.Current.SaveChangesAsync(); after DeleteAsync?

    eg:

    await this.VehicleRepository.DeleteAsync(vehicleId);
    
    await UnitOfWorkManager.Current.SaveChangesAsync();
    
    var shipment = await _shipmentRepository.GetAsync(shipmentId);
    var count = shipment.Vehicles.Count;
    
    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    AutosledDBP created

    hi

    Can you call the await UnitOfWorkManager.Current.SaveChangesAsync(); after DeleteAsync?

    eg:

    await this.VehicleRepository.DeleteAsync(vehicleId); 
     
    await UnitOfWorkManager.Current.SaveChangesAsync(); 
     
    var shipment = await _shipmentRepository.GetAsync(shipmentId); 
    var count = shipment.Vehicles.Count; 
    

    Hi, I've tried this and doesn't work. Only work around I have is to add the following which I'd rather not do, which uses what you told me

    public class EfCoreShipmentRepository : EfCoreServiceRepository<CoreServiceDbContext, Shipment, Guid>, IShipmentRepository
    {
        public EfCoreShipmentRepository(IDbContextProvider<CoreServiceDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }
    
        public override async Task<Shipment> GetAsync(Guid id, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            await this.UnitOfWorkManager.Current.SaveChangesAsync(cancellationToken);
            var dbContext = await this.GetDbContextAsync();
            var shipment = await base.GetAsync(id, includeDetails, cancellationToken);
            shipment.Vehicles = await dbContext.Vehicles
                .Where(x => x.ShipmentId == id)
                .ToListAsync(cancellationToken);
            return shipment;
        }
    
    }
    
    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Hi, I've tried this and doesn't work.

    Can you share a simple project to show that?

    Thanks.

    liming.ma@volosoft.com

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    AutosledDBP created

    hi

    Hi, I've tried this and doesn't work.

    Can you share a simple project to show that?

    Thanks.

    liming.ma@volosoft.com

    Hi, sorry for the delay. Sent simple project to show it via email.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Try this:

    private async Task UpdateVehicleCount(Guid shipmentId)
    {
        using (var uow = this.UnitOfWorkManager.Begin(requiresNew: true))
        {
            var shipment = await this.ShipmentRepository.GetAsync(shipmentId);
            shipment.VehicleCount = shipment.Vehicles.Count;
            await this.ShipmentRepository.UpdateAsync(shipment);
    
            await uow.CompleteAsync();
        }
    }
    
    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    AutosledDBP created

    Hi, it does not work either. Won't update count.

    hi

    Try this:

    private async Task UpdateVehicleCount(Guid shipmentId) 
    { 
        using (var uow = this.UnitOfWorkManager.Begin(requiresNew: true)) 
        { 
            var shipment = await this.ShipmentRepository.GetAsync(shipmentId); 
            shipment.VehicleCount = shipment.Vehicles.Count; 
            await this.ShipmentRepository.UpdateAsync(shipment); 
     
            await uow.CompleteAsync(); 
        } 
    } 
    
    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Here is my test code:

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    AutosledDBP created

    Hi here's mine. I'm running the solution and executing the endpoints from the swagger. My database for this test is MS SQL Server. Don't know what else can I supply to further help understand why it's happening on my end.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    add this line await UnitOfWorkManager.Current!.SaveChangesAsync();

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 09, 2026, 11:56
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.