-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix to #16323 - Query: add support for keyless entity types with defi…
…ning query During nav expansion, when visiting EntityQueryables we now peek into entity metadata looking for defining query and query filter. If they are found, nav expansion applies them and expands navigations in the resulting query (recursively). Also, fix to #17111 - Cannot use DbSet in query filter DbSet accesses are converted into EntityQueryables during model finialization. We need to do this early, because otherwise we would need a dbcontext to create a queryable, and that dbcontext could be disposed at the time nav expansion runs. Since FromSql/FromSqlRaw/FromSqlInterpolated are constrained to DbSet<T> they need to be converted to FromSqlOnQueryable at the same time.
- Loading branch information
Showing
26 changed files
with
432 additions
and
247 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
....Relational/Metadata/Conventions/RelationalQueryFilterDefiningQueryRewritingConvention.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Linq.Expressions; | ||
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions | ||
{ | ||
public class RelationalQueryFilterDefiningQueryRewritingConvention : QueryFilterDefiningQueryRewritingConvention | ||
{ | ||
/// <summary> | ||
/// Creates a new instance of <see cref="RelationalQueryFilterDefiningQueryRewritingConvention" />. | ||
/// </summary> | ||
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param> | ||
/// <param name="relationalDependencies"> Parameter object containing relational dependencies for this convention. </param> | ||
public RelationalQueryFilterDefiningQueryRewritingConvention( | ||
[NotNull] ProviderConventionSetBuilderDependencies dependencies, | ||
[NotNull] RelationalConventionSetBuilderDependencies relationalDependencies) | ||
: base(dependencies) | ||
{ | ||
DbSetAccessRewriter = new RelationalDbSetAccessRewritingExpressionVisitor(Dependencies.ContextType); | ||
} | ||
|
||
protected class RelationalDbSetAccessRewritingExpressionVisitor : DbSetAccessRewritingExpressionVisitor | ||
{ | ||
public RelationalDbSetAccessRewritingExpressionVisitor(Type contextType) | ||
: base(contextType) | ||
{ | ||
} | ||
|
||
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) | ||
{ | ||
if (methodCallExpression.Method.DeclaringType == typeof(RelationalQueryableExtensions) | ||
&& (methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.FromSql) | ||
|| methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.FromSqlRaw) | ||
|| methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.FromSqlInterpolated))) | ||
{ | ||
var newSource = Visit(methodCallExpression.Arguments[0]); | ||
var fromSqlOnQueryableMethod = RelationalQueryableExtensions.FromSqlOnQueryableMethodInfo.MakeGenericMethod(newSource.Type.GetGenericArguments()[0]); | ||
|
||
switch (methodCallExpression.Method.Name) | ||
{ | ||
case nameof(RelationalQueryableExtensions.FromSqlRaw): | ||
return Expression.Call( | ||
null, | ||
fromSqlOnQueryableMethod, | ||
newSource, | ||
methodCallExpression.Arguments[1], | ||
methodCallExpression.Arguments[2]); | ||
|
||
case nameof(RelationalQueryableExtensions.FromSqlInterpolated): | ||
case nameof(RelationalQueryableExtensions.FromSql) when methodCallExpression.Arguments.Count == 2: | ||
var formattableString = Expression.Lambda<Func<FormattableString>>(Expression.Convert(methodCallExpression.Arguments[1], typeof(FormattableString))).Compile().Invoke(); | ||
|
||
return Expression.Call( | ||
null, | ||
fromSqlOnQueryableMethod, | ||
newSource, | ||
Expression.Constant(formattableString.Format), | ||
Expression.Constant(formattableString.GetArguments())); | ||
|
||
case nameof(RelationalQueryableExtensions.FromSql) when methodCallExpression.Arguments.Count == 3: | ||
#pragma warning disable CS0618 // Type or member is obsolete | ||
var rawSqlStringString = Expression.Lambda<Func<RawSqlString>>(Expression.Convert(methodCallExpression.Arguments[1], typeof(RawSqlString))).Compile().Invoke(); | ||
#pragma warning restore CS0618 // Type or member is obsolete | ||
|
||
return Expression.Call( | ||
null, | ||
fromSqlOnQueryableMethod, | ||
newSource, | ||
Expression.Constant(rawSqlStringString.Format), | ||
methodCallExpression.Arguments[2]); | ||
} | ||
} | ||
|
||
return base.VisitMethodCall(methodCallExpression); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
src/EFCore/Metadata/Conventions/QueryFilterDefiningQueryRewritingConvention.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Linq.Expressions; | ||
using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; | ||
using Microsoft.EntityFrameworkCore.Query.Internal; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions | ||
{ | ||
/// <summary> | ||
/// Convention that converts accesses of DbSets inside query filters and defining queries into EntityQueryables. | ||
/// This makes them consistent with how DbSet accesses in the actual queries are represented, which allows for easier processing in the query pipeline. | ||
/// </summary> | ||
public class QueryFilterDefiningQueryRewritingConvention : IModelFinalizedConvention | ||
{ | ||
/// <summary> | ||
/// Creates a new instance of <see cref="QueryFilterDefiningQueryRewritingConvention" />. | ||
/// </summary> | ||
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param> | ||
public QueryFilterDefiningQueryRewritingConvention([NotNull] ProviderConventionSetBuilderDependencies dependencies) | ||
{ | ||
Dependencies = dependencies; | ||
DbSetAccessRewriter = new DbSetAccessRewritingExpressionVisitor(dependencies.ContextType); | ||
} | ||
|
||
/// <summary> | ||
/// Parameter object containing service dependencies. | ||
/// </summary> | ||
protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } | ||
|
||
/// <summary> | ||
/// Visitor used to rewrite DbSets accesses encountered in query filters and defining queries to EntityQueryables. | ||
/// </summary> | ||
protected virtual DbSetAccessRewritingExpressionVisitor DbSetAccessRewriter { get; [param: NotNull] set; } | ||
|
||
/// <summary> | ||
/// Called after a model is finalized. | ||
/// </summary> | ||
/// <param name="modelBuilder"> The builder for the model. </param> | ||
/// <param name="context"> Additional information associated with convention execution. </param> | ||
public virtual void ProcessModelFinalized( | ||
IConventionModelBuilder modelBuilder, | ||
IConventionContext<IConventionModelBuilder> context) | ||
{ | ||
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) | ||
{ | ||
var queryFilter = entityType.GetQueryFilter(); | ||
if (queryFilter != null) | ||
{ | ||
entityType.SetQueryFilter((LambdaExpression)DbSetAccessRewriter.Visit(queryFilter)); | ||
} | ||
|
||
var definingQuery = entityType.GetDefiningQuery(); | ||
if (definingQuery != null) | ||
{ | ||
entityType.SetDefiningQuery((LambdaExpression)DbSetAccessRewriter.Visit(definingQuery)); | ||
} | ||
} | ||
} | ||
|
||
protected class DbSetAccessRewritingExpressionVisitor : ExpressionVisitor | ||
{ | ||
private readonly Type _contextType; | ||
|
||
public DbSetAccessRewritingExpressionVisitor(Type contextType) | ||
{ | ||
_contextType = contextType; | ||
} | ||
|
||
protected override Expression VisitMember(MemberExpression memberExpression) | ||
{ | ||
if (memberExpression.Expression != null | ||
&& (memberExpression.Expression.Type.IsAssignableFrom(_contextType) | ||
|| _contextType.IsAssignableFrom(memberExpression.Expression.Type)) | ||
&& memberExpression.Type.IsGenericType | ||
&& (memberExpression.Type.GetGenericTypeDefinition() == typeof(DbSet<>) | ||
#pragma warning disable CS0618 // Type or member is obsolete | ||
|| memberExpression.Type.GetGenericTypeDefinition() == typeof(DbQuery<>))) | ||
#pragma warning restore CS0618 // Type or member is obsolete | ||
{ | ||
return NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(memberExpression.Type.GetGenericArguments()[0]); | ||
} | ||
|
||
return base.VisitMember(memberExpression); | ||
} | ||
|
||
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) | ||
{ | ||
if (methodCallExpression.Method.Name == nameof(DbContext.Set) | ||
&& methodCallExpression.Object != null | ||
&& typeof(DbContext).IsAssignableFrom(methodCallExpression.Object.Type) | ||
&& methodCallExpression.Type.IsGenericType | ||
&& methodCallExpression.Type.GetGenericTypeDefinition() == typeof(DbSet<>)) | ||
{ | ||
return NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(methodCallExpression.Type.GetGenericArguments()[0]); | ||
} | ||
|
||
return base.VisitMethodCall(methodCallExpression); | ||
} | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.