From dc1e8115b1fccba9e20fb53b9961073b9406767f Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Mon, 16 Dec 2019 15:51:15 -0800 Subject: [PATCH] Allow creation of a DbCommand from an EF IQueryable Provides access to all the real parameter values, including facets. Should be executable. However, pretty sure people will think this isn't a good idea... --- .../RelationalQueryableExtensions.cs | 36 ++++++++++++ .../Properties/RelationalStrings.Designer.cs | 6 ++ .../Properties/RelationalStrings.resx | 57 ++++++++++--------- .../Query/IRelationalQueryingEnumerable.cs | 38 +++++++++++++ .../Query/Internal/QueryingEnumerable.cs | 17 ++++-- src/EFCore/Query/IQueryingEnumerable.cs | 10 +++- 6 files changed, 130 insertions(+), 34 deletions(-) create mode 100644 src/EFCore.Relational/Query/IRelationalQueryingEnumerable.cs diff --git a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs index 8da21159a98..92de54d3dc0 100644 --- a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs @@ -2,10 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; +using System.Data.Common; using System.Linq; using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Utilities; @@ -17,6 +20,39 @@ namespace Microsoft.EntityFrameworkCore /// public static class RelationalQueryableExtensions { + /// + /// + /// Creates a set up to execute this query. + /// + /// + /// This is only typically supported by queries generated by Entity Framework Core. + /// + /// + /// Warning: there is no guarantee that executing this command directly will result in the same behavior as if EF Core had + /// executed the command. + /// + /// + /// Note that DbCommand is an object. The caller is responsible for disposing the returned + /// command. + /// + /// + /// This is only typically supported by queries generated by Entity Framework Core. + /// + /// + /// The query source. + /// The query string for debugging. + public static DbCommand CreateDbCommand([NotNull] this IQueryable source) + { + Check.NotNull(source, nameof(source)); + + if (source.Provider.Execute(source.Expression) is IRelationalQueryingEnumerable queryingEnumerable) + { + return queryingEnumerable.CreateDbCommand(); + } + + throw new NotSupportedException(RelationalStrings.NoDbCommand); + } + internal static readonly MethodInfo FromSqlOnQueryableMethodInfo = typeof(RelationalQueryableExtensions) .GetTypeInfo().GetDeclaredMethods(nameof(FromSqlOnQueryable)) diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 37470c3db1b..c99863a30cf 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -32,6 +32,12 @@ public static string ModificationCommandInvalidEntityState([CanBeNull] object en GetString("ModificationCommandInvalidEntityState", nameof(entityState)), entityState); + /// + /// Cannot create a 'DbCommand' for a non-relational query. + /// + public static string NoDbCommand + => GetString("NoDbCommand"); + /// /// Database operation expected to affect {expectedRows} row(s) but actually affected {actualRows} row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index bd339582bd9..eceab20d10a 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -1,17 +1,17 @@  - @@ -120,6 +120,9 @@ Cannot save changes for an entity in state '{entityState}'. + + Cannot create a 'DbCommand' for a non-relational query. + Database operation expected to affect {expectedRows} row(s) but actually affected {actualRows} row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. diff --git a/src/EFCore.Relational/Query/IRelationalQueryingEnumerable.cs b/src/EFCore.Relational/Query/IRelationalQueryingEnumerable.cs new file mode 100644 index 00000000000..175767a9b6a --- /dev/null +++ b/src/EFCore.Relational/Query/IRelationalQueryingEnumerable.cs @@ -0,0 +1,38 @@ +// 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.Collections; +using System.Data.Common; + +namespace Microsoft.EntityFrameworkCore.Query +{ + /// + /// + /// Interface that can be implemented by a database provider's implementation to + /// provide the query string for debugging purposes. + /// + /// + /// This interface is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + public interface IRelationalQueryingEnumerable : IQueryingEnumerable + { + /// + /// + /// Creates a set up to execute this query. + /// + /// + /// Warning: there is no guarantee that executing this command directly will result in the same behavior as if EF Core had + /// executed the command. + /// + /// + /// Note that DbCommand is an object. The caller is responsible for disposing the returned + /// command. + /// + /// + /// The newly created command. + DbCommand CreateDbCommand(); + } +} diff --git a/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs index 43142a9782a..9659176960c 100644 --- a/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs @@ -22,7 +22,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 QueryingEnumerable : IEnumerable, IAsyncEnumerable, IQueryingEnumerable + public class QueryingEnumerable : IEnumerable, IAsyncEnumerable, IRelationalQueryingEnumerable { private readonly RelationalQueryContext _relationalQueryContext; private readonly RelationalCommandCache _relationalCommandCache; @@ -87,9 +87,8 @@ public virtual IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancella /// 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 ToQueryString() - { - using var command = _relationalCommandCache + public virtual DbCommand CreateDbCommand() + => _relationalCommandCache .GetRelationalCommand(_relationalQueryContext.ParameterValues) .CreateCommand( new RelationalCommandParameterObject( @@ -101,6 +100,16 @@ public virtual string ToQueryString() Guid.Empty, (DbCommandMethod)(-1)); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 ToQueryString() + { + using var command = CreateDbCommand(); + if (command.Parameters.Count == 0) { return command.CommandText; diff --git a/src/EFCore/Query/IQueryingEnumerable.cs b/src/EFCore/Query/IQueryingEnumerable.cs index 373c02b122d..f66f9a9163e 100644 --- a/src/EFCore/Query/IQueryingEnumerable.cs +++ b/src/EFCore/Query/IQueryingEnumerable.cs @@ -11,15 +11,19 @@ namespace Microsoft.EntityFrameworkCore.Query /// provide the query string for debugging purposes. /// /// - /// This method is typically used by database providers (and other extensions). It is generally + /// This interface is typically used by database providers (and other extensions). It is generally /// not used in application code. /// /// public interface IQueryingEnumerable { /// - /// A string representation of the query used. This string may not be suitable for direct execution is intended only - /// for use in debugging. + /// + /// A string representation of the query used. + /// + /// + /// Warning: this string may not be suitable for direct execution is intended only for use in debugging. + /// /// /// The query string. string ToQueryString();