Skip to content

Instantly share code, notes, and snippets.

@haacked
Last active September 5, 2024 09:26
Show Gist options
  • Save haacked/febe9e88354fb2f4a4eb11ba88d64c24 to your computer and use it in GitHub Desktop.
Save haacked/febe9e88354fb2f4a4eb11ba88d64c24 to your computer and use it in GitHub Desktop.
Example of applying an EF Core global query filter on all entity types that implement an interface
/*
Copyright Phil Haack
Licensed under the MIT license - https://github.com/haacked/CodeHaacks/blob/main/LICENSE.
*/
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query;
public static class ModelBuilderExtensions
{
static readonly MethodInfo SetQueryFilterMethod = typeof(ModelBuilderExtensions)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.Single(t => t.IsGenericMethod && t.Name == nameof(SetQueryFilter));
public static void SetQueryFilterOnAllEntities<TEntityInterface>(
this ModelBuilder builder,
Expression<Func<TEntityInterface, bool>> filterExpression)
{
foreach (var type in builder.Model.GetEntityTypes()
.Where(t => t.BaseType == null)
.Select(t => t.ClrType)
.Where(t => typeof(TEntityInterface).IsAssignableFrom(t)))
{
builder.SetEntityQueryFilter(
type,
filterExpression);
}
}
static void SetEntityQueryFilter<TEntityInterface>(
this ModelBuilder builder,
Type entityType,
Expression<Func<TEntityInterface, bool>> filterExpression)
{
SetQueryFilterMethod
.MakeGenericMethod(entityType, typeof(TEntityInterface))
.Invoke(null, new object[] { builder, filterExpression });
}
static void SetQueryFilter<TEntity, TEntityInterface>(
this ModelBuilder builder,
Expression<Func<TEntityInterface, bool>> filterExpression)
where TEntityInterface : class
where TEntity : class, TEntityInterface
{
var concreteExpression = filterExpression
.Convert<TEntityInterface, TEntity>();
builder.Entity<TEntity>()
.AppendQueryFilter(concreteExpression);
}
// CREDIT: This comment by magiak on GitHub https://github.com/dotnet/efcore/issues/10275#issuecomment-785916356
static void AppendQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expression<Func<T, bool>> expression)
where T : class
{
var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType);
var expressionFilter = ReplacingExpressionVisitor.Replace(
expression.Parameters.Single(), parameterType, expression.Body);
if (entityTypeBuilder.Metadata.GetQueryFilter() != null)
{
var currentQueryFilter = entityTypeBuilder.Metadata.GetQueryFilter();
var currentExpressionFilter = ReplacingExpressionVisitor.Replace(
currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body);
expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter);
}
var lambdaExpression = Expression.Lambda(expressionFilter, parameterType);
entityTypeBuilder.HasQueryFilter(lambdaExpression);
}
}
public static class ExpressionExtensions
{
// This magic is courtesy of this StackOverflow post.
// https://stackoverflow.com/questions/38316519/replace-parameter-type-in-lambda-expression
// I made some tweaks to adapt it to our needs - @haacked
public static Expression<Func<TTarget, bool>> Convert<TSource, TTarget>(
this Expression<Func<TSource, bool>> root)
{
var visitor = new ParameterTypeVisitor<TSource, TTarget>();
return (Expression<Func<TTarget, bool>>)visitor.Visit(root);
}
class ParameterTypeVisitor<TSource, TTarget> : ExpressionVisitor
{
private ReadOnlyCollection<ParameterExpression> _parameters;
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameters?.FirstOrDefault(p => p.Name == node.Name)
?? (node.Type == typeof(TSource) ? Expression.Parameter(typeof(TTarget), node.Name) : node);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_parameters = VisitAndConvert(node.Parameters, "VisitLambda");
return Expression.Lambda(Visit(node.Body), _parameters);
}
}
}
@sarowa36
Copy link

sarowa36 commented Dec 4, 2022

@Grauenwolf thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment