Browse Source

Respect entity tracking by forcing AsTracking

pull/24913/head
maliming 7 months ago
parent
commit
4bac0f1d76
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs
  2. 27
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
  3. 91
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs

2
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs

@ -49,6 +49,6 @@ public static class EfCoreRepositoryExtensions
public static IQueryable<TEntity> AsNoTrackingIf<TEntity>(this IQueryable<TEntity> queryable, bool condition)
where TEntity : class, IEntity
{
return condition ? queryable.AsNoTracking() : queryable;
return condition ? queryable.AsNoTracking() : queryable.AsTracking();
}
}

27
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs

@ -476,30 +476,9 @@ public class EfCoreRepository<TDbContext, TEntity, TKey> : EfCoreRepository<TDbC
public virtual async Task<TEntity?> FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
{
if (includeDetails)
{
return await (await WithDetailsAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken));
}
if (!ShouldTrackingEntityChange())
{
return await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken));
}
var dbSet = await GetDbSetAsync();
var entity = await dbSet.FindAsync(new object[] { id! }, GetCancellationToken(cancellationToken));
if (entity == null)
{
return null;
}
if (dbSet.Entry(entity).State == EntityState.Detached)
{
dbSet.Attach(entity);
}
return entity;
return includeDetails
? await (await WithDetailsAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken))
: await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken));
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)

91
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Shouldly;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.ChangeTracking;
@ -83,6 +84,96 @@ public class ChangeTrackingInterceptor_Tests : TestAppTestBase<AbpEntityFramewor
});
}
[Fact]
public async Task Repository_Should_Override_Global_NoTracking_When_EntityChangeTracking_Is_Enabled()
{
await AddSomePeopleAsync();
var repository = GetRequiredService<IRepository<Person, Guid>>();
Guid personId = default;
await WithUnitOfWorkAsync(async () =>
{
var p = await repository.FindAsync(x => x.Name == "people1");
p.ShouldNotBeNull();
personId = p.Id;
});
// Simulate global NoTracking configured on DbContext (e.g. optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking))
await WithUnitOfWorkAsync(async () =>
{
var db = await repository.GetDbContextAsync();
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
db.ChangeTracker.Entries().Count().ShouldBe(0);
// FindAsync(id): ShouldTrackingEntityChange()=true, GetQueryableAsync() uses AsTracking() to override global NoTracking
var person = await repository.FindAsync(personId, includeDetails: false);
person.ShouldNotBeNull();
db.ChangeTracker.Entries<Person>().Count().ShouldBe(1);
db.Entry(person).State.ShouldBe(EntityState.Unchanged);
});
await WithUnitOfWorkAsync(async () =>
{
var db = await repository.GetDbContextAsync();
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
db.ChangeTracker.Entries().Count().ShouldBe(0);
// FindAsync(predicate): same - AsTracking() overrides global NoTracking
var person = await repository.FindAsync(x => x.Name == "people1");
person.ShouldNotBeNull();
db.ChangeTracker.Entries<Person>().Count().ShouldBe(1);
db.Entry(person).State.ShouldBe(EntityState.Unchanged);
});
await WithUnitOfWorkAsync(async () =>
{
var db = await repository.GetDbContextAsync();
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
db.ChangeTracker.Entries().Count().ShouldBe(0);
// GetListAsync: same - AsTracking() overrides global NoTracking
var list = await repository.GetListAsync();
list.Count.ShouldBeGreaterThan(0);
db.ChangeTracker.Entries<Person>().Count().ShouldBe(list.Count);
});
}
[Fact]
public async Task Repository_Should_Respect_NoTracking_When_EntityChangeTracking_Is_Disabled_With_Global_NoTracking()
{
await AddSomePeopleAsync();
var repository = GetRequiredService<IRepository<Person, Guid>>();
Guid personId = default;
await WithUnitOfWorkAsync(async () =>
{
var p = await repository.FindAsync(x => x.Name == "people1");
p.ShouldNotBeNull();
personId = p.Id;
});
await WithUnitOfWorkAsync(async () =>
{
var db = await repository.GetDbContextAsync();
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
db.ChangeTracker.Entries().Count().ShouldBe(0);
// When tracking is explicitly disabled, entity should NOT be tracked regardless of global setting
using (repository.DisableTracking())
{
var person = await repository.FindAsync(personId, includeDetails: false);
person.ShouldNotBeNull();
db.ChangeTracker.Entries().Count().ShouldBe(0);
var list = await repository.GetListAsync();
list.Count.ShouldBeGreaterThan(0);
db.ChangeTracker.Entries().Count().ShouldBe(0);
}
});
}
private async Task AddSomePeopleAsync()
{
var repository = GetRequiredService<IRepository<Person, Guid>>();

Loading…
Cancel
Save