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);
}
}
}
CodePudding user response:
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);
}
}