mirror of https://github.com/abpframework/abp.git
Browse Source
Introduce ` RepositoryInterceptor` and `Enable/DisableTracking()` extension methods.pull/17543/head
committed by
GitHub
21 changed files with 521 additions and 71 deletions
@ -1,22 +0,0 @@ |
|||
namespace Volo.Abp.Data; |
|||
|
|||
public class AbpRepositoryIsReadOnlyException : AbpException |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new <see cref="AbpRepositoryIsReadOnlyException"/> object.
|
|||
/// </summary>
|
|||
public AbpRepositoryIsReadOnlyException() |
|||
{ |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new <see cref="AbpRepositoryIsReadOnlyException"/> object.
|
|||
/// </summary>
|
|||
/// <param name="message">Exception message</param>
|
|||
public AbpRepositoryIsReadOnlyException(string message) |
|||
: base(message) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Domain.ChangeTracking; |
|||
|
|||
public static class ChangeTrackingHelper |
|||
{ |
|||
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<EntityChangeTrackingAttribute>().ToArray(); |
|||
if (attrs.Any()) |
|||
{ |
|||
entityChangeTrackingAttribute = attrs.First(); |
|||
return true; |
|||
} |
|||
|
|||
if (methodInfo.DeclaringType != null) |
|||
{ |
|||
//Class declaration
|
|||
attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<EntityChangeTrackingAttribute>().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 memberInfo) |
|||
{ |
|||
return memberInfo.IsDefined(typeof(EntityChangeTrackingAttribute), true); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.DynamicProxy; |
|||
|
|||
namespace Volo.Abp.Domain.ChangeTracking; |
|||
|
|||
public class ChangeTrackingInterceptor : AbpInterceptor, ITransientDependency |
|||
{ |
|||
private readonly IEntityChangeTrackingProvider _entityChangeTrackingProvider; |
|||
|
|||
public ChangeTrackingInterceptor(IEntityChangeTrackingProvider entityChangeTrackingProvider) |
|||
{ |
|||
_entityChangeTrackingProvider = entityChangeTrackingProvider; |
|||
} |
|||
|
|||
public async override Task InterceptAsync(IAbpMethodInvocation invocation) |
|||
{ |
|||
if (!ChangeTrackingHelper.IsEntityChangeTrackingMethod(invocation.Method, out var changeTrackingAttribute)) |
|||
{ |
|||
await invocation.ProceedAsync(); |
|||
return; |
|||
} |
|||
|
|||
using (_entityChangeTrackingProvider.Change(changeTrackingAttribute?.IsEnabled)) |
|||
{ |
|||
await invocation.ProceedAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using System.Reflection; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.DynamicProxy; |
|||
|
|||
namespace Volo.Abp.Domain.ChangeTracking; |
|||
|
|||
public class ChangeTrackingInterceptorRegistrar |
|||
{ |
|||
public static void RegisterIfNeeded(IOnServiceRegistredContext context) |
|||
{ |
|||
if (ShouldIntercept(context.ImplementationType)) |
|||
{ |
|||
context.Interceptors.TryAdd<ChangeTrackingInterceptor>(); |
|||
} |
|||
} |
|||
|
|||
private static bool ShouldIntercept(Type type) |
|||
{ |
|||
return !DynamicProxyIgnoreTypes.Contains(type) && ChangeTrackingHelper.IsEntityChangeTrackingType(type.GetTypeInfo()); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Domain.ChangeTracking; |
|||
|
|||
/// <summary>
|
|||
/// Ensures that the change tracking in enabled for the given method or class.
|
|||
/// </summary>
|
|||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] |
|||
public class DisableEntityChangeTrackingAttribute : EntityChangeTrackingAttribute |
|||
{ |
|||
public DisableEntityChangeTrackingAttribute() |
|||
: base(false) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Domain.ChangeTracking; |
|||
|
|||
/// <summary>
|
|||
/// Ensures that the change tracking in enabled for the given method or class.
|
|||
/// </summary>
|
|||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] |
|||
public class EnableEntityChangeTrackingAttribute : EntityChangeTrackingAttribute |
|||
{ |
|||
public EnableEntityChangeTrackingAttribute() |
|||
: base(true) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Domain.ChangeTracking; |
|||
|
|||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] |
|||
public abstract class EntityChangeTrackingAttribute : Attribute |
|||
{ |
|||
public virtual bool IsEnabled { get; set; } |
|||
|
|||
public EntityChangeTrackingAttribute(bool isEnabled) |
|||
{ |
|||
IsEnabled = isEnabled; |
|||
} |
|||
} |
|||
@ -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<bool?> _current = new AsyncLocal<bool?>(); |
|||
|
|||
public IDisposable Change(bool? enabled) |
|||
{ |
|||
var previousValue = Enabled; |
|||
_current.Value = enabled; |
|||
return new DisposeAction(() => _current.Value = previousValue); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Domain.Repositories; |
|||
|
|||
public interface IEntityChangeTrackingProvider |
|||
{ |
|||
bool? Enabled { get; } |
|||
|
|||
IDisposable Change(bool? enabled); |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.ChangeTracking; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.TestApp.Domain; |
|||
using Volo.Abp.TestApp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.ChangeTracking; |
|||
|
|||
public class ChangeTrackingInterceptor_Tests : TestAppTestBase<AbpEntityFrameworkCoreTestModule> |
|||
{ |
|||
[Fact] |
|||
public async Task ReadOnly_Repository_Should_Not_Track_Entities() |
|||
{ |
|||
await AddSomePeopleAsync(); |
|||
|
|||
var readOnlyRepository = GetRequiredService<IReadOnlyRepository<Person, Guid>>(); |
|||
|
|||
await WithUnitOfWorkAsync(async () => |
|||
{ |
|||
var db = await readOnlyRepository.GetDbContextAsync(); |
|||
db.ChangeTracker.Entries().Count().ShouldBe(0); |
|||
|
|||
var service = GetRequiredService<MyReadOnlyService>(); |
|||
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<IRepository<Person, Guid>>(); |
|||
|
|||
await WithUnitOfWorkAsync(async () => |
|||
{ |
|||
var service = GetRequiredService<MyService>(); |
|||
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<MyServiceEnableEntityChangeTracking>(); |
|||
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<MyServiceChangeTrackingByEntityChangeTrackingProvider>(); |
|||
var db = await repository.GetDbContextAsync(); |
|||
db.ChangeTracker.Entries().Count().ShouldBe(0); |
|||
|
|||
var entityChangeTrackingProvider = GetRequiredService<IEntityChangeTrackingProvider>(); |
|||
// 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<IRepository<Person, Guid>>(); |
|||
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<Person, Guid> _repository; |
|||
|
|||
public MyService(IRepository<Person, Guid> repository) |
|||
{ |
|||
_repository = repository; |
|||
} |
|||
|
|||
[DisableEntityChangeTracking] |
|||
public virtual async Task<List<Person>> GetPeoplesAsync() |
|||
{ |
|||
await GetPeopleAsync(); |
|||
return await _repository.GetListAsync(); |
|||
} |
|||
|
|||
[EnableEntityChangeTracking] |
|||
public virtual async Task<Person> GetPeopleAsync() |
|||
{ |
|||
var p1 = await _repository.FindAsync(x => x.Name == "people1"); |
|||
return p1; |
|||
} |
|||
} |
|||
|
|||
public class MyReadOnlyService : MyService |
|||
{ |
|||
public MyReadOnlyService(IReadOnlyRepository<Person, Guid> repository) |
|||
: base(repository.As<IRepository<Person, Guid>>()) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
|
|||
[EnableEntityChangeTracking] |
|||
public class MyServiceEnableEntityChangeTracking : ITransientDependency |
|||
{ |
|||
private readonly IRepository<Person, Guid> _repository; |
|||
|
|||
public MyServiceEnableEntityChangeTracking(IRepository<Person, Guid> repository) |
|||
{ |
|||
_repository = repository; |
|||
} |
|||
|
|||
public virtual async Task<List<Person>> GetPeoplesAsync() |
|||
{ |
|||
var p1 = await GetPeopleAsync(); |
|||
var p2 = await _repository.FindAsync(x => x.Name == "people2"); |
|||
|
|||
return new List<Person> {p1, p2}; |
|||
} |
|||
|
|||
[DisableEntityChangeTracking] |
|||
public virtual async Task<Person> GetPeopleAsync() |
|||
{ |
|||
var p1 = await _repository.FindAsync(x => x.Name == "people1"); |
|||
return p1; |
|||
} |
|||
} |
|||
|
|||
public class MyServiceChangeTrackingByEntityChangeTrackingProvider : ITransientDependency |
|||
{ |
|||
private readonly IRepository<Person, Guid> _repository; |
|||
|
|||
public MyServiceChangeTrackingByEntityChangeTrackingProvider(IRepository<Person, Guid> repository) |
|||
{ |
|||
_repository = repository; |
|||
} |
|||
|
|||
public virtual async Task<List<Person>> GetPeoplesAsync() |
|||
{ |
|||
return await _repository.GetListAsync(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue