使用 .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);
}
}
所以我在我的 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);
}
}