From 78a6b9816239b163efb05a92e8170ec978484499 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Mon, 8 Nov 2021 12:40:58 -0800 Subject: [PATCH] Query: Throw error for unhandled derived query root expression Query root design Core exposes QueryRootExpression which is base class for any query root so we can access EntityType out of it. This is needed for nav expansion and any potential future use case. Since any derived query root would derive from it, core assembly translates it only when type is exact match to QueryRootExpression. Relational layer also processes QueryRootExpression when they are mapped to default SqlQuery, which also uses exact type match now. Apart from QueryRootExpression, no other kind of query root which can appear in different providers should be derivable (else they need to use exact type match too). This rule makes relational ones sealed class. In case any one needs to derive from it, they need to add additional processing anyway. Provider specific derived query roots can be non-sealed. If anyone is deriving from it then they should be using their derived provider which process those nodes too and if the derived provider wasn't used and shipped provider is used then it is an error from user perspective. If derived query root is used on other provider (targeting diff database) then it will fail since even the base shipped query root is unknown. Resolves #26502 --- .../Query/Internal/FromSqlQueryRootExpression.cs | 6 +++--- .../Internal/TableValuedFunctionQueryRootExpression.cs | 6 +++--- ...ionalQueryableMethodTranslatingExpressionVisitor.cs | 5 +++-- .../QueryableMethodTranslatingExpressionVisitor.cs | 10 ++++------ 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs b/src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs index a7551b57cdd..94266382593 100644 --- a/src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs +++ b/src/EFCore.Relational/Query/Internal/FromSqlQueryRootExpression.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public class FromSqlQueryRootExpression : QueryRootExpression + public sealed class FromSqlQueryRootExpression : QueryRootExpression { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -55,7 +55,7 @@ public class FromSqlQueryRootExpression : QueryRootExpression /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual string Sql { get; } + public string Sql { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -63,7 +63,7 @@ public class FromSqlQueryRootExpression : QueryRootExpression /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Expression Argument { get; } + public Expression Argument { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs b/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs index f05dac69e4e..3395d703c89 100644 --- a/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs +++ b/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs @@ -16,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public class TableValuedFunctionQueryRootExpression : QueryRootExpression + public sealed class TableValuedFunctionQueryRootExpression : QueryRootExpression { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -41,7 +41,7 @@ public class TableValuedFunctionQueryRootExpression : QueryRootExpression /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IStoreFunction Function { get; } + public IStoreFunction Function { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -49,7 +49,7 @@ public class TableValuedFunctionQueryRootExpression : QueryRootExpression /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IReadOnlyCollection Arguments { get; } + public IReadOnlyCollection Arguments { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 5e510191eb1..7efb2dde939 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -87,7 +87,7 @@ protected override Expression VisitExtension(Expression extensionExpression) _sqlExpressionFactory.Select( fromSqlQueryRootExpression.EntityType, new FromSqlExpression( - fromSqlQueryRootExpression.EntityType.GetDefaultMappings().Single().Table.Name.Substring(0, 1) + fromSqlQueryRootExpression.EntityType.GetDefaultMappings().Single().Table.Name[..1] .ToLowerInvariant(), fromSqlQueryRootExpression.Sql, fromSqlQueryRootExpression.Argument))); @@ -134,7 +134,8 @@ protected override Expression VisitExtension(Expression extensionExpression) return CreateShapedQueryExpression(entityType, queryExpression); case QueryRootExpression queryRootExpression - when queryRootExpression.EntityType.GetSqlQueryMappings().FirstOrDefault(m => m.IsDefaultSqlQueryMapping)?.SqlQuery is + when queryRootExpression.Type == typeof(QueryRootExpression) + && queryRootExpression.EntityType.GetSqlQueryMappings().FirstOrDefault(m => m.IsDefaultSqlQueryMapping)?.SqlQuery is ISqlQuery sqlQuery: return Visit( new FromSqlQueryRootExpression( diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs index 553d75c3ec9..1f1767abdb6 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -90,12 +90,10 @@ protected override Expression VisitExtension(Expression extensionExpression) { Check.NotNull(extensionExpression, nameof(extensionExpression)); - return extensionExpression switch - { - ShapedQueryExpression _ => extensionExpression, - QueryRootExpression queryRootExpression => CreateShapedQueryExpression(queryRootExpression.EntityType), - _ => base.VisitExtension(extensionExpression), - }; + // This has to be exact type match + return extensionExpression.GetType() == typeof(QueryRootExpression) + ? CreateShapedQueryExpression(((QueryRootExpression)extensionExpression).EntityType) + : base.VisitExtension(extensionExpression); } ///