使用 .NET 6 创建通用 EF 查询过滤器

Creating a generic EF Query Filter with .NET 6

所以我在我的 DbContext 上有一个这样的过滤器:

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

    }

我想将其抽象为更通用的内容,如下所示,但它不喜欢我为过滤器传递的参数。对导致问题的原因有什么想法吗?

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

标记的答案已经完成了 99%,但从技术上讲是行不通的。这是一个有效的实现:

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

试试这个实现。抱歉,未测试:

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