简体   繁体   English

结合 BinaryExpression 和 Expression <Func<dynamic, bool> &gt; 在 C# 中

[英]Combine BinaryExpression and Expression<Func<dynamic, bool>> in C#

How can I combine BinaryExpression and Expression<Func<dynamic / T, bool>> ?如何结合BinaryExpressionExpression<Func<dynamic / T, bool>>

For example:例如:

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);
}

I am trying to define global filter for Entity Framework (EF) Core.我正在尝试为实体框架 (EF) 核心定义全局过滤器 The problem is I must manually combine multiple filters .问题是我必须手动组合多个过滤器

One filter may be added in ModelBuilder if model implements IDbDeleted interface.如果模型实现了IDbDeleted接口,则可以在ModelBuilder器中添加一个过滤器。
Another could be added manually for specific model.另一个可以为特定模型手动添加。 Basic idea is I have a list of all Expressions, and then combine them:基本思想是我有一个所有表达式的列表,然后将它们组合起来:

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);

Of course I get error (first is from Expression.Equal and second from t => t... ):当然我得到错误(第一个来自Expression.Equal ,第二个来自t => t... ):

The filter expression 't => t => (Not(t. ...过滤器表达式 't => t => (Not(t. ...

Edited : code looks something like that:编辑:代码看起来像这样:

[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);
        }
    }
}

You are mixing expressions with lambda expressions .您正在将表达式lambda 表达式混合使用。 There are many posts showing how you can combine lambda expressions, but the essential part is to compose expressions from lambda expression bodies and rebind the parameters .有很多帖子展示了如何组合 lambda 表达式,但最重要的部分是从 lambda 表达式主体组合表达式并重新绑定参数

The later is usually achieved by a custom ExpressionVisitor like this:后者通常由自定义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);
        }
    }
}

Now regarding the EF Core combined query filters.现在关于 EF Core 组合查询过滤器。

Using dictionaries and expression lists seems overcomplicated for what are you doing.使用字典和表达式列表对于你在做什么来说似乎过于复杂。 Since IMutableEntityType provides read/write access to the QueryFilter , the same can be achieved with a small set of a custom extension methods.由于IMutableEntityType提供对QueryFilter读/写访问,因此可以使用一QueryFilter自定义扩展方法来实现相同的目的。

All they go inside a class like this:它们都进入这样的类:

using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

public static class QueryFilterExtensions
{
}

First method:第一种方法:

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);
    }
}

This is a non generic method which combines the exiting filter with passed filter using AndAlso (C# && ) operator and shows the aforementioned lambda expression combining principles.这是一个非通用方法,它使用AndAlso (C# && ) 运算符将退出过滤器与通过过滤器组合在一起,并显示了上述 lambda 表达式组合原理。

However it's not so useful directly, like inside your entity type configuration loop (it can, but requires you to build manually the lambda expression instead of letting C# compiler do that).然而,它并不是那么直接有用,就像在实体类型配置循环中一样(它可以,但需要您手动构建 lambda 表达式,而不是让 C# 编译器这样做)。 So here comes the second method:于是就出现了第二种方法:

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);
}

It's a generic method - not quite type-safe, but allows you to use a compile time lambda expression and bind it to the actual entity type as follows:这是一个通用方法 - 不是很类型安全,但允许您使用编译时 lambda 表达式并将其绑定到实际实体类型,如下所示:

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    if (typeof(IDeleted).IsAssignableFrom(entityType.ClrType))
        entityType.AddQueryFilter<IDeleted>(e => e.DateTimeDeleted == null);
}

Looks better, isn't it :)看起来更好,不是吗:)

The last custom extension method is complement to (replacement of) the standard EF Core generic HasQueryFilter method:最后一个自定义扩展方法是对标准 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;
}

and allows you to replace并允许您更换

this.AddToDict<DbMyEntity>(t => t.Name == null || t.Name == "Something");

with the more convenient随着更方便

modelBuilder.Entity<DbMyEntity>()
    .AddQueryFilter(t => t.Name == null || t.Name == "Something");

Update (EF Core 3.0): QueryFilter property has been replaced with GetQueryFilter and SetQueryFilter extension methods.更新(EF Core 3.0): QueryFilter属性已替换为GetQueryFilterSetQueryFilter扩展方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM