Skip to content

Commit

Permalink
Added named query filters
Browse files Browse the repository at this point in the history
- Configure multiple query filters on a entity referenced by name.
- Ignore individual filters by name.

The current implmentation of query filter is kept but when ignoring the filters using the current extension method will ignore all filters (so also the named).

Fixes dotnet#8576 dotnet#10275 dotnet#21459
  • Loading branch information
mnijholt committed May 13, 2021
1 parent fe2e2ec commit 0cfa6b5
Show file tree
Hide file tree
Showing 21 changed files with 404 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
Expand Down Expand Up @@ -34,10 +35,17 @@ public override void ProcessModelFinalizing(
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
var queryFilter = entityType.GetQueryFilter();
if (queryFilter != null)
var queryFilters = entityType.GetQueryFilters();
if (queryFilters != null)
{
entityType.SetQueryFilter((LambdaExpression)DbSetAccessRewriter.Rewrite(modelBuilder.Metadata, queryFilter));
var rewritedQueryfilters = new Dictionary<string, LambdaExpression>();
foreach (var queryfilter in queryFilters)
{
rewritedQueryfilters.Add(queryfilter.Key,
(LambdaExpression)DbSetAccessRewriter.Rewrite(modelBuilder.Metadata, queryfilter.Value));
}

entityType.SetQueryFilters(rewritedQueryfilters);
}

#pragma warning disable CS0618 // Type or member is obsolete
Expand Down
30 changes: 30 additions & 0 deletions src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2396,6 +2396,36 @@ source.Provider is EntityQueryProvider
: source;
}

internal static readonly MethodInfo IgnoreQueryFilterMethodInfo
= typeof(EntityFrameworkQueryableExtensions)
.GetRequiredDeclaredMethod(nameof(IgnoreQueryFilter));

/// <summary>
/// Specifies that the current Entity Framework LINQ query should not have a specific model-level entity query filters applied.
/// </summary>
/// <typeparam name="TEntity"> The type of entity being queried. </typeparam>
/// <param name="source"> The source query. </param>
/// <param name="name"> The name of the query filter that should not be applied. </param>
/// <returns> A new query that will not apply the specified model-level entity query filters. </returns>
/// <exception cref="ArgumentNullException"> <paramref name="source" /> is <see langword="null" />. </exception>
public static IQueryable<TEntity> IgnoreQueryFilter<TEntity>(
this IQueryable<TEntity> source, [NotParameterized] string name)
where TEntity : class
{
Check.NotNull(source, nameof(source));
Check.NotEmpty(name, nameof(name));

return
source.Provider is EntityQueryProvider
? source.Provider.CreateQuery<TEntity>(
Expression.Call(
instance: null,
method: IgnoreQueryFilterMethodInfo.MakeGenericMethod(typeof(TEntity)),
arg0: source.Expression,
arg1: Expression.Constant(name)))
: source;
}

#endregion

#region Tracking
Expand Down
10 changes: 5 additions & 5 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -933,21 +933,21 @@ protected virtual void ValidateQueryFilters(

foreach (var entityType in model.GetEntityTypes())
{
if (entityType.GetQueryFilter() != null)
if (entityType.GetQueryFilters() != null)
{
if (entityType.BaseType != null)
{
throw new InvalidOperationException(
CoreStrings.BadFilterDerivedType(
entityType.GetQueryFilter(),
entityType.GetQueryFilters(),
entityType.DisplayName(),
entityType.GetRootType().DisplayName()));
}

if (entityType.IsOwned())
{
throw new InvalidOperationException(
CoreStrings.BadFilterOwnedType(entityType.GetQueryFilter(), entityType.DisplayName()));
CoreStrings.BadFilterOwnedType(entityType.GetQueryFilters(), entityType.DisplayName()));
}
}

Expand All @@ -956,8 +956,8 @@ protected virtual void ValidateQueryFilters(
n => !n.IsCollection
&& n.ForeignKey.IsRequired
&& n.IsOnDependent
&& n.ForeignKey.PrincipalEntityType.GetQueryFilter() != null
&& n.ForeignKey.DeclaringEntityType.GetQueryFilter() == null).FirstOrDefault();
&& n.ForeignKey.PrincipalEntityType.GetQueryFilters() != null
&& n.ForeignKey.DeclaringEntityType.GetQueryFilters() == null).FirstOrDefault();

if (requiredNavigationWithQueryFilter != null)
{
Expand Down
12 changes: 11 additions & 1 deletion src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,18 @@ public virtual EntityTypeBuilder Ignore(string propertyName)
/// <param name="filter"> The LINQ predicate expression. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual EntityTypeBuilder HasQueryFilter(LambdaExpression? filter)
=> HasQueryFilter("", filter);

/// <summary>
/// Specifies a named LINQ predicate expression that will automatically be applied to any queries targeting
/// this entity type.
/// </summary>
/// <param name="name"> The name of the LINQ predicate expression. </param>
/// <param name="filter"> The LINQ predicate expression. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual EntityTypeBuilder HasQueryFilter(string name, LambdaExpression? filter)
{
Builder.HasQueryFilter(filter, ConfigurationSource.Explicit);
Builder.HasQueryFilter(name, filter, ConfigurationSource.Explicit);

return this;
}
Expand Down
24 changes: 22 additions & 2 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public virtual EntityTypeBuilder<TEntity> Ignore(Expression<Func<TEntity, object
/// <param name="filter"> The LINQ predicate expression. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual EntityTypeBuilder<TEntity> HasQueryFilter(LambdaExpression? filter)
=> (EntityTypeBuilder<TEntity>)base.HasQueryFilter(filter);
=> (EntityTypeBuilder<TEntity>)base.HasQueryFilter("", filter);

/// <summary>
/// Specifies a LINQ predicate expression that will automatically be applied to any queries targeting
Expand All @@ -227,7 +227,27 @@ public virtual EntityTypeBuilder<TEntity> Ignore(Expression<Func<TEntity, object
/// <param name="filter"> The LINQ predicate expression. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual EntityTypeBuilder<TEntity> HasQueryFilter(Expression<Func<TEntity, bool>>? filter)
=> (EntityTypeBuilder<TEntity>)base.HasQueryFilter(filter);
=> (EntityTypeBuilder<TEntity>)base.HasQueryFilter("", filter);

/// <summary>
/// Specifies a named LINQ predicate expression that will automatically be applied to any queries targeting
/// this entity type.
/// </summary>
/// <param name="name">The name of the LINQ predicate expression. </param>
/// <param name="filter"> The LINQ predicate expression. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual EntityTypeBuilder<TEntity> HasQueryFilter(string name, LambdaExpression? filter)
=> (EntityTypeBuilder<TEntity>)base.HasQueryFilter(name, filter);

/// <summary>
/// Specifies a named LINQ predicate expression that will automatically be applied to any queries targeting
/// this entity type.
/// </summary>
/// <param name="name">The name of the LINQ predicate expression. </param>
/// <param name="filter"> The LINQ predicate expression. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual EntityTypeBuilder<TEntity> HasQueryFilter(string name, Expression<Func<TEntity, bool>>? filter)
=> (EntityTypeBuilder<TEntity>)base.HasQueryFilter(name, filter);

/// <summary>
/// Configures a query used to provide data for a keyless entity type.
Expand Down
12 changes: 9 additions & 3 deletions src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,16 @@ protected virtual void ProcessEntityTypeAnnotations(
annotations.Remove(CoreAnnotationNames.NavigationAccessMode);
annotations.Remove(CoreAnnotationNames.DiscriminatorProperty);

if (annotations.TryGetValue(CoreAnnotationNames.QueryFilter, out var queryFilter))
if (annotations.TryGetValue(CoreAnnotationNames.QueryFilter, out var queryFilters))
{
annotations[CoreAnnotationNames.QueryFilter] =
new QueryRootRewritingExpressionVisitor(runtimeEntityType.Model).Rewrite((Expression)queryFilter!);
var result = new Dictionary<string, LambdaExpression>();
foreach(var queryFilter in (Dictionary<string, LambdaExpression>)queryFilters!)
{
result.Add(queryFilter.Key,
(LambdaExpression)new QueryRootRewritingExpressionVisitor(runtimeEntityType.Model).Rewrite(queryFilter.Value!));

}
annotations[CoreAnnotationNames.QueryFilter] = result;
}

#pragma warning disable CS0612 // Type or member is obsolete
Expand Down
27 changes: 25 additions & 2 deletions src/EFCore/Metadata/IConventionEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,34 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas
LambdaExpression? SetQueryFilter(LambdaExpression? queryFilter, bool fromDataAnnotation = false);

/// <summary>
/// Returns the configuration source for <see cref="IReadOnlyEntityType.GetQueryFilter" />.
/// Sets a named LINQ expression filter automatically applied to queries for this entity type.
/// </summary>
/// <returns> The configuration source for <see cref="IReadOnlyEntityType.GetQueryFilter" />. </returns>
/// <param name="name"> The name of the expression filter. </param>
/// <param name="queryFilter"> The LINQ expression filter. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> The configured filter. </returns>
LambdaExpression? SetQueryFilter(string name, LambdaExpression? queryFilter, bool fromDataAnnotation = false);

/// <summary>
/// Returns the configuration source for <see cref="IReadOnlyEntityType.GetQueryFilter()" />.
/// </summary>
/// <returns> The configuration source for <see cref="IReadOnlyEntityType.GetQueryFilter()" />. </returns>
ConfigurationSource? GetQueryFilterConfigurationSource();

/// <summary>
/// Sets the LINQ expression filters automatically applied to queries for this entity type.
/// </summary>
/// <param name="queryFilters"> The LINQ expression filters. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> The configured filters. </returns>
Dictionary<string, LambdaExpression>? SetQueryFilters(Dictionary<string, LambdaExpression>? queryFilters, bool fromDataAnnotation = false);

/// <summary>
/// Returns the configuration source for <see cref="IReadOnlyEntityType.GetQueryFilters()" />.
/// </summary>
/// <returns> The configuration source for <see cref="IReadOnlyEntityType.GetQueryFilters()" />. </returns>
ConfigurationSource? GetQueryFiltersConfigurationSource();

/// <summary>
/// Returns the property that will be used for storing a discriminator value.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/EFCore/Metadata/IReadOnlyEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ public interface IReadOnlyEntityType : IReadOnlyTypeBase
/// <returns> The LINQ expression filter. </returns>
LambdaExpression? GetQueryFilter();

/// <summary>
/// Gets a named LINQ expression filter automatically applied to queries for this entity type.
/// </summary>
/// <param name="name">The name of the LINQ expression.</param>
/// <returns> The LINQ expression filter. </returns>
LambdaExpression? GetQueryFilter(string name);

/// <summary>
/// Gets the LINQ expression filters automatically applied to queries for this entity type.
/// </summary>
/// <returns> The LINQ expression filters. </returns>
IDictionary<string, LambdaExpression>? GetQueryFilters();

/// <summary>
/// Returns the property that will be used for storing a discriminator value.
/// </summary>
Expand Down
Loading

0 comments on commit 0cfa6b5

Please sign in to comment.