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.
9 Answer(s)
-
0
hi
Can you call the
await UnitOfWorkManager.Current.SaveChangesAsync();
afterDeleteAsync
?eg:
await this.VehicleRepository.DeleteAsync(vehicleId); await UnitOfWorkManager.Current.SaveChangesAsync(); var shipment = await _shipmentRepository.GetAsync(shipmentId); var count = shipment.Vehicles.Count;
-
0
hi
Can you call the
await UnitOfWorkManager.Current.SaveChangesAsync();
afterDeleteAsync
?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; } }
-
0
hi
Hi, I've tried this and doesn't work.
Can you share a simple project to show that?
Thanks.
liming.ma@volosoft.com
-
0
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.
-
0
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(); } }
-
0
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(); } }
-
0
-
0
-
0