diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
index 21f9e5c07d1..1c0ee7f0ebf 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
@@ -12,9 +13,15 @@
namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal
{
+ ///
+ /// 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 partial class CosmosShapedQueryCompilingExpressionVisitor
{
- private sealed class QueryingEnumerable : IEnumerable, IAsyncEnumerable
+ private sealed class QueryingEnumerable : IEnumerable, IAsyncEnumerable, IQueryingEnumerable
{
private readonly CosmosQueryContext _cosmosQueryContext;
private readonly ISqlExpressionFactory _sqlExpressionFactory;
@@ -48,6 +55,32 @@ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToke
public IEnumerator GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ public string ToQueryString()
+ {
+ var sqlQuery = _querySqlGeneratorFactory.Create().GetSqlQuery(
+ (SelectExpression)new InExpressionValuesExpandingExpressionVisitor(
+ _sqlExpressionFactory, _cosmosQueryContext.ParameterValues).Visit(_selectExpression),
+ _cosmosQueryContext.ParameterValues);
+
+ if (sqlQuery.Parameters.Count == 0)
+ {
+ return sqlQuery.Query;
+ }
+
+ var builder = new StringBuilder();
+ foreach (var parameter in sqlQuery.Parameters)
+ {
+ builder
+ .Append("-- ")
+ .Append(parameter.Name)
+ .Append("='")
+ .Append(parameter.Value)
+ .AppendLine("'");
+ }
+
+ return builder.Append(sqlQuery.Query).ToString();
+ }
+
private sealed class Enumerator : IEnumerator
{
private IEnumerator _enumerator;
diff --git a/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs b/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs
index 5dfae7d1bec..78790302664 100644
--- a/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs
+++ b/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs
@@ -1,4 +1,4 @@
-//
+//
using System;
using System.Reflection;
@@ -21,6 +21,12 @@ public static class InMemoryStrings
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.EntityFrameworkCore.InMemory.Properties.InMemoryStrings", typeof(InMemoryStrings).Assembly);
+ ///
+ /// There is no query string because the in-memory provider does not use a string-based query language.
+ ///
+ public static string NoQueryStrings
+ => GetString("NoQueryStrings");
+
///
/// Attempted to update or delete an entity that does not exist in the store.
///
diff --git a/src/EFCore.InMemory/Properties/InMemoryStrings.resx b/src/EFCore.InMemory/Properties/InMemoryStrings.resx
index 80390db54ed..c194012f7d9 100644
--- a/src/EFCore.InMemory/Properties/InMemoryStrings.resx
+++ b/src/EFCore.InMemory/Properties/InMemoryStrings.resx
@@ -1,17 +1,17 @@
-
@@ -125,6 +125,9 @@
Transactions are not supported by the in-memory store. See http://go.microsoft.com/fwlink/?LinkId=800142Warning InMemoryEventId.TransactionIgnoredWarning
+
+ There is no query string because the in-memory provider does not use a string-based query language.
+
Attempted to update or delete an entity that does not exist in the store.
diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
index 82c7b5297c8..1eb85c97120 100644
--- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
+++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
@@ -7,15 +7,22 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.InMemory.Internal;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal
{
+ ///
+ /// 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 partial class InMemoryShapedQueryCompilingExpressionVisitor
{
- private sealed class QueryingEnumerable : IAsyncEnumerable, IEnumerable
+ private sealed class QueryingEnumerable : IAsyncEnumerable, IEnumerable, IQueryingEnumerable
{
private readonly QueryContext _queryContext;
private readonly IEnumerable _innerEnumerable;
@@ -44,6 +51,8 @@ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToke
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ public string ToQueryString() => InMemoryStrings.NoQueryStrings;
+
private sealed class Enumerator : IEnumerator
{
private IEnumerator _enumerator;
diff --git a/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs
index c8b70945ee0..43142a9782a 100644
--- a/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs
+++ b/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs
@@ -6,11 +6,13 @@
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Internal;
namespace Microsoft.EntityFrameworkCore.Query.Internal
{
@@ -20,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
+ public class QueryingEnumerable : IEnumerable, IAsyncEnumerable, IQueryingEnumerable
{
private readonly RelationalQueryContext _relationalQueryContext;
private readonly RelationalCommandCache _relationalCommandCache;
@@ -30,6 +32,12 @@ public class QueryingEnumerable : IEnumerable, IAsyncEnumerable
private readonly Type _contextType;
private readonly IDiagnosticsLogger _logger;
+ ///
+ /// 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 QueryingEnumerable(
[NotNull] RelationalQueryContext relationalQueryContext,
[NotNull] RelationalCommandCache relationalCommandCache,
@@ -48,12 +56,71 @@ public QueryingEnumerable(
_logger = logger;
}
+ ///
+ /// 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 IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
=> new AsyncEnumerator(this, cancellationToken);
+ ///
+ /// 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 IEnumerator GetEnumerator() => new Enumerator(this);
+
+ ///
+ /// 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.
+ ///
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ ///
+ /// 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 = _relationalCommandCache
+ .GetRelationalCommand(_relationalQueryContext.ParameterValues)
+ .CreateCommand(
+ new RelationalCommandParameterObject(
+ _relationalQueryContext.Connection,
+ _relationalQueryContext.ParameterValues,
+ null,
+ null,
+ null),
+ Guid.Empty,
+ (DbCommandMethod)(-1));
+
+ if (command.Parameters.Count == 0)
+ {
+ return command.CommandText;
+ }
+
+ var builder = new StringBuilder();
+ foreach (var parameter in command.Parameters.FormatParameterList(logParameterValues: true))
+ {
+ builder.Append("-- ").AppendLine(parameter);
+ }
+
+ return builder.Append(command.CommandText).ToString();
+ }
+
+ ///
+ /// 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 static int[] BuildIndexMap([CanBeNull] IReadOnlyList columnNames, [NotNull] DbDataReader dataReader)
{
if (columnNames == null)
diff --git a/src/EFCore.Relational/Storage/IRelationalCommand.cs b/src/EFCore.Relational/Storage/IRelationalCommand.cs
index cc938ffe4de..f222caae70a 100644
--- a/src/EFCore.Relational/Storage/IRelationalCommand.cs
+++ b/src/EFCore.Relational/Storage/IRelationalCommand.cs
@@ -1,9 +1,12 @@
// 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.Generic;
+using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Diagnostics;
namespace Microsoft.EntityFrameworkCore.Storage
{
@@ -84,5 +87,24 @@ Task