Combine BinaryExpression and Expression> in C#

c# entity-framework-core entity-framework-core-2.1 expression-trees lambda

Question

How can I blendBinaryExpression and Expression<Func<dynamic / T, bool>> ?

For instance:

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

To define worldwide filter for Entity Framework (EF) Core, please help. The issue is that I have to manual combining of several filters.

One filter might be included.ModelBuilder if the model is usedIDbDeleted interface.
Another might be manually added for a particular model. I have a list of all Expressions, and I mix them as follows:

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 make mistakes (the first is fromExpression.Equal second from fromt => t... ):

The filter expression 't => t => (Not(t. ...

Zzz-58-Zzz: code seems to be as follows:

[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);
        }
    }
}
1
4
7/25/2018 7:19:13 PM

Accepted Answer

You are combining the numbers zzz-5 and zzz-9. The key is to assemble expressions from the lambda expression bodies and rebind the parameters, as many blogs demonstrate how to combine lambda expressions.

The latter is often accomplished with a unique 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);
        }
    }
}

The combined query filters for EF Core are now discussed.

It seems too difficult to be using dictionaries and expression lists for what you are doing. Since the IMutableEntityType allows read/write access to the QueryFilter, a limited number of custom extension methods may do the same.

All they do in a lesson is the following:

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

public static class QueryFilterExtensions
{
}

First approach:

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 approach that utilizes the passed filter in combination with the outgoing filter.AndAlso (C# && ) operator and demonstrates the aforementioned rules for merging lambda expressions.

It can be, but you have to generate the lambda expression manually rather than allowing the C# compiler do it, so it's not really helpful immediately, like within your entity type setup loop. So here's the second approach:

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 that isn't exactly type-safe but nonetheless enables you to tie a compile-time lambda expression to the real object type in the manner shown below:

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

I think it looks better now:)

The last unique extension method is an addition to (or a substitute for) the common generic HasQueryFilter EF Core 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;
}

and allows you to replace

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

with the more practical

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

(EF Core 3.0) Update:QueryFilter property was swapped out forGetQueryFilter and SetQueryFilter extension techniques

5
11/18/2019 6:31:10 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow