Browse Source

Merge pull request #17130 from abpframework/HasAbpQueryFilter

Add `HasAbpQueryFilter` method to combine the custom filter with global filters.
pull/17156/head
Halil İbrahim Kalkan 3 years ago
committed by GitHub
parent
commit
be4182571b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      docs/en/Data-Filtering.md
  2. 39
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  3. 27
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityTypeBuilderExtensions.cs
  4. 37
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/QueryFilterExpressionHelper.cs
  5. 18
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs
  6. 36
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/EfCore_Custom_Filter_Tests.cs
  7. 9
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  8. 11
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/Domain/Category.cs
  9. 7
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

14
docs/en/Data-Filtering.md

@ -237,6 +237,20 @@ protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntit
* Added a `IsActiveFilterEnabled` property to check if `IIsActive` is enabled or not. It internally uses the `IDataFilter` service introduced before.
* Overrided the `ShouldFilterEntity` and `CreateFilterExpression` methods, checked if given entity implements the `IIsActive` interface and combines the expressions if necessary.
In addition you can also use `HasAbpQueryFilter` to set a filter for an entity. It will combine your filter with ABP EF Core builtin global query filters.
````csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyEntity>(b =>
{
b.HasAbpQueryFilter(e => e.Name.StartsWith("abp"));
});
}
````
### MongoDB
ABP abstracts the `IMongoDbRepositoryFilterer` interface to implement data filtering for the [MongoDB Integration](MongoDB.md), it works only if you use the repositories properly. Otherwise, you should manually filter the data.

39
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -626,7 +626,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
var filterExpression = CreateFilterExpression<TEntity>();
if (filterExpression != null)
{
modelBuilder.Entity<TEntity>().HasQueryFilter(filterExpression);
modelBuilder.Entity<TEntity>().HasAbpQueryFilter(filterExpression);
}
}
}
@ -705,44 +705,9 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, "TenantId") == CurrentTenantId;
expression = expression == null ? multiTenantFilter : CombineExpressions(expression, multiTenantFilter);
expression = expression == null ? multiTenantFilter : QueryFilterExpressionHelper.CombineExpressions(expression, multiTenantFilter);
}
return expression;
}
protected virtual Expression<Func<T, bool>> CombineExpressions<T>(Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
var parameter = Expression.Parameter(typeof(T));
var leftVisitor = new ReplaceExpressionVisitor(expression1.Parameters[0], parameter);
var left = leftVisitor.Visit(expression1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expression2.Parameters[0], parameter);
var right = rightVisitor.Visit(expression2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left, right), parameter);
}
class ReplaceExpressionVisitor : ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
{
return _newValue;
}
return base.Visit(node);
}
}
}

27
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityTypeBuilderExtensions.cs

@ -0,0 +1,27 @@
using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Volo.Abp.EntityFrameworkCore;
public static class EntityTypeBuilderExtensions
{
/// <summary>
/// This method is used to add a query filter to this entity which combine with ABP EF Core builtin query filters.
/// </summary>
/// <returns></returns>
public static EntityTypeBuilder<TEntity> HasAbpQueryFilter<TEntity>(this EntityTypeBuilder<TEntity> builder, Expression<Func<TEntity, bool>> filter)
where TEntity : class
{
#pragma warning disable EF1001
var queryFilterAnnotation = builder.Metadata.FindAnnotation(CoreAnnotationNames.QueryFilter);
#pragma warning restore EF1001
if (queryFilterAnnotation != null && queryFilterAnnotation.Value != null && queryFilterAnnotation.Value is Expression<Func<TEntity, bool>> existingFilter)
{
filter = QueryFilterExpressionHelper.CombineExpressions(filter, existingFilter);
}
return builder.HasQueryFilter(filter);
}
}

37
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/QueryFilterExpressionHelper.cs

@ -0,0 +1,37 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.EntityFrameworkCore;
public static class QueryFilterExpressionHelper
{
public static Expression<Func<T, bool>> CombineExpressions<T>(Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
var parameter = Expression.Parameter(typeof(T));
var leftVisitor = new ReplaceExpressionVisitor(expression1.Parameters[0], parameter);
var left = leftVisitor.Visit(expression1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expression2.Parameters[0], parameter);
var right = rightVisitor.Visit(expression2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left!, right!), parameter);
}
private class ReplaceExpressionVisitor : ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
return node == _oldValue ? _newValue : base.Visit(node);
}
}
}

18
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Autofac;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EntityFrameworkCore.Domain;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.EntityFrameworkCore.TestApp.FifthContext;
@ -14,6 +17,7 @@ using Volo.Abp.MultiTenancy;
using Volo.Abp.TestApp;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.EntityFrameworkCore;
using Volo.Abp.Threading;
namespace Volo.Abp.EntityFrameworkCore;
@ -71,6 +75,20 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
context.ServiceProvider.GetRequiredService<SecondDbContext>().Database.Migrate();
using (var scope = context.ServiceProvider.CreateScope())
{
var categoryRepository = scope.ServiceProvider.GetRequiredService<IBasicRepository<Category, Guid>>();
AsyncHelper.RunSync(async () =>
{
await categoryRepository.InsertManyAsync(new List<Category>
{
new Category { Name = "volo.abp" },
new Category { Name = "abp.cli" },
new Category { Name = "abp.core", IsDeleted = true }
});
});
}
}
private static SqliteConnection CreateDatabaseAndGetConnection()

36
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DataFiltering/EfCore_Custom_Filter_Tests.cs

@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.EntityFrameworkCore.DataFiltering;
public class EfCore_Custom_Filter_Tests : TestAppTestBase<AbpEntityFrameworkCoreTestModule>
{
private readonly IBasicRepository<Category, Guid> _categoryRepository;
public EfCore_Custom_Filter_Tests()
{
_categoryRepository = GetRequiredService<IBasicRepository<Category, Guid>>();
}
[Fact]
public async Task Should_Combine_Abp_And_Custom_QueryFilter_Test()
{
var categories = await _categoryRepository.GetListAsync();
categories.Count.ShouldBe(1);
categories[0].Name.ShouldBe("abp.cli");
using (GetRequiredService<IDataFilter<ISoftDelete>>().Disable())
{
categories = await _categoryRepository.GetListAsync();
categories.Count.ShouldBe(2);
categories.ShouldContain(x => x.Name == "abp.cli" && x.IsDeleted == false);
categories.ShouldContain(x => x.Name == "abp.core" && x.IsDeleted == true);
}
}
}

9
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs

@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.EntityFrameworkCore.TestApp.SecondContext;
using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext;
using Volo.Abp.TestApp.Domain;
@ -23,6 +24,8 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public TestMigrationsDbContext(DbContextOptions<TestMigrationsDbContext> options)
: base(options)
{
@ -40,7 +43,6 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
b.HasKey(p => new { p.PersonId, p.Number });
});
modelBuilder.Entity<Person>(b =>
{
b.Property(x => x.LastActiveTime).ValueGeneratedOnAddOrUpdate().HasDefaultValue(DateTime.Now);
@ -57,5 +59,10 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
});
modelBuilder.Entity<Product>();
modelBuilder.Entity<Category>(b =>
{
b.HasAbpQueryFilter(e => e.Name.StartsWith("abp"));
});
}
}

11
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/Domain/Category.cs

@ -0,0 +1,11 @@
using System;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.TestApp.Domain;
public class Category : AggregateRoot<Guid>, ISoftDelete
{
public string Name { get; set; }
public bool IsDeleted { get; set; }
}

7
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

@ -32,6 +32,8 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public TestAppDbContext(DbContextOptions<TestAppDbContext> options)
: base(options)
{
@ -85,6 +87,11 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
modelBuilder.Entity<Product>();
modelBuilder.Entity<Category>(b =>
{
b.HasAbpQueryFilter(e => e.Name.StartsWith("abp"));
});
modelBuilder.TryConfigureObjectExtensions<TestAppDbContext>();
}
}

Loading…
Cancel
Save