Skip to content

Instantly share code, notes, and snippets.

@dotnet22
Forked from haacked/ModelBuilderExtensions.cs
Created September 5, 2024 09:26
Show Gist options
  • Save dotnet22/edb34a5eb4e588b0217b8f7d8c968785 to your computer and use it in GitHub Desktop.
Save dotnet22/edb34a5eb4e588b0217b8f7d8c968785 to your computer and use it in GitHub Desktop.

Revisions

  1. @haacked haacked revised this gist Mar 23, 2021. 1 changed file with 8 additions and 19 deletions.
    27 changes: 8 additions & 19 deletions ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -51,40 +51,29 @@ static void SetQueryFilter<TEntity, TEntityInterface>(
    var concreteExpression = filterExpression
    .Convert<TEntityInterface, TEntity>();
    builder.Entity<TEntity>()
    .AddQueryFilter(concreteExpression);
    .AppendQueryFilter(concreteExpression);
    }

    // CREDIT: The AddQueryFilter and GetInternalEntityTypeBuilder methods come from this comment:
    // https://github.com/aspnet/EntityFrameworkCore/issues/10275#issuecomment-457504348
    // And was written by https://github.com/YZahringer
    static void AddQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expression<Func<T, bool>> expression)
    // 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);

    var internalEntityTypeBuilder = entityTypeBuilder.GetInternalEntityTypeBuilder();
    var queryFilter = internalEntityTypeBuilder?.Metadata.GetQueryFilter();
    if (queryFilter != null)
    if (entityTypeBuilder.Metadata.GetQueryFilter() != null)
    {
    var currentQueryFilter = entityTypeBuilder.Metadata.GetQueryFilter();
    var currentExpressionFilter = ReplacingExpressionVisitor.Replace(
    queryFilter.Parameters.Single(), parameterType, queryFilter.Body);
    currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body);
    expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter);
    }

    var lambdaExpression = Expression.Lambda(expressionFilter, parameterType);
    entityTypeBuilder.HasQueryFilter(lambdaExpression);
    }

    static InternalEntityTypeBuilder? GetInternalEntityTypeBuilder(
    this EntityTypeBuilder entityTypeBuilder)
    {
    var internalEntityTypeBuilder = typeof(EntityTypeBuilder)
    .GetProperty("Builder", BindingFlags.NonPublic | BindingFlags.Instance)?
    .GetValue(entityTypeBuilder) as InternalEntityTypeBuilder;

    return internalEntityTypeBuilder;
    }
    }

    public static class ExpressionExtensions
  2. @haacked haacked revised this gist Aug 7, 2020. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    /*
    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;
  3. @haacked haacked revised this gist Apr 30, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using Microsoft.EntityFrameworkCore.Metadata.Internal;
    using Remotion.Linq.Parsing.ExpressionVisitors;
    using Microsoft.EntityFrameworkCore.Query;

    public static class ModelBuilderExtensions
    {
  4. @haacked haacked revised this gist Apr 30, 2020. 1 changed file with 9 additions and 0 deletions.
    9 changes: 9 additions & 0 deletions ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,12 @@
    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 Remotion.Linq.Parsing.ExpressionVisitors;

    public static class ModelBuilderExtensions
    {
    static readonly MethodInfo SetQueryFilterMethod = typeof(ModelBuilderExtensions)
  5. @haacked haacked revised this gist Apr 30, 2020. 1 changed file with 7 additions and 16 deletions.
    23 changes: 7 additions & 16 deletions ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -1,13 +1,3 @@
    using System;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using Microsoft.EntityFrameworkCore.Metadata.Internal;
    using Remotion.Linq.Parsing.ExpressionVisitors;

    public static class ModelBuilderExtensions
    {
    static readonly MethodInfo SetQueryFilterMethod = typeof(ModelBuilderExtensions)
    @@ -23,7 +13,7 @@ public static void SetQueryFilterOnAllEntities<TEntityInterface>(
    .Select(t => t.ClrType)
    .Where(t => typeof(TEntityInterface).IsAssignableFrom(t)))
    {
    builder.SetEntityQueryFilter<TEntityInterface>(
    builder.SetEntityQueryFilter(
    type,
    filterExpression);
    }
    @@ -51,7 +41,7 @@ static void SetQueryFilter<TEntity, TEntityInterface>(
    .AddQueryFilter(concreteExpression);
    }

    // The AddQueryFilter and GetInternalEntityTypeBuilder methods come from this comment:
    // CREDIT: The AddQueryFilter and GetInternalEntityTypeBuilder methods come from this comment:
    // https://github.com/aspnet/EntityFrameworkCore/issues/10275#issuecomment-457504348
    // And was written by https://github.com/YZahringer
    static void AddQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expression<Func<T, bool>> expression)
    @@ -61,19 +51,20 @@ static void AddQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expressi
    expression.Parameters.Single(), parameterType, expression.Body);

    var internalEntityTypeBuilder = entityTypeBuilder.GetInternalEntityTypeBuilder();
    if (internalEntityTypeBuilder.Metadata.QueryFilter != null)
    var queryFilter = internalEntityTypeBuilder?.Metadata.GetQueryFilter();
    if (queryFilter != null)
    {
    var currentQueryFilter = internalEntityTypeBuilder.Metadata.QueryFilter;
    var currentExpressionFilter = ReplacingExpressionVisitor.Replace(
    currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body);
    queryFilter.Parameters.Single(), parameterType, queryFilter.Body);
    expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter);
    }

    var lambdaExpression = Expression.Lambda(expressionFilter, parameterType);
    entityTypeBuilder.HasQueryFilter(lambdaExpression);
    }

    static InternalEntityTypeBuilder GetInternalEntityTypeBuilder(this EntityTypeBuilder entityTypeBuilder)
    static InternalEntityTypeBuilder? GetInternalEntityTypeBuilder(
    this EntityTypeBuilder entityTypeBuilder)
    {
    var internalEntityTypeBuilder = typeof(EntityTypeBuilder)
    .GetProperty("Builder", BindingFlags.NonPublic | BindingFlags.Instance)?
  6. @haacked haacked revised this gist Aug 20, 2019. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -51,6 +51,9 @@ static void SetQueryFilter<TEntity, TEntityInterface>(
    .AddQueryFilter(concreteExpression);
    }

    // The AddQueryFilter and GetInternalEntityTypeBuilder methods come from this comment:
    // https://github.com/aspnet/EntityFrameworkCore/issues/10275#issuecomment-457504348
    // And was written by https://github.com/YZahringer
    static void AddQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expression<Func<T, bool>> expression)
    {
    var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType);
  7. @haacked haacked revised this gist Aug 20, 2019. 1 changed file with 35 additions and 10 deletions.
    45 changes: 35 additions & 10 deletions ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,12 @@
    using System;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using Microsoft.EntityFrameworkCore.Metadata.Internal;
    using Remotion.Linq.Parsing.ExpressionVisitors;

    public static class ModelBuilderExtensions
    {
    @@ -44,16 +48,37 @@ static void SetQueryFilter<TEntity, TEntityInterface>(
    var concreteExpression = filterExpression
    .Convert<TEntityInterface, TEntity>();
    builder.Entity<TEntity>()
    .HasQueryFilter(concreteExpression);
    .AddQueryFilter(concreteExpression);
    }
    }

    static void AddQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expression<Func<T, bool>> expression)
    {
    var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType);
    var expressionFilter = ReplacingExpressionVisitor.Replace(
    expression.Parameters.Single(), parameterType, expression.Body);

    // -------------------------------------------------------
    var internalEntityTypeBuilder = entityTypeBuilder.GetInternalEntityTypeBuilder();
    if (internalEntityTypeBuilder.Metadata.QueryFilter != null)
    {
    var currentQueryFilter = internalEntityTypeBuilder.Metadata.QueryFilter;
    var currentExpressionFilter = ReplacingExpressionVisitor.Replace(
    currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body);
    expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter);
    }

    using System;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Linq.Expressions;
    var lambdaExpression = Expression.Lambda(expressionFilter, parameterType);
    entityTypeBuilder.HasQueryFilter(lambdaExpression);
    }

    static InternalEntityTypeBuilder GetInternalEntityTypeBuilder(this EntityTypeBuilder entityTypeBuilder)
    {
    var internalEntityTypeBuilder = typeof(EntityTypeBuilder)
    .GetProperty("Builder", BindingFlags.NonPublic | BindingFlags.Instance)?
    .GetValue(entityTypeBuilder) as InternalEntityTypeBuilder;

    return internalEntityTypeBuilder;
    }
    }

    public static class ExpressionExtensions
    {
    @@ -74,13 +99,13 @@ class ParameterTypeVisitor<TSource, TTarget> : ExpressionVisitor
    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);
    ?? (node.Type == typeof(TSource) ? Expression.Parameter(typeof(TTarget), node.Name) : node);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
    _parameters = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
    _parameters = VisitAndConvert(node.Parameters, "VisitLambda");
    return Expression.Lambda(Visit(node.Body), _parameters);
    }
    }
    }
    }
  8. @haacked haacked revised this gist Jul 29, 2019. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -15,6 +15,7 @@ public static void SetQueryFilterOnAllEntities<TEntityInterface>(
    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)))
    {
  9. @haacked haacked created this gist Jul 26, 2019.
    85 changes: 85 additions & 0 deletions ModelBuilderExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,85 @@
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using Microsoft.EntityFrameworkCore;

    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()
    .Select(t => t.ClrType)
    .Where(t => typeof(TEntityInterface).IsAssignableFrom(t)))
    {
    builder.SetEntityQueryFilter<TEntityInterface>(
    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>()
    .HasQueryFilter(concreteExpression);
    }
    }

    // -------------------------------------------------------

    using System;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Linq.Expressions;

    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<ParameterExpression>(node.Parameters, "VisitLambda");
    return Expression.Lambda(Visit(node.Body), _parameters);
    }
    }
    }