如何組合BinaryExpression
和Expression<Func<dynamic / T, bool>>
?
例如:
void AddGlobalFilter<T>(Expression<Func<T, bool>> expr)
{
var parameter = Expression.Parameter(type, "t");
var member = Expression.Property(filter.Parameter, field);
var constant = Expression.Constant(null);
var body = Expression.Equal(member, constant);
var combine = Expression.AndAlso(body, expr);
}
我正在嘗試為Entity Framework(EF)Core定義全局過濾器 。問題是我必須手動組合多個過濾器 。
如果model實現了IDbDeleted
接口,則可以在ModelBuilder
添加一個過濾器。
可以為特定模型手動添加另一個。基本的想法是我有一個所有表達式的列表,然後將它們組合在一起:
var expression = listExpressions.First();
foreach (var second in listExpressions.Skip(1))
{
expression = Expression.AndAlso(expression, second);
}
var lambdaExpression = Expression.Lambda(expression, parameter);
modelBuilder.Entity(item.Key).HasQueryFilter(lambdaExpression);
當然我得到錯誤(首先是來自Expression.Equal
,第二個來自t => t...
):
過濾器表達式't => t =>(不是(t ....)
編輯 :代碼看起來像這樣:
[Table("MyEntities")]
public class DbMyEntity : IDeleted
{
public string Name { get; set; }
public DateTime? DateTimeDeleted { get; set; }
}
public interface IDeleted
{
DateTime? DateTimeDeleted { get; set; }
}
public class MyContext : IdentityDbContext
{
private Dictionary<Type, List<Expression>> dict = new Dictionary<Type, List<Expression>>();
private Dictionary<Type, ParameterExpression> dictParameter = new Dictionary<Type, ParameterExpression>();
private ParameterExpression GetParameter(Type type)
{
if (!this.dictParameter.ContainsKey(type))
{
this.dictParameter.Add(type, Expression.Parameter(type, "t"));
}
return this.dictParameter[type];
}
private void AddToDict(Type type, Expression expr)
{
if (!this.dict.ContainsKey(type))
{
this.dict.Add(type, new List<Expression>());
this.GetParameter(type); //Just to create ParameterExpression if not exists.
}
this.dict[type].Add(expr);
}
private void AddToDict<T>(Expression<Func<T, bool>> expr)
{
this.AddToDict(typeof(T), expr);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
if (typeof(IDeleted).IsAssignableFrom(entity.ClrType))
{
var member = Expression.Property(this.GetParameter(entity.ClrType), "DateTimeDeleted");
var constant = Expression.Constant(null);
var body = Expression.Equal(member, constant);
this.AddToDict(entity.ClrType, body);
}
}
//This is done in another project in same solution. See comment bellow.
this.AddToDict<DbMyEntity>(t => t.Name == null || t.Name == "Something");
//foreach (var builderType in allDllModules)
//{
// if (builderType != null && builderType != typeof(ICustomModelBuilder))
// {
// var builder = (ICustomModelBuilder)Activator.CreateInstance(builderType);
// builder.Build(modelBuilder);
// }
//}
foreach (var item in this.dict)
{
var expression = item.Value.First();
foreach (var second in item.Value.Skip(1))
{
expression = Expression.AndAlso(expression, second);
}
var lambdaExpression = Expression.Lambda(expression, this.dictParameter[item.Key]);
modelBuilder.Entity(item.Key).HasQueryFilter(lambdaExpression);
}
}
}
您正在將表達式與lambda表達式混合使用。有許多文章顯示瞭如何組合lambda表達式,但是必不可少的部分是從lambda表達式主體組成表達式並重新綁定參數 。
後者通常是通過這樣的自定義ExpressionVisitor
實現的:
using System.Linq.Expressions;
public static class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
現在有關EF Core組合查詢過濾器。
對於正在執行的操作,使用字典和表達式列表似乎過於復雜。由於IMutableEntityType
提供了對QueryFilter
讀/寫訪問權限,因此可以使用少量的自定義擴展方法集來實現相同的目的。
他們所有進入這樣的類:
using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public static class QueryFilterExtensions
{
}
第一種方法:
public static void AddQueryFilter(this IMutableEntityType target, LambdaExpression filter)
{
if (target.QueryFilter == null)
target.QueryFilter = filter;
else
{
var parameter = target.QueryFilter.Parameters[0];
var left = target.QueryFilter.Body;
var right = filter.Body.ReplaceParameter(filter.Parameters[0], parameter);
var body = Expression.AndAlso(left, right);
target.QueryFilter = Expression.Lambda(body, parameter);
}
}
這是一種非通用方法,該方法使用AndAlso
(C# &&
)運算符將現有過濾器與通過的過濾器組合在一起,並顯示了上述的lambda表達式組合原理。
但是,它並不是直接有用的,就像在實體類型配置循環內部一樣(它可以,但是需要您手動構建lambda表達式,而不是讓C#編譯器執行此操作)。所以這是第二種方法:
public static void AddQueryFilter<T>(this IMutableEntityType target, Expression<Func<T, bool>> filter)
{
LambdaExpression targetFilter = filter;
if (target.ClrType != typeof(T))
{
var parameter = Expression.Parameter(target.ClrType, "e");
var body = filter.Body.ReplaceParameter(filter.Parameters[0], parameter);
targetFilter = Expression.Lambda(body, parameter);
}
target.AddQueryFilter(targetFilter);
}
這是一種通用方法-不太安全,但是允許您使用編譯時lambda表達式並將其綁定到實際的實體類型,如下所示:
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(IDeleted).IsAssignableFrom(entityType.ClrType))
entityType.AddQueryFilter<IDeleted>(e => e.DateTimeDeleted == null);
}
看起來更好,不是嗎:)
最後一個自定義擴展方法是對標準EF Core通用HasQueryFilter
方法的補充(替代):
public static EntityTypeBuilder<TEntity> AddQueryFilter<TEntity>(this EntityTypeBuilder<TEntity> target, Expression<Func<TEntity, bool>> filter)
where TEntity : class
{
target.Metadata.AddQueryFilter(filter);
return target;
}
並允許您更換
this.AddToDict<DbMyEntity>(t => t.Name == null || t.Name == "Something");
更方便
modelBuilder.Entity<DbMyEntity>()
.AddQueryFilter(t => t.Name == null || t.Name == "Something");
更新(EF Core 3.0): QueryFilter
屬性已替換為GetQueryFilter
和SetQueryFilter
擴展方法。