-
-
Save haacked/febe9e88354fb2f4a4eb11ba88d64c24 to your computer and use it in GitHub Desktop.
/* | |
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); | |
} | |
} | |
} |
@fiseni Ah yeah, I hadn't updated the gist with my latest changes. Done!
May we have permission to include this in the .NET ORM Cookbook?
@Grauenwolf be my guest!
Thank you
Thank you for great class. May we include it and use it inside next project?
https://github.com/AshkanAbd/efCoreSoftDeletes/tree/master/SoftDeletes
@stzoran1 yes, I updated the code sample to include the license terms. This code sample is licensed under the MIT license. Anyone can use it with proper attribution.
Thank you very much. I updated license header ;)
superhuman skills...thanks this saved my behind.
I've updated the code for EF Core 5.0.2 and above. It no longer relies on internal interfaces. If you want the version for EF Core 3, you'll have to look at the previous revision for this gist.
Thats more simple and working.
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
public static class ModelBuilderExtensions
{
public static void ApplyGlobalFilters<TInterface>(this ModelBuilder modelBuilder, Expression<Func<TInterface, bool>> expression)
{
var entities = modelBuilder.Model
.GetEntityTypes()
.Where(t => t.BaseType == null)
.Select(t => t.ClrType)
.Where(t => typeof(TInterface).IsAssignableFrom(t));
foreach (var entity in entities)
{
var newParam = Expression.Parameter(entity);
var newbody = ReplacingExpressionVisitor.Replace(expression.Parameters.Single(), newParam, expression.Body);
modelBuilder.Entity(entity).HasQueryFilter(Expression.Lambda(newbody, newParam));
}
}
public static void ApplyGlobalInclude<TInterface, TProperty>(this ModelBuilder modelBuilder, Expression<Func<TInterface, TProperty>> expression) where TProperty : class
{
var entities = modelBuilder.Model
.GetEntityTypes()
.Where(t => t.BaseType == null)
.Select(t => t.ClrType)
.Where(t => typeof(TInterface).IsAssignableFrom(t));
foreach (var entity in entities)
{
modelBuilder.Entity(entity).Navigation(expression.ReturnType.Name).AutoInclude();
}
}
}
Use three quotes ``` instead of one ` to get formatting.
@Grauenwolf thank you
You mean entity core 3.1, not just runtime 3.1, right? Actually I just checked now, it worked for me too, with few minor changes.
The namespace is not available. Instead of
using Remotion.Linq.Parsing.ExpressionVisitors;
, we should useusing Microsoft.EntityFrameworkCore.Query;
Also,
internalEntityTypeBuilder.Metadata.QueryFilter
should be replaced withinternalEntityTypeBuilder.Metadata.GetQueryFilter()