结合BinaryExpression和Expression Func动态,bool在C#中 [英] Combine BinaryExpression and Expression<Func<dynamic, bool>> in C#

查看:161
本文介绍了结合BinaryExpression和Expression Func动态,bool在C#中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何结合BinaryExpressionExpression<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);
}

我正在尝试为实体定义全局过滤器框架(EF)核心.问题是我必须手动组合多个过滤器.

I am trying to define global filter for Entity Framework (EF) Core. The problem is I must manually combine multiple filters.

如果模型实现IDbDeleted接口,则可以在ModelBuilder中添加一个过滤器.
可以为特定模型手动添加另一个.基本思想是我拥有所有表达式的列表,然后将它们组合在一起:

One filter may be added in ModelBuilder if model implements IDbDeleted interface.
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);

我当然会出错(第一个来自Expression.Equal,第二个来自t => t...):

Of course I get error (first is from Expression.Equal and second from t => t...):

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

The filter expression '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);
        }
    }
}

推荐答案

您正在混合 lambda表达式.有许多文章显示了如何组合lambda表达式,但是必不可少的部分是根据lambda表达式参数.

You are mixing expressions with lambda expressions. 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.

后者通常是通过自定义 ExpressionVisitor 像这样:

The later is usually achieved by a custom ExpressionVisitor like this:

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组合查询过滤器.

Now regarding the EF Core combined query filters.

使用字典和表达式列表对于您正在执行的操作似乎过于复杂.由于 IMutableEntityType 提供对 QueryFilter ,使用一小套自定义扩展方法也可以实现相同的目的.

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.

他们全部进入这样的类中:

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
{
}

第一种方法:

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表达式组合原理.

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.

但是,它并不是直接有用的,就像在实体类型配置循环内部一样(它可以,但是需要您手动构建lambda表达式,而不是让C#编译器执行此操作).第二种方法是这样:

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

这是一种通用方法-不太安全,但是允许您使用编译时lambda表达式并将其绑定到实际的实体类型,如下所示:

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:

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

最后一个自定义扩展方法是对标准EF Core通用

The last custom extension method is complement to (replacement of) the standard EF Core generic HasQueryFilter method:

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属性已由GetQueryFilterSetQueryFilter扩展方法替换.

Update (EF Core 3.0): QueryFilter property has been replaced with GetQueryFilter and SetQueryFilter extension methods.

这篇关于结合BinaryExpression和Expression Func动态,bool在C#中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆