Browse Source

Add `RepositoryInterceptor`.

pull/17491/head
maliming 3 years ago
parent
commit
eb9b954717
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 1
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs
  2. 22
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs
  3. 12
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/DisableEntityChangeTrackingAttribute.cs
  4. 12
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EnableEntityChangeTrackingAttribute.cs
  5. 14
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingAttribute.cs
  6. 19
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs
  7. 10
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs
  8. 2
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs
  9. 52
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryHelper.cs
  10. 29
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptor.cs
  11. 22
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryInterceptorRegistrar.cs
  12. 8
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
  13. 2
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs
  14. 211
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/RepositoryInterceptor_Tests.cs

1
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);
}
}

22
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs

@ -40,7 +40,9 @@ public abstract class BasicRepositoryBase<TEntity> :
public ILogger Logger => LazyServiceProvider.LazyGetService<ILogger>(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance);
public bool IsChangeTrackingEnabled { get; protected set; } = true;
public IEntityChangeTrackingProvider EntityChangeTrackingProvider => LazyServiceProvider.LazyGetRequiredService<IEntityChangeTrackingProvider>();
public bool? IsChangeTrackingEnabled { get; protected set; }
protected BasicRepositoryBase()
{
@ -114,6 +116,24 @@ public abstract class BasicRepositoryBase<TEntity> :
{
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<TEntity, TKey> : BasicRepositoryBase<TEntity>, IBasicRepository<TEntity, TKey>

12
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)
{
}
}

12
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)
{
}
}

14
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;
}
}

19
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<bool?> _current = new AsyncLocal<bool?>();
public IDisposable Change(bool? enabled)
{
var previousValue = Enabled;
_current.Value = enabled;
return new DisposeAction(() => _current.Value = previousValue);
}
}

10
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);
}

2
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs

@ -12,7 +12,7 @@ namespace Volo.Abp.Domain.Repositories;
/// </summary>
public interface IRepository
{
bool IsChangeTrackingEnabled { get; }
bool? IsChangeTrackingEnabled { get; }
}
public interface IRepository<TEntity> : IReadOnlyRepository<TEntity>, IBasicRepository<TEntity>

52
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<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 methodInfo)
{
return methodInfo.IsDefined(typeof(EntityChangeTrackingAttribute), true);
}
}

29
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();
}
}
}

22
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<RepositoryInterceptor>();
}
}
private static bool ShouldIntercept(Type type)
{
return !DynamicProxyIgnoreTypes.Contains(type) && RepositoryHelper.IsEntityChangeTrackingType(type.GetTypeInfo());
}
}

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

@ -277,12 +277,12 @@ public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IE
[Obsolete("Use GetQueryableAsync method.")]
protected override IQueryable<TEntity> GetQueryable()
{
return DbSet.AsQueryable().AsNoTrackingIf(!IsChangeTrackingEnabled);
return DbSet.AsQueryable().AsNoTrackingIf(!ShouldTrackingEntityChange());
}
public async override Task<IQueryable<TEntity>> 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<TDbContext, TEntity> : RepositoryBase<TEntity>, 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<TDbContext, TEntity, TKey> : EfCoreRepository<TDbC
{
return includeDetails
? await (await WithDetailsAsync()).OrderBy(e => 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));
}

2
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

211
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<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<MyServiceEnableEntityChangeTrackingOnInterface>();
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;
}
}
[EnableEntityChangeTracking]
public interface IMyServiceEnableEntityChangeTrackingOnInterface
{
}
public class MyServiceEnableEntityChangeTrackingOnInterface : IMyServiceEnableEntityChangeTrackingOnInterface, ITransientDependency
{
private readonly IRepository<Person, Guid> _repository;
public MyServiceEnableEntityChangeTrackingOnInterface(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…
Cancel
Save