mirror of https://github.com/abpframework/abp.git
36 changed files with 574 additions and 101 deletions
@ -1,9 +1,6 @@ |
|||||
using JetBrains.Annotations; |
namespace Volo.Abp.Auditing; |
||||
|
|
||||
namespace Volo.Abp.Auditing; |
|
||||
|
|
||||
public interface IAuditLogScope |
public interface IAuditLogScope |
||||
{ |
{ |
||||
[NotNull] |
|
||||
AuditLogInfo Log { get; } |
AuditLogInfo Log { get; } |
||||
} |
} |
||||
|
|||||
@ -1,12 +1,11 @@ |
|||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
using Volo.Abp; |
|
||||
using Volo.Abp.AutoMapper; |
using Volo.Abp.AutoMapper; |
||||
using Volo.Abp.Data; |
using Volo.Abp.Data; |
||||
using Volo.Abp.ObjectExtending; |
using Volo.Abp.ObjectExtending; |
||||
|
|
||||
namespace AutoMapper; |
namespace AutoMapper; |
||||
|
|
||||
public static class AbpAutoMapperExtensibleDtoExtensions |
public static class AbpAutoMapperExtensibleObjectExtensions |
||||
{ |
{ |
||||
public static IMappingExpression<TSource, TDestination> MapExtraProperties<TSource, TDestination>( |
public static IMappingExpression<TSource, TDestination> MapExtraProperties<TSource, TDestination>( |
||||
this IMappingExpression<TSource, TDestination> mappingExpression, |
this IMappingExpression<TSource, TDestination> mappingExpression, |
||||
@ -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