0

So I have a filter on my DbContext like this:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        var stringList = new List<string>(); // <-- actually a service
        var guidList = new List<Guid?>(); // <-- actually a service

        modelBuilder.Entity<MyEntity>().HasQueryFilter(x =>
            (stringList.Contains(x.StringId))
                || (guidList.Contains(x.GuidId ?? Guid.Empty)));

    }

I'd like to abstract it out to something more generic like the below, but it doesn't like the params I'm passing for my filter. Any thoughts on what's causing the issue?

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.MyFilter<MyEntity>(x => x.StringId,
            x => x.GuidId);

    }


        
    public static void MyFilter<TEntity>(this ModelBuilder modelBuilder,
        Func<TEntity, string> filterOne,
        Func<TEntity, Guid?> filterTwo)
        where TEntity : class
    {
        var stringList = new List<string>(); // <-- actually a service
        var guidList = new List<Guid?>(); // <-- actually a service

        Expression<Func<TEntity, bool>> filterExpr = entity =>
            (stringList.Contains(filterOne(entity)))
                || (guidList.Contains(filterTwo(entity) ?? Guid.Empty));
        
        foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes()
                     .Where(m => m.ClrType.IsAssignableTo(typeof(TEntity))))
        {
            // modify expression to handle correct child type
            var parameter = Expression.Parameter(mutableEntityType.ClrType);
            var body = ReplacingExpressionVisitor
                .Replace(filterExpr.Parameters.First(), parameter, filterExpr.Body);
            var lambdaExpression = Expression.Lambda(body, parameter);

            // set filter
            mutableEntityType.SetQueryFilter(lambdaExpression);
        }
    }

The marked answer was 99% of the way there but technically won't work. here is a working implementation:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using System.ComponentModel.DataAnnotations;

public class Program
{
    public static void Main()
    {
        var options = new DbContextOptionsBuilder<TestDbContext>().UseInMemoryDatabase("inmemory").Options;
        var context = new TestDbContext(options);
        var fakeEntityVisible = new TestingEntity();
        fakeEntityVisible.StringId = "abc123";
        context.TestingEntities.AddRange(fakeEntityVisible, new TestingEntity(), new TestingEntity());
        context.SaveChanges();
        var something = context.TestingEntities.ToList();
        var count = something.Count;
        Console.WriteLine($"Count {count} should be 1");
    }

    public class TestingEntity
    {
        [Key]
        public Guid Id { get; set; }

        public string StringId { get; set; }
    }

    public class TestDbContext : DbContext
    {
        public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
        {
        }

        public DbSet<TestingEntity> TestingEntities { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            var stringList = new List<string>(); // <-- this is really a service
            stringList.Add("abc123");
            // THIS IS BROKEN
            modelBuilder.SimpleFilter<TestingEntity>(x => x.StringId);
        // THIS WORKS -- HOW TO ABSTRACT THIS?
        // modelBuilder.Entity<TestingEntity>().HasQueryFilter(x => stringList.Contains(x.StringId));
        }
    }
}

public static class Extensions
{
    public static void MyFilter<TEntity>(this ModelBuilder modelBuilder, Func<TEntity, string> stringId)
        where TEntity : class
    {
        var stringList = new List<string>(); // <-- this is really a service
        stringList.Add("abc123");
        Expression<Func<TEntity, bool>> filterExpr = entity => stringList.Contains(stringId(entity));
        foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes().Where(m => m.ClrType.IsAssignableTo(typeof(TEntity))))
        {
            // modify expression to handle correct child type
            var parameter = Expression.Parameter(mutableEntityType.ClrType);
            var body = ReplacingExpressionVisitor.Replace(filterExpr.Parameters.First(), parameter, filterExpr.Body);
            var lambdaExpression = Expression.Lambda(body, parameter);
            // set filter
            mutableEntityType.SetQueryFilter(lambdaExpression);
        }
    }

    public static void SimpleFilter<TEntity>(this ModelBuilder modelBuilder, Expression<Func<TEntity, string>> filterOne)
        where TEntity : class
    {
        var stringList = new List<string>(); // <-- this is really a service
        stringList.Add("abc123");
        var stringListExpr = Expression.Constant(stringList);
        foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes().Where(m => m.ClrType.IsAssignableTo(typeof(TEntity))))
        {
            var parameter = Expression.Parameter(mutableEntityType.ClrType, "e");
            var filter1 = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[]{typeof(string)}, stringListExpr, ReplacingExpressionVisitor.Replace(filterOne.Parameters[0], parameter, filterOne.Body));
            
            var body = filter1; 
            var lambdaExpression = Expression.Lambda(body, parameter);
            // set filter
            mutableEntityType.SetQueryFilter(lambdaExpression);
        }
    }
}
3
  • Problem is mixing Func<T, bool> with Expression<Func<T, bool>>. When you build ExpressionTree it always should be Expression. Commented Feb 16, 2022 at 18:52
  • any recommendations on alternatives to resolve it? Commented Feb 16, 2022 at 18:57
  • Well will try to show how to do that. Commented Feb 16, 2022 at 18:59

1 Answer 1

1

Try this implementation. Sorry, not not tested:

public static void MyFilter<TEntity>(this ModelBuilder modelBuilder,
    Expression<Func<TEntity, string>> filterOne,
    Expression<Func<TEntity, Guid?>> filterTwo)
    where TEntity : class
{
    var stringList = new List<string>(); // <-- actually a service
    var guidList = new List<Guid?>(); // <-- actually a service

    var stringListExpr = Expression.Constant(stringList);
    var guidListExpr = Expression.Constant(guidList);

    foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes()
                    .Where(m => m.ClrType.IsAssignableTo(typeof(TEntity))))
    {
        var parameter = Expression.Parameter(mutableEntityType.ClrType, "e");

        // stringList.Contains(e.StrField)
        var filter1 = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new []{typeof(string)}, 
            stringListExpr,
            ReplacingExpressionVisitor.Replace(filterOne.Parameters[0], parameter, filterOne.Body));

        // guidList.Contains(e.GuidField)
        var filter2 = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new []{typeof(Guid?)}, 
            guidListExpr,
            ReplacingExpressionVisitor.Replace(filterTwo.Parameters[0], parameter, filterTwo.Body));

        // stringList.Contains(e.StrField) || guidList.Contains(e.GuidField)
        var body = Expression.OrElse(filter1, filter2);

        // e => stringList.Contains(e.StrField) || guidList.Contains(e.GuidField)
        var lambdaExpression = Expression.Lambda(body, parameter);

        // set filter
        mutableEntityType.SetQueryFilter(lambdaExpression);
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

this is close, but getting an issue i can't resolve. made a fiddle for reference and removed the dual list aspect to keep it even more simple. dotnetfiddle.net/D3laqQ
Sorry missed that you have passed Func<>, it should. be Expression<Func<TEntity, string>> filterOne, Expression<Func<TEntity, Guid?>> filterTwo)
i could i swore i tried that, but i must have updated something else along with it. regardless, that got it working (and the fiddle is updated). thanks! 🍻

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.