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

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

问题描述

如何将BinaryExpressionExpression>结合起来?

例如:

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 泛型的补充(替代)Has>QueryFilter

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&lt;Func&lt;dynamic,bool&gt;&gt;在 C# 中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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