From eb9b95471792d8350001215d5a67c229729f488c Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 27 Aug 2023 17:18:17 +0800 Subject: [PATCH] Add `RepositoryInterceptor`. --- .../Volo/Abp/Domain/AbpDddDomainModule.cs | 1 + .../Repositories/BasicRepositoryBase.cs | 22 +- .../DisableEntityChangeTrackingAttribute.cs | 12 + .../EnableEntityChangeTrackingAttribute.cs | 12 + .../EntityChangeTrackingAttribute.cs | 14 ++ .../EntityChangeTrackingProvider.cs | 19 ++ .../IEntityChangeTrackingProvider.cs | 10 + .../Abp/Domain/Repositories/IRepository.cs | 2 +- .../Domain/Repositories/RepositoryHelper.cs | 52 +++++ .../Repositories/RepositoryInterceptor.cs | 29 +++ .../RepositoryInterceptorRegistrar.cs | 22 ++ .../EntityFrameworkCore/EfCoreRepository.cs | 8 +- .../RepositoryRegistration_Tests.cs | 2 +- .../RepositoryInterceptor_Tests.cs | 211 ++++++++++++++++++ 14 files changed, 409 insertions(+), 7 deletions(-) create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/DisableEntityChangeTrackingAttribute.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EnableEntityChangeTrackingAttribute.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingAttribute.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryHelper.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptor.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptorRegistrar.cs create mode 100644 framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/RepositoryInterceptor_Tests.cs diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs index 536e31e51f..2e67c42820 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs @@ -30,5 +30,6 @@ public class AbpDddDomainModule : AbpModule public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.AddConventionalRegistrar(new AbpRepositoryConventionalRegistrar()); + context.Services.OnRegistered(RepositoryInterceptorRegistrar.RegisterIfNeeded); } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs index 8257c4c8af..cd85acce50 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs @@ -40,7 +40,9 @@ public abstract class BasicRepositoryBase : public ILogger Logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance); - public bool IsChangeTrackingEnabled { get; protected set; } = true; + public IEntityChangeTrackingProvider EntityChangeTrackingProvider => LazyServiceProvider.LazyGetRequiredService(); + + public bool? IsChangeTrackingEnabled { get; protected set; } protected BasicRepositoryBase() { @@ -114,6 +116,24 @@ public abstract class BasicRepositoryBase : { return CancellationTokenProvider.FallbackToProvider(preferredValue); } + + protected virtual bool ShouldTrackingEntityChange() + { + // If IsChangeTrackingEnabled is set, it has the highest priority. This generally means the repository is read-only. + if (IsChangeTrackingEnabled.HasValue) + { + return IsChangeTrackingEnabled.Value; + } + + // If Interface/Class/Method has Enable/DisableEntityChangeTrackingAttribute, it has the second highest priority. + if (EntityChangeTrackingProvider.Enabled.HasValue) + { + return EntityChangeTrackingProvider.Enabled.Value; + } + + // Default behavior is tracking entity change. + return true; + } } public abstract class BasicRepositoryBase : BasicRepositoryBase, IBasicRepository diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/DisableEntityChangeTrackingAttribute.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/DisableEntityChangeTrackingAttribute.cs new file mode 100644 index 0000000000..d6a73cba32 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/DisableEntityChangeTrackingAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Volo.Abp.Domain.Repositories; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)] +public class DisableEntityChangeTrackingAttribute : EntityChangeTrackingAttribute +{ + public DisableEntityChangeTrackingAttribute() + : base(false) + { + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EnableEntityChangeTrackingAttribute.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EnableEntityChangeTrackingAttribute.cs new file mode 100644 index 0000000000..a94df978fb --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EnableEntityChangeTrackingAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Volo.Abp.Domain.Repositories; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)] +public class EnableEntityChangeTrackingAttribute : EntityChangeTrackingAttribute +{ + public EnableEntityChangeTrackingAttribute() + : base(true) + { + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingAttribute.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingAttribute.cs new file mode 100644 index 0000000000..0bb6ca82e4 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace Volo.Abp.Domain.Repositories; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)] +public class EntityChangeTrackingAttribute : Attribute +{ + public bool Enabled { get; set; } + + public EntityChangeTrackingAttribute(bool enabled) + { + Enabled = enabled; + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs new file mode 100644 index 0000000000..19c10e9a3f --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Domain.Repositories; + +public class EntityChangeTrackingProvider : IEntityChangeTrackingProvider, ISingletonDependency +{ + public bool? Enabled => _current.Value; + + private readonly AsyncLocal _current = new AsyncLocal(); + + public IDisposable Change(bool? enabled) + { + var previousValue = Enabled; + _current.Value = enabled; + return new DisposeAction(() => _current.Value = previousValue); + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs new file mode 100644 index 0000000000..f1db1584fa --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Domain.Repositories; + +public interface IEntityChangeTrackingProvider +{ + bool? Enabled { get; } + + IDisposable Change(bool? enabled); +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs index fda0c7ed94..dc39255b25 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.Domain.Repositories; /// public interface IRepository { - bool IsChangeTrackingEnabled { get; } + bool? IsChangeTrackingEnabled { get; } } public interface IRepository : IReadOnlyRepository, IBasicRepository diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryHelper.cs new file mode 100644 index 0000000000..014970c527 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryHelper.cs @@ -0,0 +1,52 @@ +using System.Linq; +using System.Reflection; +using JetBrains.Annotations; + +namespace Volo.Abp.Domain.Repositories; + +public static class RepositoryHelper +{ + public static bool IsEntityChangeTrackingType(TypeInfo implementationType) + { + return HasEntityChangeTrackingAttribute(implementationType) || AnyMethodHasEntityChangeTrackingAttribute(implementationType); + } + + public static bool IsEntityChangeTrackingMethod([NotNull] MethodInfo methodInfo, out EntityChangeTrackingAttribute? entityChangeTrackingAttribute) + { + Check.NotNull(methodInfo, nameof(methodInfo)); + + //Method declaration + var attrs = methodInfo.GetCustomAttributes(true).OfType().ToArray(); + if (attrs.Any()) + { + entityChangeTrackingAttribute = attrs.First(); + return true; + } + + if (methodInfo.DeclaringType != null) + { + //Class declaration + attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType().ToArray(); + if (attrs.Any()) + { + entityChangeTrackingAttribute = attrs.First(); + return true; + } + } + + entityChangeTrackingAttribute = null; + return false; + } + + private static bool AnyMethodHasEntityChangeTrackingAttribute(TypeInfo implementationType) + { + return implementationType + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Any(HasEntityChangeTrackingAttribute); + } + + private static bool HasEntityChangeTrackingAttribute(MemberInfo methodInfo) + { + return methodInfo.IsDefined(typeof(EntityChangeTrackingAttribute), true); + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptor.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptor.cs new file mode 100644 index 0000000000..f322d1e87d --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptor.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; + +namespace Volo.Abp.Domain.Repositories; + +public class RepositoryInterceptor : AbpInterceptor, ITransientDependency +{ + private readonly IEntityChangeTrackingProvider _entityChangeTrackingProvider; + + public RepositoryInterceptor(IEntityChangeTrackingProvider entityChangeTrackingProvider) + { + _entityChangeTrackingProvider = entityChangeTrackingProvider; + } + + public async override Task InterceptAsync(IAbpMethodInvocation invocation) + { + if (!RepositoryHelper.IsEntityChangeTrackingMethod(invocation.Method, out var changeTrackingAttribute)) + { + await invocation.ProceedAsync(); + return; + } + + using (_entityChangeTrackingProvider.Change(changeTrackingAttribute?.Enabled)) + { + await invocation.ProceedAsync(); + } + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptorRegistrar.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptorRegistrar.cs new file mode 100644 index 0000000000..538fa72774 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptorRegistrar.cs @@ -0,0 +1,22 @@ +using System; +using System.Reflection; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; + +namespace Volo.Abp.Domain.Repositories; + +public class RepositoryInterceptorRegistrar +{ + public static void RegisterIfNeeded(IOnServiceRegistredContext context) + { + if (ShouldIntercept(context.ImplementationType)) + { + context.Interceptors.TryAdd(); + } + } + + private static bool ShouldIntercept(Type type) + { + return !DynamicProxyIgnoreTypes.Contains(type) && RepositoryHelper.IsEntityChangeTrackingType(type.GetTypeInfo()); + } +} 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 aa0b10913d..ff40c8a60b 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 @@ -277,12 +277,12 @@ public class EfCoreRepository : RepositoryBase, IE [Obsolete("Use GetQueryableAsync method.")] protected override IQueryable GetQueryable() { - return DbSet.AsQueryable().AsNoTrackingIf(!IsChangeTrackingEnabled); + return DbSet.AsQueryable().AsNoTrackingIf(!ShouldTrackingEntityChange()); } public async override Task> GetQueryableAsync() { - return (await GetDbSetAsync()).AsQueryable().AsNoTrackingIf(!IsChangeTrackingEnabled); + return (await GetDbSetAsync()).AsQueryable().AsNoTrackingIf(!ShouldTrackingEntityChange()); } protected async override Task SaveChangesAsync(CancellationToken cancellationToken) @@ -431,7 +431,7 @@ public class EfCoreRepository : RepositoryBase, IE protected virtual void CheckChangeTracking() { - if (!IsChangeTrackingEnabled) + if (!ShouldTrackingEntityChange()) { Logger.LogWarning("This repository has disabled change tracking. Your changes may not be saved!"); } @@ -467,7 +467,7 @@ public class EfCoreRepository : EfCoreRepository e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)) - : !IsChangeTrackingEnabled + : !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)); } diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs index 00be2f9522..ca98017e0a 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs @@ -407,7 +407,7 @@ public class RepositoryRegistration_Tests public class MyTestAggregateRootWithDefaultPkEmptyRepository : IMyTestAggregateRootWithDefaultPkEmptyRepository { - public bool IsChangeTrackingEnabled { get; set; } + public bool? IsChangeTrackingEnabled { get; set; } } public class TestDbContextRegistrationOptions : AbpCommonDbContextRegistrationOptions diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/RepositoryInterceptor_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/RepositoryInterceptor_Tests.cs new file mode 100644 index 0000000000..8230a6ec7e --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/RepositoryInterceptor_Tests.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.TestApp.Testing; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.Repositories; + +public class RepositoryInterceptor_Tests : TestAppTestBase +{ + [Fact] + public async Task ReadOnly_Repository_Should_Not_Track_Entities() + { + await AddSomePeopleAsync(); + + var readOnlyRepository = GetRequiredService>(); + + await WithUnitOfWorkAsync(async () => + { + var db = await readOnlyRepository.GetDbContextAsync(); + db.ChangeTracker.Entries().Count().ShouldBe(0); + + var service = GetRequiredService(); + var list = await service.GetPeoplesAsync(); + list.Count.ShouldBeGreaterThan(0); + + // RepositoryInterceptor always not track entities + db.ChangeTracker.Entries().Count().ShouldBe(0); + }); + } + + [Fact] + public async Task RepositoryInterceptor_Test() + { + await AddSomePeopleAsync(); + + var repository = GetRequiredService>(); + + await WithUnitOfWorkAsync(async () => + { + var service = GetRequiredService(); + var db = await repository.GetDbContextAsync(); + db.ChangeTracker.Entries().Count().ShouldBe(0); + + var list = await service.GetPeoplesAsync(); + list.Count.ShouldBeGreaterThan(0); + + db.ChangeTracker.Entries().Count().ShouldBe(1); // Track one entity from GetPeopleAsync + }); + + await WithUnitOfWorkAsync(async () => + { + var service = GetRequiredService(); + var db = await repository.GetDbContextAsync(); + db.ChangeTracker.Entries().Count().ShouldBe(0); + + var list = await service.GetPeoplesAsync(); + list.Count.ShouldBeGreaterThan(0); + + db.ChangeTracker.Entries().Count().ShouldBe(1); // Track one entity from GetPeoplesAsync + }); + + await WithUnitOfWorkAsync(async () => + { + var service = GetRequiredService(); + var db = await repository.GetDbContextAsync(); + db.ChangeTracker.Entries().Count().ShouldBe(0); + + var list = await service.GetPeoplesAsync(); + list.Count.ShouldBeGreaterThan(0); + + db.ChangeTracker.Entries().Count().ShouldBe(1); // Track one entity from GetPeoplesAsync + }); + + await WithUnitOfWorkAsync(async () => + { + var service = GetRequiredService(); + var db = await repository.GetDbContextAsync(); + db.ChangeTracker.Entries().Count().ShouldBe(0); + + var entityChangeTrackingProvider = GetRequiredService(); + // Disable entity change tracking + using (entityChangeTrackingProvider.Change(false)) + { + var list = await service.GetPeoplesAsync(); + list.Count.ShouldBeGreaterThan(0); + db.ChangeTracker.Entries().Count().ShouldBe(0); + } + }); + } + + private async Task AddSomePeopleAsync() + { + var repository = GetRequiredService>(); + await repository.InsertAsync(new Person(Guid.NewGuid(), "people1", 18)); + await repository.InsertAsync(new Person(Guid.NewGuid(), "people2", 19)); + await repository.InsertAsync(new Person(Guid.NewGuid(), "people3", 20)); + await repository.InsertAsync(new Person(Guid.NewGuid(), "people4", 21)); + } +} + +public class MyService : ITransientDependency +{ + private readonly IRepository _repository; + + public MyService(IRepository repository) + { + _repository = repository; + } + + [DisableEntityChangeTracking] + public virtual async Task> GetPeoplesAsync() + { + await GetPeopleAsync(); + return await _repository.GetListAsync(); + } + + [EnableEntityChangeTracking] + public virtual async Task GetPeopleAsync() + { + var p1 = await _repository.FindAsync(x => x.Name == "people1"); + return p1; + } +} + +public class MyReadOnlyService : MyService +{ + public MyReadOnlyService(IReadOnlyRepository repository) + : base(repository.As>()) + { + } +} + + +[EnableEntityChangeTracking] +public class MyServiceEnableEntityChangeTracking : ITransientDependency +{ + private readonly IRepository _repository; + + public MyServiceEnableEntityChangeTracking(IRepository repository) + { + _repository = repository; + } + + public virtual async Task> GetPeoplesAsync() + { + var p1 = await GetPeopleAsync(); + var p2 = await _repository.FindAsync(x => x.Name == "people2"); + + return new List {p1, p2}; + } + + [DisableEntityChangeTracking] + public virtual async Task GetPeopleAsync() + { + var p1 = await _repository.FindAsync(x => x.Name == "people1"); + return p1; + } +} + + +[EnableEntityChangeTracking] +public interface IMyServiceEnableEntityChangeTrackingOnInterface +{ + +} + +public class MyServiceEnableEntityChangeTrackingOnInterface : IMyServiceEnableEntityChangeTrackingOnInterface, ITransientDependency +{ + private readonly IRepository _repository; + + public MyServiceEnableEntityChangeTrackingOnInterface(IRepository repository) + { + _repository = repository; + } + + public virtual async Task> GetPeoplesAsync() + { + var p1 = await GetPeopleAsync(); + var p2 = await _repository.FindAsync(x => x.Name == "people2"); + + return new List {p1, p2}; + } + + [DisableEntityChangeTracking] + public virtual async Task GetPeopleAsync() + { + var p1 = await _repository.FindAsync(x => x.Name == "people1"); + return p1; + } +} + +public class MyServiceChangeTrackingByEntityChangeTrackingProvider : ITransientDependency +{ + private readonly IRepository _repository; + + public MyServiceChangeTrackingByEntityChangeTrackingProvider(IRepository repository) + { + _repository = repository; + } + + public virtual async Task> GetPeoplesAsync() + { + return await _repository.GetListAsync(); + } +}