Skip to content

Commit

Permalink
Allow creation of a DbCommand from an EF IQueryable
Browse files Browse the repository at this point in the history
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...
  • Loading branch information
ajcvickers committed Dec 18, 2019
1 parent a388f5e commit dc1e811
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 34 deletions.
36 changes: 36 additions & 0 deletions src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -17,6 +20,39 @@ namespace Microsoft.EntityFrameworkCore
/// </summary>
public static class RelationalQueryableExtensions
{
/// <summary>
/// <para>
/// Creates a <see cref="DbCommand"/> set up to execute this query.
/// </para>
/// <para>
/// This is only typically supported by queries generated by Entity Framework Core.
/// </para>
/// <para>
/// Warning: there is no guarantee that executing this command directly will result in the same behavior as if EF Core had
/// executed the command.
/// </para>
/// <para>
/// Note that DbCommand is an <see cref="IDisposable"/> object. The caller is responsible for disposing the returned
/// command.
/// </para>
/// <para>
/// This is only typically supported by queries generated by Entity Framework Core.
/// </para>
/// </summary>
/// <param name="source"> The query source. </param>
/// <returns> The query string for debugging. </returns>
public static DbCommand CreateDbCommand([NotNull] this IQueryable source)
{
Check.NotNull(source, nameof(source));

if (source.Provider.Execute<IEnumerable>(source.Expression) is IRelationalQueryingEnumerable queryingEnumerable)
{
return queryingEnumerable.CreateDbCommand();
}

throw new NotSupportedException(RelationalStrings.NoDbCommand);
}

internal static readonly MethodInfo FromSqlOnQueryableMethodInfo
= typeof(RelationalQueryableExtensions)
.GetTypeInfo().GetDeclaredMethods(nameof(FromSqlOnQueryable))
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 30 additions & 27 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -120,6 +120,9 @@
<data name="ModificationCommandInvalidEntityState" xml:space="preserve">
<value>Cannot save changes for an entity in state '{entityState}'.</value>
</data>
<data name="NoDbCommand" xml:space="preserve">
<value>Cannot create a 'DbCommand' for a non-relational query.</value>
</data>
<data name="UpdateConcurrencyException" xml:space="preserve">
<value>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.</value>
</data>
Expand Down
38 changes: 38 additions & 0 deletions src/EFCore.Relational/Query/IRelationalQueryingEnumerable.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// <para>
/// Interface that can be implemented by a database provider's <see cref="IEnumerable" /> implementation to
/// provide the query string for debugging purposes.
/// </para>
/// <para>
/// This interface is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
public interface IRelationalQueryingEnumerable : IQueryingEnumerable
{
/// <summary>
/// <para>
/// Creates a <see cref="DbCommand"/> set up to execute this query.
/// </para>
/// <para>
/// Warning: there is no guarantee that executing this command directly will result in the same behavior as if EF Core had
/// executed the command.
/// </para>
/// <para>
/// Note that DbCommand is an <see cref="IDisposable"/> object. The caller is responsible for disposing the returned
/// command.
/// </para>
/// </summary>
/// <returns> The newly created command. </returns>
DbCommand CreateDbCommand();
}
}
17 changes: 13 additions & 4 deletions src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public class QueryingEnumerable<T> : IEnumerable<T>, IAsyncEnumerable<T>, IQueryingEnumerable
public class QueryingEnumerable<T> : IEnumerable<T>, IAsyncEnumerable<T>, IRelationalQueryingEnumerable
{
private readonly RelationalQueryContext _relationalQueryContext;
private readonly RelationalCommandCache _relationalCommandCache;
Expand Down Expand Up @@ -87,9 +87,8 @@ public virtual IAsyncEnumerator<T> 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.
/// </summary>
public virtual string ToQueryString()
{
using var command = _relationalCommandCache
public virtual DbCommand CreateDbCommand()
=> _relationalCommandCache
.GetRelationalCommand(_relationalQueryContext.ParameterValues)
.CreateCommand(
new RelationalCommandParameterObject(
Expand All @@ -101,6 +100,16 @@ public virtual string ToQueryString()
Guid.Empty,
(DbCommandMethod)(-1));

/// <summary>
/// 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.
/// </summary>
public virtual string ToQueryString()
{
using var command = CreateDbCommand();

if (command.Parameters.Count == 0)
{
return command.CommandText;
Expand Down
10 changes: 7 additions & 3 deletions src/EFCore/Query/IQueryingEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ namespace Microsoft.EntityFrameworkCore.Query
/// provide the query string for debugging purposes.
/// </para>
/// <para>
/// 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.
/// </para>
/// </summary>
public interface IQueryingEnumerable
{
/// <summary>
/// A string representation of the query used. This string may not be suitable for direct execution is intended only
/// for use in debugging.
/// <para>
/// A string representation of the query used.
/// </para>
/// <para>
/// Warning: this string may not be suitable for direct execution is intended only for use in debugging.
/// </para>
/// </summary>
/// <returns> The query string. </returns>
string ToQueryString();
Expand Down

0 comments on commit dc1e811

Please sign in to comment.