Mak*_*kla 4 c# lambda expression-trees entity-framework-core entity-framework-core-2.1
如何结合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);
}
Run Code Online (Sandbox Code Playgroud)
我正在尝试为实体框架(EF)核心定义全局过滤器。问题是我必须手动组合多个过滤器。
ModelBuilder如果模型实现IDbDeleted接口,则可以添加一个过滤器。
可以为特定模型手动添加另一个。基本思想是我拥有所有表达式的列表,然后将它们组合:
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);
Run Code Online (Sandbox Code Playgroud)
当然我得到了错误(第一个来自Expression.Equal,第二个来自t => t...):
过滤器表达式't => t =>(Not(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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
您正在将表达式与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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在有关EF Core组合查询过滤器。
对于正在执行的操作,使用字典和表达式列表似乎过于复杂。由于IMutableEntityType提供了对的读/写访问权限QueryFilter,因此可以使用一小组自定义扩展方法来实现相同的目的。
他们所有进入这样的类:
using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public static class QueryFilterExtensions
{
}
Run Code Online (Sandbox Code Playgroud)
第一种方法:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是一种非通用方法,该方法使用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);
}
Run Code Online (Sandbox Code Playgroud)
这是一种通用方法-不太安全,但是允许您使用编译时lambda表达式并将其绑定到实际的实体类型,如下所示:
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(IDeleted).IsAssignableFrom(entityType.ClrType))
entityType.AddQueryFilter<IDeleted>(e => e.DateTimeDeleted == null);
}
Run Code Online (Sandbox Code Playgroud)
看起来更好,不是吗:)
最后一个自定义扩展方法是对标准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;
}
Run Code Online (Sandbox Code Playgroud)
并允许您更换
this.AddToDict<DbMyEntity>(t => t.Name == null || t.Name == "Something");
Run Code Online (Sandbox Code Playgroud)
更方便
modelBuilder.Entity<DbMyEntity>()
.AddQueryFilter(t => t.Name == null || t.Name == "Something");
Run Code Online (Sandbox Code Playgroud)
更新(EF Core 3.0): QueryFilter属性已替换为GetQueryFilter和SetQueryFilter扩展方法。