diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs index 614ba135ff..d054733785 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs @@ -49,6 +49,6 @@ public static class EfCoreRepositoryExtensions public static IQueryable AsNoTrackingIf(this IQueryable queryable, bool condition) where TEntity : class, IEntity { - return condition ? queryable.AsNoTracking() : queryable; + return condition ? queryable.AsNoTracking() : queryable.AsTracking(); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index ff4aeabc44..cf8f994d42 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -478,9 +478,7 @@ public class EfCoreRepository : EfCoreRepository e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken)) - : !ShouldTrackingEntityChange() - ? await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken)) - : await (await GetDbSetAsync()).FindAsync(new object[] { 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) diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs index 87b487b68a..9581df3a19 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs +++ b/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>(); + + 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().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().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().Count().ShouldBe(list.Count); + }); + } + + [Fact] + public async Task Repository_Should_Respect_NoTracking_When_EntityChangeTracking_Is_Disabled_With_Global_NoTracking() + { + await AddSomePeopleAsync(); + + var repository = GetRequiredService>(); + + 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>();