diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 9ed4a277eb..73be904765 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata; +using Volo.Abp.Data; using Volo.Abp.Domain.Entities; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; @@ -21,13 +22,15 @@ namespace Volo.Abp.EntityFrameworkCore { public Guid? CurrentTenantId => CurrentTenant?.Id; - protected virtual bool IsMayHaveTenantFilterEnabled => true; //TODO: Change this when data filtering system is full implemented - protected virtual bool IsSoftDeleteFilterEnabled => true; //TODO: Change this when data filtering system is full implemented + protected virtual bool IsMayHaveTenantFilterEnabled => DataFilter.IsEnabled(); + protected virtual bool IsSoftDeleteFilterEnabled => DataFilter.IsEnabled(); public ICurrentTenant CurrentTenant { get; set; } public IGuidGenerator GuidGenerator { get; set; } + public IDataFilter DataFilter { get; set; } + private static readonly MethodInfo ConfigureGlobalFiltersMethodInfo = typeof(AbpDbContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.Instance | BindingFlags.NonPublic); protected AbpDbContext(DbContextOptions options) diff --git a/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs b/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs index ac8a81416a..79a5ecfa7f 100644 --- a/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs +++ b/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.ApiVersioning; +using Volo.Abp.Data; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.ObjectMapping; @@ -40,6 +41,7 @@ namespace Volo.Abp services.AddAssemblyOf(); services.AddSingleton(NullRequestedApiVersion.Instance); + services.AddSingleton(typeof(IDataFilter<>), typeof(DataFilter<>)); services.Configure(options => { diff --git a/src/Volo.Abp/Volo/Abp/Data/DataFilter.cs b/src/Volo.Abp/Volo/Abp/Data/DataFilter.cs new file mode 100644 index 0000000000..c45b13a375 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Data/DataFilter.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Data +{ + public class DataFilter : IDataFilter, ISingletonDependency + { + private readonly ConcurrentDictionary _filters; + + private readonly IServiceProvider _serviceProvider; + + public DataFilter(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + _filters = new ConcurrentDictionary(); + } + + public IDisposable Enable() + where TFilter : class + { + return GetFilter().Enable(); + } + + public IDisposable Disable() + where TFilter : class + { + return GetFilter().Disable(); + } + + public bool IsEnabled() + where TFilter : class + { + return GetFilter().IsEnabled; + } + + private IDataFilter GetFilter() + where TFilter : class + { + return _filters.GetOrAdd( + typeof(TFilter), + () => _serviceProvider.GetRequiredService>() + ) as IDataFilter; + } + } + + public class DataFilter : IDataFilter + where TFilter : class + { + public bool IsEnabled + { + get + { + EnsureInitialized(); + return _filter.Value.IsEnabled; + } + } + + private readonly AsyncLocal _filter; + + public DataFilter() + { + _filter = new AsyncLocal(); + } + + public IDisposable Enable() + { + if (IsEnabled) + { + return NullDisposable.Instance; + } + + _filter.Value.IsEnabled = true; + + return new DisposeAction(() => Disable()); + } + + public IDisposable Disable() + { + if (!IsEnabled) + { + return NullDisposable.Instance; + } + + _filter.Value.IsEnabled = false; + + return new DisposeAction(() => Enable()); + } + + private void EnsureInitialized() + { + if (_filter.Value != null) + { + return; + } + + _filter.Value = new DataFilterItem { IsEnabled = true }; //TODO: Get from default setting! + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Data/DataFilterItem.cs b/src/Volo.Abp/Volo/Abp/Data/DataFilterItem.cs new file mode 100644 index 0000000000..94d7cc8ac7 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Data/DataFilterItem.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Data +{ + internal class DataFilterItem + { + public bool IsEnabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Data/IDataFilter.cs b/src/Volo.Abp/Volo/Abp/Data/IDataFilter.cs new file mode 100644 index 0000000000..28a0254523 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Data/IDataFilter.cs @@ -0,0 +1,26 @@ +using System; + +namespace Volo.Abp.Data +{ + public interface IDataFilter + where TFilter : class + { + IDisposable Enable(); + + IDisposable Disable(); + + bool IsEnabled { get; } + } + + public interface IDataFilter + { + IDisposable Enable() + where TFilter : class; + + IDisposable Disable() + where TFilter : class; + + bool IsEnabled() + where TFilter : class; + } +} diff --git a/src/Volo.Abp/Volo/Abp/NullDisposable.cs b/src/Volo.Abp/Volo/Abp/NullDisposable.cs new file mode 100644 index 0000000000..ddc174eb64 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/NullDisposable.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.Abp +{ + internal sealed class NullDisposable : IDisposable + { + public static NullDisposable Instance { get; } = new NullDisposable(); + + private NullDisposable() + { + + } + + public void Dispose() + { + + } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/MultiTenant_Filter_Tests.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/MultiTenant_Filter_Tests.cs similarity index 69% rename from test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/MultiTenant_Filter_Tests.cs rename to test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/MultiTenant_Filter_Tests.cs index 84e7cc44c6..ab9dda2e72 100644 --- a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/MultiTenant_Filter_Tests.cs +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/MultiTenant_Filter_Tests.cs @@ -1,25 +1,29 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Shouldly; +using Volo.Abp.Data; using Volo.Abp.Domain.Repositories; using Volo.Abp.MultiTenancy; using Volo.Abp.TestApp; using Volo.Abp.TestApp.Domain; using Xunit; -namespace Volo.Abp.EntityFrameworkCore.Repositories +namespace Volo.Abp.EntityFrameworkCore.DataFiltering { public class MultiTenant_Filter_Tests : EntityFrameworkCoreTestBase { private ICurrentTenant _fakeCurrentTenant; private readonly IRepository _personRepository; + private readonly IDataFilter _multiTenantFilter; public MultiTenant_Filter_Tests() { _personRepository = GetRequiredService>(); + _multiTenantFilter = GetRequiredService>(); } protected override void AfterAddApplication(IServiceCollection services) @@ -55,5 +59,22 @@ namespace Volo.Abp.EntityFrameworkCore.Repositories people = await _personRepository.GetListAsync(); people.Count.ShouldBe(0); } + + [Fact] + public async Task Should_Get_All_People_When_MultiTenant_Filter_Is_Disabled() + { + List people; + + using (_multiTenantFilter.Disable()) + { + //Filter disabled manually + people = await _personRepository.GetListAsync(); + people.Count.ShouldBe(3); + } + + //Filter re-enabled automatically + people = await _personRepository.GetListAsync(); + people.Count.ShouldBe(1); + } } } diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_Filter_Tests.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_Filter_Tests.cs new file mode 100644 index 0000000000..ba28f82b5c --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/SoftDelete_Filter_Tests.cs @@ -0,0 +1,64 @@ +using System.Linq; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.DataFiltering +{ + public class SoftDelete_Filter_Tests : EntityFrameworkCoreTestBase + { + private readonly IRepository _personRepository; + private readonly IDataFilter _dataFilter; + + public SoftDelete_Filter_Tests() + { + _personRepository = GetRequiredService>(); + _dataFilter = GetRequiredService(); + } + + [Fact] + public void Should_Not_Get_Deleted_Entities_By_Default() + { + var people = _personRepository.GetList(); + people.Count.ShouldBe(1); + people.Any(p => p.Name == "Douglas").ShouldBeTrue(); + } + + [Fact] + public void Should_Get_Deleted_Entities_When_Filter_Is_Disabled() + { + //Soft delete is enabled by default + var people = _personRepository.GetList(); + people.Any(p => !p.IsDeleted).ShouldBeTrue(); + people.Any(p => p.IsDeleted).ShouldBeFalse(); + + using (_dataFilter.Disable()) + { + //Soft delete is disabled + people = _personRepository.GetList(); + people.Any(p => !p.IsDeleted).ShouldBeTrue(); + people.Any(p => p.IsDeleted).ShouldBeTrue(); + + using (_dataFilter.Enable()) + { + //Soft delete is enabled again + people = _personRepository.GetList(); + people.Any(p => !p.IsDeleted).ShouldBeTrue(); + people.Any(p => p.IsDeleted).ShouldBeFalse(); + } + + //Soft delete is disabled (restored previous state) + people = _personRepository.GetList(); + people.Any(p => !p.IsDeleted).ShouldBeTrue(); + people.Any(p => p.IsDeleted).ShouldBeTrue(); + } + + //Soft delete is enabled (restored previous state) + people = _personRepository.GetList(); + people.Any(p => !p.IsDeleted).ShouldBeTrue(); + people.Any(p => p.IsDeleted).ShouldBeFalse(); + } + } +} diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SoftDelete_Filter_Tests.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SoftDelete_Filter_Tests.cs deleted file mode 100644 index 5892f88701..0000000000 --- a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SoftDelete_Filter_Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Linq; -using Shouldly; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.TestApp.Domain; -using Xunit; - -namespace Volo.Abp.EntityFrameworkCore.Repositories -{ - public class SoftDelete_Filter_Tests : EntityFrameworkCoreTestBase - { - private readonly IRepository _personRepository; - - public SoftDelete_Filter_Tests() - { - _personRepository = GetRequiredService>(); - } - - [Fact] - public void Should_Not_Get_Deleted_Entities_By_Default() - { - var people = _personRepository.GetList(); - people.Count.ShouldBe(1); - people.Any(p => p.Name == "Douglas").ShouldBeTrue(); - } - } -}