diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index d5db7a50471..21dc6295188 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -94,7 +94,7 @@ public static readonly IDictionary RelationalServi { typeof(IRelationalTypeMappingSourcePlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }, // New Query Pipeline - { typeof(IQuerySqlGeneratorFactory2), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IQuerySqlGeneratorFactory2), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IRelationalSqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IMethodCallTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IMemberTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) }, diff --git a/src/EFCore.Relational/Query/Pipeline/AsyncQueryingEnumerable.cs b/src/EFCore.Relational/Query/Pipeline/AsyncQueryingEnumerable.cs index e5c3b80cee2..c5d18b20d33 100644 --- a/src/EFCore.Relational/Query/Pipeline/AsyncQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Pipeline/AsyncQueryingEnumerable.cs @@ -23,10 +23,14 @@ private class AsyncQueryingEnumerable : IAsyncEnumerable private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; public AsyncQueryingEnumerable( RelationalQueryContext relationalQueryContext, IQuerySqlGeneratorFactory2 querySqlGeneratorFactory, + ISqlExpressionFactory sqlExpressionFactory, + IParameterNameGeneratorFactory parameterNameGeneratorFactory, SelectExpression selectExpression, Func> shaper, Type contextType, @@ -34,16 +38,15 @@ public AsyncQueryingEnumerable( { _relationalQueryContext = relationalQueryContext; _querySqlGeneratorFactory = querySqlGeneratorFactory; + _sqlExpressionFactory = sqlExpressionFactory; + _parameterNameGeneratorFactory = parameterNameGeneratorFactory; _selectExpression = selectExpression; _shaper = shaper; _contextType = contextType; _logger = logger; } - public IAsyncEnumerator GetEnumerator() - { - return new AsyncEnumerator(this); - } + public IAsyncEnumerator GetEnumerator() => new AsyncEnumerator(this); private sealed class AsyncEnumerator : IAsyncEnumerator { @@ -55,6 +58,8 @@ private sealed class AsyncEnumerator : IAsyncEnumerator private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; public AsyncEnumerator(AsyncQueryingEnumerable queryingEnumerable) { @@ -64,6 +69,8 @@ public AsyncEnumerator(AsyncQueryingEnumerable queryingEnumerable) _querySqlGeneratorFactory = queryingEnumerable._querySqlGeneratorFactory; _contextType = queryingEnumerable._contextType; _logger = queryingEnumerable._logger; + _sqlExpressionFactory = queryingEnumerable._sqlExpressionFactory; + _parameterNameGeneratorFactory = queryingEnumerable._parameterNameGeneratorFactory; } public T Current { get; private set; } @@ -85,11 +92,12 @@ public async Task MoveNext(CancellationToken cancellationToken) try { - var relationalCommand = _querySqlGeneratorFactory.Create() - .GetCommand( - _selectExpression, - _relationalQueryContext.ParameterValues, - _relationalQueryContext.CommandLogger); + var selectExpression = new ParameterValueBasedSelectExpressionOptimizer( + _sqlExpressionFactory, + _parameterNameGeneratorFactory) + .Optimize(_selectExpression, _relationalQueryContext.ParameterValues); + + var relationalCommand = _querySqlGeneratorFactory.Create().GetCommand(selectExpression); _dataReader = await relationalCommand.ExecuteReaderAsync( diff --git a/src/EFCore.Relational/Query/Pipeline/FromSqlNonComposedAsyncQueryingEnumerable.cs b/src/EFCore.Relational/Query/Pipeline/FromSqlNonComposedAsyncQueryingEnumerable.cs new file mode 100644 index 00000000000..bf9bbfce221 --- /dev/null +++ b/src/EFCore.Relational/Query/Pipeline/FromSqlNonComposedAsyncQueryingEnumerable.cs @@ -0,0 +1,177 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline +{ + public partial class RelationalShapedQueryCompilingExpressionVisitor + { + private class FromSqlNonComposedAsyncQueryingEnumerable : IAsyncEnumerable + { + private readonly RelationalQueryContext _relationalQueryContext; + private readonly SelectExpression _selectExpression; + private readonly Func> _shaper; + private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; + private readonly Type _contextType; + private readonly IDiagnosticsLogger _logger; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; + + public FromSqlNonComposedAsyncQueryingEnumerable( + RelationalQueryContext relationalQueryContext, + IQuerySqlGeneratorFactory2 querySqlGeneratorFactory, + ISqlExpressionFactory sqlExpressionFactory, + IParameterNameGeneratorFactory parameterNameGeneratorFactory, + SelectExpression selectExpression, + Func> shaper, + Type contextType, + IDiagnosticsLogger logger) + { + _relationalQueryContext = relationalQueryContext; + _querySqlGeneratorFactory = querySqlGeneratorFactory; + _sqlExpressionFactory = sqlExpressionFactory; + _parameterNameGeneratorFactory = parameterNameGeneratorFactory; + _selectExpression = selectExpression; + _shaper = shaper; + _contextType = contextType; + _logger = logger; + } + + public IAsyncEnumerator GetEnumerator() => new AsyncEnumerator(this); + + private sealed class AsyncEnumerator : IAsyncEnumerator + { + private RelationalDataReader _dataReader; + private int[] _indexMap; + private readonly RelationalQueryContext _relationalQueryContext; + private readonly SelectExpression _selectExpression; + private readonly Func> _shaper; + private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; + private readonly Type _contextType; + private readonly IDiagnosticsLogger _logger; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; + + public AsyncEnumerator(FromSqlNonComposedAsyncQueryingEnumerable queryingEnumerable) + { + _relationalQueryContext = queryingEnumerable._relationalQueryContext; + _shaper = queryingEnumerable._shaper; + _selectExpression = queryingEnumerable._selectExpression; + _querySqlGeneratorFactory = queryingEnumerable._querySqlGeneratorFactory; + _contextType = queryingEnumerable._contextType; + _logger = queryingEnumerable._logger; + _sqlExpressionFactory = queryingEnumerable._sqlExpressionFactory; + _parameterNameGeneratorFactory = queryingEnumerable._parameterNameGeneratorFactory; + } + + public T Current { get; private set; } + + + public void Dispose() + { + _dataReader?.Dispose(); + _dataReader = null; + _relationalQueryContext.Connection.Close(); + } + + public async Task MoveNext(CancellationToken cancellationToken) + { + try + { + if (_dataReader == null) + { + _relationalQueryContext.Connection.Open(); + + try + { + var projection = _selectExpression.Projection.ToList(); + + var selectExpression = new ParameterValueBasedSelectExpressionOptimizer( + _sqlExpressionFactory, + _parameterNameGeneratorFactory) + .Optimize(_selectExpression, _relationalQueryContext.ParameterValues); + + var relationalCommand = _querySqlGeneratorFactory.Create().GetCommand(selectExpression); + + _dataReader + = await relationalCommand.ExecuteReaderAsync( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalQueryContext.CommandLogger, + cancellationToken); + + var readerColumns = Enumerable.Range(0, _dataReader.DbDataReader.FieldCount) + .Select( + i => new + { + Name = _dataReader.DbDataReader.GetName(i), + Ordinal = i + }).ToList(); + + _indexMap = new int[projection.Count]; + + for (var i = 0; i < projection.Count; i++) + { + if (projection[i].Expression is ColumnExpression columnExpression) + { + var columnName = columnExpression.Name; + + if (columnName != null) + { + var readerColumn + = readerColumns.SingleOrDefault( + c => + string.Equals(columnName, c.Name, StringComparison.OrdinalIgnoreCase)); + + if (readerColumn == null) + { + throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(columnName)); + } + + _indexMap[i] = readerColumn.Ordinal; + } + } + } + } + catch (Exception) + { + // If failure happens creating the data reader, then it won't be available to + // handle closing the connection, so do it explicitly here to preserve ref counting. + _relationalQueryContext.Connection.Close(); + + throw; + } + } + + var hasNext = await _dataReader.ReadAsync(cancellationToken); + + Current + = hasNext + ? await _shaper(_relationalQueryContext, _dataReader.DbDataReader, _indexMap) + : default; + + return hasNext; + } + catch (Exception exception) + { + _logger.QueryIterationFailed(_contextType, exception); + + throw; + } + } + + public void Reset() => throw new NotImplementedException(); + } + } + } +} diff --git a/src/EFCore.Relational/Query/Pipeline/FromSqlNonComposedQueryingEnumerable.cs b/src/EFCore.Relational/Query/Pipeline/FromSqlNonComposedQueryingEnumerable.cs new file mode 100644 index 00000000000..3eaab6c9fce --- /dev/null +++ b/src/EFCore.Relational/Query/Pipeline/FromSqlNonComposedQueryingEnumerable.cs @@ -0,0 +1,177 @@ +// 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.Collections.Generic; +using System.Data.Common; +using System.Linq; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline +{ + public partial class RelationalShapedQueryCompilingExpressionVisitor + { + private class FromSqlNonComposedQueryingEnumerable : IEnumerable + { + private readonly RelationalQueryContext _relationalQueryContext; + private readonly SelectExpression _selectExpression; + private readonly Func _shaper; + private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; + private readonly Type _contextType; + private readonly IDiagnosticsLogger _logger; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; + + public FromSqlNonComposedQueryingEnumerable( + RelationalQueryContext relationalQueryContext, + IQuerySqlGeneratorFactory2 querySqlGeneratorFactory, + ISqlExpressionFactory sqlExpressionFactory, + IParameterNameGeneratorFactory parameterNameGeneratorFactory, + SelectExpression selectExpression, + Func shaper, + Type contextType, + IDiagnosticsLogger logger) + { + _relationalQueryContext = relationalQueryContext; + _querySqlGeneratorFactory = querySqlGeneratorFactory; + _sqlExpressionFactory = sqlExpressionFactory; + _parameterNameGeneratorFactory = parameterNameGeneratorFactory; + _selectExpression = selectExpression; + _shaper = shaper; + _contextType = contextType; + _logger = logger; + } + + public IEnumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private sealed class Enumerator : IEnumerator + { + private RelationalDataReader _dataReader; + private int[] _indexMap; + private readonly RelationalQueryContext _relationalQueryContext; + private readonly SelectExpression _selectExpression; + private readonly Func _shaper; + private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; + private readonly Type _contextType; + private readonly IDiagnosticsLogger _logger; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; + + public Enumerator(FromSqlNonComposedQueryingEnumerable queryingEnumerable) + { + _relationalQueryContext = queryingEnumerable._relationalQueryContext; + _shaper = queryingEnumerable._shaper; + _selectExpression = queryingEnumerable._selectExpression; + _querySqlGeneratorFactory = queryingEnumerable._querySqlGeneratorFactory; + _contextType = queryingEnumerable._contextType; + _logger = queryingEnumerable._logger; + _sqlExpressionFactory = queryingEnumerable._sqlExpressionFactory; + _parameterNameGeneratorFactory = queryingEnumerable._parameterNameGeneratorFactory; + } + + public T Current { get; private set; } + + object IEnumerator.Current => Current; + + public void Dispose() + { + _dataReader?.Dispose(); + _dataReader = null; + _relationalQueryContext.Connection.Close(); + } + + public bool MoveNext() + { + try + { + if (_dataReader == null) + { + _relationalQueryContext.Connection.Open(); + + try + { + var projection = _selectExpression.Projection.ToList(); + + var selectExpression = new ParameterValueBasedSelectExpressionOptimizer( + _sqlExpressionFactory, + _parameterNameGeneratorFactory) + .Optimize(_selectExpression, _relationalQueryContext.ParameterValues); + + var relationalCommand = _querySqlGeneratorFactory.Create().GetCommand(selectExpression); + + _dataReader + = relationalCommand.ExecuteReader( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalQueryContext.CommandLogger); + + var readerColumns = Enumerable.Range(0, _dataReader.DbDataReader.FieldCount) + .Select( + i => new + { + Name = _dataReader.DbDataReader.GetName(i), + Ordinal = i + }).ToList(); + + _indexMap = new int[projection.Count]; + + for (var i = 0; i < projection.Count; i++) + { + if (projection[i].Expression is ColumnExpression columnExpression) + { + var columnName = columnExpression.Name; + + if (columnName != null) + { + var readerColumn + = readerColumns.SingleOrDefault( + c => + string.Equals(columnName, c.Name, StringComparison.OrdinalIgnoreCase)); + + if (readerColumn == null) + { + throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(columnName)); + } + + _indexMap[i] = readerColumn.Ordinal; + } + } + } + } + catch (Exception) + { + // If failure happens creating the data reader, then it won't be available to + // handle closing the connection, so do it explicitly here to preserve ref counting. + _relationalQueryContext.Connection.Close(); + + throw; + } + } + + var hasNext = _dataReader.Read(); + + Current + = hasNext + ? _shaper(_relationalQueryContext, _dataReader.DbDataReader, _indexMap) + : default; + + return hasNext; + } + catch (Exception exception) + { + _logger.QueryIterationFailed(_contextType, exception); + + throw; + } + } + + public void Reset() => throw new NotImplementedException(); + } + } + } +} diff --git a/src/EFCore.Relational/Query/Pipeline/FromSqlParameterApplyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Pipeline/FromSqlParameterApplyingExpressionVisitor.cs new file mode 100644 index 00000000000..bdcf2fe716d --- /dev/null +++ b/src/EFCore.Relational/Query/Pipeline/FromSqlParameterApplyingExpressionVisitor.cs @@ -0,0 +1,95 @@ +// 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.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; + +namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline +{ + public partial class RelationalShapedQueryCompilingExpressionVisitor + { + private class FromSqlParameterApplyingExpressionVisitor : ExpressionVisitor + { + private readonly IDictionary _visitedFromSqlExpressions + = new Dictionary(ReferenceEqualityComparer.Instance); + + private readonly ISqlExpressionFactory _SqlExpressionFactory; + private readonly ParameterNameGenerator _parameterNameGenerator; + private IReadOnlyDictionary _parametersValues; + + public FromSqlParameterApplyingExpressionVisitor( + ISqlExpressionFactory _sqlExpressionFactory, + ParameterNameGenerator parameterNameGenerator, + IReadOnlyDictionary parametersValues) + { + _SqlExpressionFactory = _sqlExpressionFactory; + _parameterNameGenerator = parameterNameGenerator; + _parametersValues = parametersValues; + } + + public override Expression Visit(Expression expression) + { + if (expression is FromSqlExpression fromSql) + { + if (!_visitedFromSqlExpressions.TryGetValue(fromSql, out var updatedFromSql)) + { + switch (fromSql.Arguments) + { + case ParameterExpression parameterExpression: + var parameterValues = (object[])_parametersValues[parameterExpression.Name]; + + var subParameters = new List(parameterValues.Length); + for (var i = 0; i < parameterValues.Length; i++) + { + var parameterName = _parameterNameGenerator.GenerateNext(); + if (parameterValues[i] is DbParameter dbParameter) + { + if (string.IsNullOrEmpty(dbParameter.ParameterName)) + { + dbParameter.ParameterName = parameterName; + } + else + { + parameterName = dbParameter.ParameterName; + } + + subParameters.Add(new RawRelationalParameter(parameterName, dbParameter)); + } + else + { + subParameters.Add( + new TypeMappedRelationalParameter( + parameterName, + parameterName, + _SqlExpressionFactory.GetTypeMappingForValue(parameterValues[i]), + parameterValues[i]?.GetType().IsNullableType())); + } + } + + updatedFromSql = new FromSqlExpression( + fromSql.Sql, + Expression.Constant( + new CompositeRelationalParameter( + parameterExpression.Name, + subParameters)), + fromSql.Alias); + + _visitedFromSqlExpressions[fromSql] = updatedFromSql; + break; + } + } + + return updatedFromSql; + } + + return base.Visit(expression); + } + } + } +} diff --git a/src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs b/src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs index c2d47479730..8eaeee3b3d4 100644 --- a/src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs @@ -15,6 +15,7 @@ public interface ISqlExpressionFactory #region TypeMapping SqlExpression ApplyTypeMapping(SqlExpression sqlExpression, RelationalTypeMapping typeMapping); SqlExpression ApplyDefaultTypeMapping(SqlExpression sqlExpression); + RelationalTypeMapping GetTypeMappingForValue(object value); #endregion #region Binary diff --git a/src/EFCore.Relational/Query/Pipeline/InExpressionValuesExpandingExpressionVisitor.cs b/src/EFCore.Relational/Query/Pipeline/InExpressionValuesExpandingExpressionVisitor.cs new file mode 100644 index 00000000000..c06cadaf33e --- /dev/null +++ b/src/EFCore.Relational/Query/Pipeline/InExpressionValuesExpandingExpressionVisitor.cs @@ -0,0 +1,100 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline +{ + public partial class RelationalShapedQueryCompilingExpressionVisitor + { + private class InExpressionValuesExpandingExpressionVisitor : ExpressionVisitor + { + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private IReadOnlyDictionary _parametersValues; + + public InExpressionValuesExpandingExpressionVisitor( + ISqlExpressionFactory sqlExpressionFactory, IReadOnlyDictionary parametersValues) + { + _sqlExpressionFactory = sqlExpressionFactory; + _parametersValues = parametersValues; + } + + public override Expression Visit(Expression expression) + { + if (expression is InExpression inExpression + && inExpression.Values != null) + { + var inValues = new List(); + var hasNullValue = false; + RelationalTypeMapping typeMapping = null; + + switch (inExpression.Values) + { + case SqlConstantExpression sqlConstant: + { + typeMapping = sqlConstant.TypeMapping; + var values = (IEnumerable)sqlConstant.Value; + foreach (var value in values) + { + if (value == null) + { + hasNullValue = true; + continue; + } + + inValues.Add(value); + } + } + break; + + case SqlParameterExpression sqlParameter: + { + typeMapping = sqlParameter.TypeMapping; + var values = (IEnumerable)_parametersValues[sqlParameter.Name]; + foreach (var value in values) + { + if (value == null) + { + hasNullValue = true; + continue; + } + + inValues.Add(value); + } + } + break; + } + + var updatedInExpression = inValues.Count > 0 + ? _sqlExpressionFactory.In( + (SqlExpression)Visit(inExpression.Item), + _sqlExpressionFactory.Constant(inValues, typeMapping), + inExpression.Negated) + : null; + + var nullCheckExpression = hasNullValue + ? _sqlExpressionFactory.IsNull(inExpression.Item) + : null; + + if (updatedInExpression != null && nullCheckExpression != null) + { + return _sqlExpressionFactory.OrElse(updatedInExpression, nullCheckExpression); + } + + if (updatedInExpression == null && nullCheckExpression == null) + { + return _sqlExpressionFactory.Equal(_sqlExpressionFactory.Constant(true), _sqlExpressionFactory.Constant(false)); + } + + return (SqlExpression)updatedInExpression ?? nullCheckExpression; + } + + return base.Visit(expression); + } + } + } +} diff --git a/src/EFCore.Relational/Query/Pipeline/ParameterValueBasedSelectExpressionOptimizer.cs b/src/EFCore.Relational/Query/Pipeline/ParameterValueBasedSelectExpressionOptimizer.cs new file mode 100644 index 00000000000..d967ff62f78 --- /dev/null +++ b/src/EFCore.Relational/Query/Pipeline/ParameterValueBasedSelectExpressionOptimizer.cs @@ -0,0 +1,40 @@ +// 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.Collections.Generic; +using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline +{ + public partial class RelationalShapedQueryCompilingExpressionVisitor + { + private class ParameterValueBasedSelectExpressionOptimizer + { + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; + + public ParameterValueBasedSelectExpressionOptimizer( + ISqlExpressionFactory sqlExpressionFactory, + IParameterNameGeneratorFactory parameterNameGeneratorFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + _parameterNameGeneratorFactory = parameterNameGeneratorFactory; + } + + public SelectExpression Optimize(SelectExpression selectExpression, IReadOnlyDictionary parametersValues) + { + var query = new InExpressionValuesExpandingExpressionVisitor( + _sqlExpressionFactory, parametersValues).Visit(selectExpression); + + query = new FromSqlParameterApplyingExpressionVisitor( + _sqlExpressionFactory, + _parameterNameGeneratorFactory.Create(), + parametersValues).Visit(query); + + + return (SelectExpression)query; + } + } + } +} diff --git a/src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs index ef8c0a50dfa..0e731d20853 100644 --- a/src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs @@ -4,11 +4,13 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Data.Common; using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline { @@ -17,8 +19,6 @@ public class QuerySqlGenerator : SqlExpressionVisitor private readonly IRelationalCommandBuilderFactory _relationalCommandBuilderFactory; private readonly ISqlGenerationHelper _sqlGenerationHelper; private IRelationalCommandBuilder _relationalCommandBuilder; - private IReadOnlyDictionary _parametersValues; - //private ParameterNameGenerator _parameterNameGenerator; private static readonly Dictionary _operatorMap = new Dictionary { @@ -39,25 +39,26 @@ public class QuerySqlGenerator : SqlExpressionVisitor { ExpressionType.Or, " | " } }; - public QuerySqlGenerator(IRelationalCommandBuilderFactory relationalCommandBuilderFactory, + public QuerySqlGenerator( + IRelationalCommandBuilderFactory relationalCommandBuilderFactory, ISqlGenerationHelper sqlGenerationHelper) { _relationalCommandBuilderFactory = relationalCommandBuilderFactory; _sqlGenerationHelper = sqlGenerationHelper; } - public virtual IRelationalCommand GetCommand( - SelectExpression selectExpression, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger commandLogger) + public virtual IRelationalCommand GetCommand(SelectExpression selectExpression) { _relationalCommandBuilder = _relationalCommandBuilderFactory.Create(); - //_parameterNameGenerator = Dependencies.ParameterNameGeneratorFactory.Create(); - - _parametersValues = parameterValues; - - VisitSelect(selectExpression); + if (selectExpression.IsNonComposedFromSql()) + { + GenerateFromSql((FromSqlExpression)selectExpression.Tables[0]); + } + else + { + VisitSelect(selectExpression); + } return _relationalCommandBuilder.Build(); } @@ -101,16 +102,14 @@ protected override Expression VisitSelect(SelectExpression selectExpression) if (selectExpression.Tables.Any()) { - _relationalCommandBuilder.AppendLine() - .Append("FROM "); + _relationalCommandBuilder.AppendLine().Append("FROM "); GenerateList(selectExpression.Tables, e => Visit(e), sql => sql.AppendLine()); } if (selectExpression.Predicate != null) { - _relationalCommandBuilder.AppendLine() - .Append("WHERE "); + _relationalCommandBuilder.AppendLine().Append("WHERE "); Visit(selectExpression.Predicate); } @@ -135,8 +134,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression) } else if (selectExpression.Offset != null) { - _relationalCommandBuilder.AppendLine() - .Append("ORDER BY (SELECT 1)"); + _relationalCommandBuilder.AppendLine().Append("ORDER BY (SELECT 1)"); } GenerateLimitOffset(selectExpression); @@ -216,14 +214,47 @@ protected override Expression VisitTable(TableExpression tableExpression) return tableExpression; } + private void GenerateFromSql(FromSqlExpression fromSqlExpression) + { + var sql = fromSqlExpression.Sql; + string[] substitutions = null; + + switch (fromSqlExpression.Arguments) + { + case ConstantExpression constantExpression + when constantExpression.Value is CompositeRelationalParameter compositeRelationalParameter: + { + var subParameters = compositeRelationalParameter.RelationalParameters; + substitutions = new string[subParameters.Count]; + for (var i = 0; i < subParameters.Count; i++) + { + substitutions[i] = _sqlGenerationHelper.GenerateParameterNamePlaceholder(subParameters[i].InvariantName); + } + + _relationalCommandBuilder.AddParameter(compositeRelationalParameter); + + break; + } + } + + if (substitutions != null) + { + // ReSharper disable once CoVariantArrayConversion + // InvariantCulture not needed since substitutions are all strings + sql = string.Format(sql, substitutions); + } + + + _relationalCommandBuilder.AppendLines(sql); + } + protected override Expression VisitFromSql(FromSqlExpression fromSqlExpression) { _relationalCommandBuilder.AppendLine("("); using (_relationalCommandBuilder.Indent()) { - _relationalCommandBuilder.AppendLines(fromSqlExpression.Sql); - // TODO: Generate parameters + GenerateFromSql(fromSqlExpression); } _relationalCommandBuilder.Append(") AS ") @@ -470,84 +501,14 @@ protected override Expression VisitIn(InExpression inExpression) { if (inExpression.Values != null) { - var inValues = new List(); - var hasNullValue = false; - - switch (inExpression.Values) - { - case SqlConstantExpression sqlConstant: - { - var values = (IEnumerable)sqlConstant.Value; - foreach (var value in values) - { - if (value == null) - { - hasNullValue = true; - continue; - } - - inValues.Add( - new SqlConstantExpression( - Expression.Constant(value), - sqlConstant.TypeMapping)); - } - } - break; - - case SqlParameterExpression sqlParameter: - { - var values = (IEnumerable)_parametersValues[sqlParameter.Name]; - foreach (var value in values) - { - if (value == null) - { - hasNullValue = true; - continue; - } - - inValues.Add( - new SqlConstantExpression( - Expression.Constant(value), - sqlParameter.TypeMapping)); - } - } - break; - } - - if (inValues.Count > 0) - { - if (hasNullValue) - { - _relationalCommandBuilder.Append("("); - } - - Visit(inExpression.Item); - _relationalCommandBuilder.Append(inExpression.Negated ? " NOT IN " : " IN "); - _relationalCommandBuilder.Append("("); - GenerateList(inValues, e => Visit(e)); - _relationalCommandBuilder.Append(")"); - - if (hasNullValue) - { - _relationalCommandBuilder.Append(_operatorMap[ExpressionType.OrElse]); - Visit(new SqlUnaryExpression( - ExpressionType.Equal, inExpression.Item, inExpression.Type, inExpression.TypeMapping)); - _relationalCommandBuilder.Append(")"); - } - } - else - { - Visit( - hasNullValue - ? (Expression)new SqlUnaryExpression( - ExpressionType.Equal, inExpression.Item, inExpression.Type, inExpression.TypeMapping) - : new SqlBinaryExpression( - ExpressionType.Equal, - new SqlConstantExpression(Expression.Constant(true), inExpression.TypeMapping), - new SqlConstantExpression(Expression.Constant(false), inExpression.TypeMapping), - typeof(bool), - inExpression.TypeMapping)); - } + Visit(inExpression.Item); + _relationalCommandBuilder.Append(inExpression.Negated ? " NOT IN " : " IN "); + _relationalCommandBuilder.Append("("); + var valuesConstant = (SqlConstantExpression)inExpression.Values; + var valuesList = ((IEnumerable)valuesConstant.Value) + .Select(v => new SqlConstantExpression(Expression.Constant(v), valuesConstant.TypeMapping)).ToList(); + GenerateList(valuesList, e => Visit(e)); + _relationalCommandBuilder.Append(")"); } else { diff --git a/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs b/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs index a9537ed8328..b4c7fb67474 100644 --- a/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs @@ -5,6 +5,8 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; @@ -22,9 +24,13 @@ private class QueryingEnumerable : IEnumerable private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; public QueryingEnumerable(RelationalQueryContext relationalQueryContext, IQuerySqlGeneratorFactory2 querySqlGeneratorFactory, + ISqlExpressionFactory sqlExpressionFactory, + IParameterNameGeneratorFactory parameterNameGeneratorFactory, SelectExpression selectExpression, Func shaper, Type contextType, @@ -32,6 +38,8 @@ public QueryingEnumerable(RelationalQueryContext relationalQueryContext, { _relationalQueryContext = relationalQueryContext; _querySqlGeneratorFactory = querySqlGeneratorFactory; + _sqlExpressionFactory = sqlExpressionFactory; + _parameterNameGeneratorFactory = parameterNameGeneratorFactory; _selectExpression = selectExpression; _shaper = shaper; _contextType = contextType; @@ -51,6 +59,8 @@ private sealed class Enumerator : IEnumerator private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; public Enumerator(QueryingEnumerable queryingEnumerable) { @@ -60,6 +70,8 @@ public Enumerator(QueryingEnumerable queryingEnumerable) _querySqlGeneratorFactory = queryingEnumerable._querySqlGeneratorFactory; _contextType = queryingEnumerable._contextType; _logger = queryingEnumerable._logger; + _sqlExpressionFactory = queryingEnumerable._sqlExpressionFactory; + _parameterNameGeneratorFactory = queryingEnumerable._parameterNameGeneratorFactory; } public T Current { get; private set; } @@ -83,11 +95,12 @@ public bool MoveNext() try { - var relationalCommand = _querySqlGeneratorFactory.Create() - .GetCommand( - _selectExpression, - _relationalQueryContext.ParameterValues, - _relationalQueryContext.CommandLogger); + var selectExpression = new ParameterValueBasedSelectExpressionOptimizer( + _sqlExpressionFactory, + _parameterNameGeneratorFactory) + .Optimize(_selectExpression, _relationalQueryContext.ParameterValues); + + var relationalCommand = _querySqlGeneratorFactory.Create().GetCommand(selectExpression); _dataReader = relationalCommand.ExecuteReader( diff --git a/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryCompilingExpressionVisitor.cs index 91d18ea2c12..7a6162f3e0c 100644 --- a/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -2,20 +2,26 @@ // 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.Diagnostics; using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Pipeline; using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline { public partial class RelationalShapedQueryCompilingExpressionVisitor : ShapedQueryCompilingExpressionVisitor { private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; private static ParameterExpression _resultCoordinatorParameter @@ -24,6 +30,8 @@ private static ParameterExpression _resultCoordinatorParameter public RelationalShapedQueryCompilingExpressionVisitor( IEntityMaterializerSource entityMaterializerSource, IQuerySqlGeneratorFactory2 querySqlGeneratorFactory, + ISqlExpressionFactory sqlExpressionFactory, + IParameterNameGeneratorFactory parameterNameGeneratorFactory, Type contextType, IDiagnosticsLogger logger, bool trackQueryResults, @@ -31,6 +39,8 @@ public RelationalShapedQueryCompilingExpressionVisitor( : base(entityMaterializerSource, trackQueryResults, async) { _querySqlGeneratorFactory = querySqlGeneratorFactory; + _sqlExpressionFactory = sqlExpressionFactory; + _parameterNameGeneratorFactory = parameterNameGeneratorFactory; _contextType = contextType; _logger = logger; } @@ -45,6 +55,31 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s shaperBody = new IncludeCompilingExpressionVisitor(TrackQueryResults).Visit(shaperBody); + if (selectExpression.IsNonComposedFromSql()) + { + var indexMapParameter = Expression.Parameter(typeof(int[]), "indexMap"); + shaperBody = new IndexMapInjectingExpressionVisitor(indexMapParameter).Visit(shaperBody); + var remappedShaperLambda = + Expression.Lambda( + shaperBody, + QueryCompilationContext2.QueryContextParameter, + RelationalProjectionBindingRemovingExpressionVisitor.DataReaderParameter, + indexMapParameter); + + return Expression.New( + Async + ? typeof(FromSqlNonComposedAsyncQueryingEnumerable<>).MakeGenericType(remappedShaperLambda.ReturnType.GetGenericArguments().Single()).GetConstructors()[0] + : typeof(FromSqlNonComposedQueryingEnumerable<>).MakeGenericType(remappedShaperLambda.ReturnType).GetConstructors()[0], + Expression.Convert(QueryCompilationContext2.QueryContextParameter, typeof(RelationalQueryContext)), + Expression.Constant(_querySqlGeneratorFactory), + Expression.Constant(_sqlExpressionFactory), + Expression.Constant(_parameterNameGeneratorFactory), + Expression.Constant(selectExpression), + Expression.Constant(remappedShaperLambda.Compile()), + Expression.Constant(_contextType), + Expression.Constant(_logger)); + } + var shaperLambda = Expression.Lambda( shaperBody, QueryCompilationContext2.QueryContextParameter, @@ -57,6 +92,8 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s typeof(AsyncQueryingEnumerable<>).MakeGenericType(shaperLambda.ReturnType.GetGenericArguments().Single()).GetConstructors()[0], Expression.Convert(QueryCompilationContext2.QueryContextParameter, typeof(RelationalQueryContext)), Expression.Constant(_querySqlGeneratorFactory), + Expression.Constant(_sqlExpressionFactory), + Expression.Constant(_parameterNameGeneratorFactory), Expression.Constant(selectExpression), Expression.Constant(shaperLambda.Compile()), Expression.Constant(_contextType), @@ -67,12 +104,41 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s typeof(QueryingEnumerable<>).MakeGenericType(shaperLambda.ReturnType).GetConstructors()[0], Expression.Convert(QueryCompilationContext2.QueryContextParameter, typeof(RelationalQueryContext)), Expression.Constant(_querySqlGeneratorFactory), + Expression.Constant(_sqlExpressionFactory), + Expression.Constant(_parameterNameGeneratorFactory), Expression.Constant(selectExpression), Expression.Constant(shaperLambda.Compile()), Expression.Constant(_contextType), Expression.Constant(_logger)); } + private class IndexMapInjectingExpressionVisitor : ExpressionVisitor + { + private ParameterExpression _indexMapParameter; + + public IndexMapInjectingExpressionVisitor(ParameterExpression indexMapParameter) + { + _indexMapParameter = indexMapParameter; + } + + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Object != null + && typeof(DbDataReader).IsAssignableFrom(methodCallExpression.Object.Type)) + { + var indexArgument = methodCallExpression.Arguments[0]; + return methodCallExpression.Update( + methodCallExpression.Object, + new[] + { + Expression.ArrayIndex(_indexMapParameter, indexArgument), + }); + } + + return base.VisitMethodCall(methodCallExpression); + } + } + private class ResultCoordinator { public bool? HasNext { get; set; } diff --git a/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryExpressionVisitorFactory.cs b/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryExpressionVisitorFactory.cs index ab0b318d08e..e1d0747879b 100644 --- a/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryExpressionVisitorFactory.cs +++ b/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryExpressionVisitorFactory.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Pipeline; +using Microsoft.EntityFrameworkCore.Storage; namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline { @@ -11,12 +11,19 @@ public class RelationalShapedQueryCompilingExpressionVisitorFactory : IShapedQue { private readonly IEntityMaterializerSource _entityMaterializerSource; private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; - public RelationalShapedQueryCompilingExpressionVisitorFactory(IEntityMaterializerSource entityMaterializerSource, - IQuerySqlGeneratorFactory2 querySqlGeneratorFactory) + public RelationalShapedQueryCompilingExpressionVisitorFactory( + IEntityMaterializerSource entityMaterializerSource, + IQuerySqlGeneratorFactory2 querySqlGeneratorFactory, + ISqlExpressionFactory sqlExpressionFactory, + IParameterNameGeneratorFactory parameterNameGeneratorFactory) { _entityMaterializerSource = entityMaterializerSource; _querySqlGeneratorFactory = querySqlGeneratorFactory; + _sqlExpressionFactory = sqlExpressionFactory; + _parameterNameGeneratorFactory = parameterNameGeneratorFactory; } public ShapedQueryCompilingExpressionVisitor Create(QueryCompilationContext2 queryCompilationContext) @@ -24,6 +31,8 @@ public ShapedQueryCompilingExpressionVisitor Create(QueryCompilationContext2 que return new RelationalShapedQueryCompilingExpressionVisitor( _entityMaterializerSource, _querySqlGeneratorFactory, + _sqlExpressionFactory, + _parameterNameGeneratorFactory, queryCompilationContext.ContextType, queryCompilationContext.Logger, queryCompilationContext.TrackQueryResults, diff --git a/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs index c31955caf82..38cb91f3e8c 100644 --- a/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs @@ -200,6 +200,11 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( resultType, resultTypeMapping); } + + public virtual RelationalTypeMapping GetTypeMappingForValue(object value) + { + return _typeMappingSource.GetMappingForValue(value); + } #endregion #region Binary diff --git a/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs index 7bb3fcd24fa..3ea0a1c5a44 100644 --- a/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs @@ -84,6 +84,18 @@ public SelectExpression(IEntityType entityType, string sql, Expression arguments _projectionMapping[new ProjectionMember()] = new EntityProjectionExpression(entityType, fromSqlExpression, false); } + public bool IsNonComposedFromSql() + { + return Limit == null + && Offset == null + && !IsDistinct + && Predicate == null + && Orderings.Count == 0 + && Tables.Count == 1 + && Tables[0] is FromSqlExpression fromSql + && Projection.All(pe => pe.Expression is ColumnExpression column ? ReferenceEquals(column.Table, fromSql) : false); + } + public SqlExpression BindProperty(Expression projectionExpression, IProperty property) { var member = (projectionExpression as ProjectionBindingExpression).ProjectionMember; diff --git a/src/EFCore.Relational/Storage/RelationalCommand.cs b/src/EFCore.Relational/Storage/RelationalCommand.cs index 33a541e2fd8..28adda56d14 100644 --- a/src/EFCore.Relational/Storage/RelationalCommand.cs +++ b/src/EFCore.Relational/Storage/RelationalCommand.cs @@ -419,7 +419,7 @@ protected virtual DbCommand CreateCommand( var command = connection.DbConnection.CreateCommand(); command.CommandText = CommandText; - + if (connection.CurrentTransaction != null) { command.Transaction = connection.CurrentTransaction.GetDbTransaction(); diff --git a/src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs b/src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs index 6db14869ea6..cbe1e165636 100644 --- a/src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs +++ b/src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs @@ -280,7 +280,7 @@ public static IRelationalCommandBuilder AddPropertyParameter( property.FindRelationalMapping(), property)); } - + private sealed class Indenter : IDisposable { private readonly IRelationalCommandBuilder _builder; diff --git a/src/EFCore.Relational/Storage/RelationalSqlGenerationHelper.cs b/src/EFCore.Relational/Storage/RelationalSqlGenerationHelper.cs index 02da8f4596f..98af01b0439 100644 --- a/src/EFCore.Relational/Storage/RelationalSqlGenerationHelper.cs +++ b/src/EFCore.Relational/Storage/RelationalSqlGenerationHelper.cs @@ -51,7 +51,9 @@ public RelationalSqlGenerationHelper([NotNull] RelationalSqlGenerationHelperDepe /// A valid name based on the candidate name. /// public virtual string GenerateParameterName(string name) - => "@" + name; + => name.StartsWith("@") + ? name + : "@" + name; /// /// Writes a valid parameter name for the given candidate name. diff --git a/test/EFCore.Relational.Specification.Tests/Query/AsyncFromSqlQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AsyncFromSqlQueryTestBase.cs index 022eaf160ea..d418f31060b 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AsyncFromSqlQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AsyncFromSqlQueryTestBase.cs @@ -101,7 +101,7 @@ from o in context.Set().FromSqlRaw(NormalizeDelimetersInRawString("SELECT } } - [Fact(Skip = "#15750")] + [Fact(Skip = "Issue#15763")] public virtual async Task FromSqlRaw_queryable_multiple_composed_with_closure_parameters() { var startDate = new DateTime(1997, 1, 1); @@ -125,7 +125,7 @@ from o in context.Set().FromSqlRaw( } } - [Fact(Skip = "#15750")] + [Fact(Skip = "Issue#15763")] public virtual async Task FromSqlRaw_queryable_multiple_composed_with_parameters_and_closure_parameters() { var city = "London"; @@ -185,7 +185,7 @@ public virtual async Task FromSqlRaw_queryable_composed_multiple_line_query() } } - [Fact(Skip = "#15750")] + [Fact] public virtual async Task FromSqlRaw_queryable_with_parameters() { var city = "London"; @@ -203,7 +203,7 @@ public virtual async Task FromSqlRaw_queryable_with_parameters() } } - [Fact(Skip = "#15750")] + [Fact] public virtual async Task FromSqlRaw_queryable_with_parameters_and_closure() { var city = "London"; @@ -241,7 +241,7 @@ public virtual async Task FromSqlRaw_queryable_simple_cache_key_includes_query_s } } - [Fact(Skip = "#15750")] + [Fact] public virtual async Task FromSqlRaw_queryable_with_parameters_cache_key_includes_parameters() { var city = "London"; diff --git a/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs index 3bcaf51fe68..a8c78a48e78 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs @@ -325,7 +325,7 @@ from o in context.Set().FromSqlRaw(NormalizeDelimetersInRawString("SELECT } } - [Fact(Skip = "#15750")] + [Fact(Skip = "Issue#15763")] public virtual void FromSqlRaw_queryable_multiple_composed_with_closure_parameters() { var startDate = new DateTime(1997, 1, 1); @@ -351,7 +351,7 @@ from o in context.Set().FromSqlRaw( } } - [Fact(Skip = "#15750")] + [Fact(Skip = "Issue#15763")] public virtual void FromSqlRaw_queryable_multiple_composed_with_parameters_and_closure_parameters() { var city = "London"; @@ -434,7 +434,7 @@ public virtual void FromSqlRaw_queryable_composed_multiple_line_query() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_queryable_with_parameters() { var city = "London"; @@ -452,7 +452,7 @@ public virtual void FromSqlRaw_queryable_with_parameters() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_queryable_with_parameters_inline() { using (var context = CreateContext()) @@ -468,7 +468,7 @@ public virtual void FromSqlRaw_queryable_with_parameters_inline() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlInterpolated_queryable_with_parameters_interpolated() { var city = "London"; @@ -487,7 +487,7 @@ public virtual void FromSqlInterpolated_queryable_with_parameters_interpolated() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlInterpolated_queryable_with_parameters_inline_interpolated() { using (var context = CreateContext()) @@ -503,7 +503,7 @@ public virtual void FromSqlInterpolated_queryable_with_parameters_inline_interpo } } - [Fact(Skip = "#15750")] + [Fact(Skip = "Issue#15763")] public virtual void FromSqlInterpolated_queryable_multiple_composed_with_parameters_and_closure_parameters_interpolated() { var city = "London"; @@ -546,7 +546,7 @@ from o in context.Set().FromSqlInterpolated( } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_queryable_with_null_parameter() { uint? reportsTo = null; @@ -556,14 +556,14 @@ public virtual void FromSqlRaw_queryable_with_null_parameter() var actual = context.Set().FromSqlRaw( NormalizeDelimetersInRawString( // ReSharper disable once ExpressionIsAlwaysNull - "SELECT * FROM [Employees] WHERE [ReportsTo] = {0} OR (]ReportsTo] IS NULL AND {0} IS NULL)"), reportsTo) + "SELECT * FROM [Employees] WHERE [ReportsTo] = {0} OR ([ReportsTo] IS NULL AND {0} IS NULL)"), reportsTo) .ToArray(); Assert.Equal(1, actual.Length); } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_queryable_with_parameters_and_closure() { var city = "London"; @@ -600,7 +600,7 @@ public virtual void FromSqlRaw_queryable_simple_cache_key_includes_query_string( } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_queryable_with_parameters_cache_key_includes_parameters() { var city = "London"; @@ -718,7 +718,7 @@ public virtual void FromSqlRaw_composed_with_nullable_predicate() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_with_dbParameter() { using (var context = CreateContext()) @@ -733,7 +733,7 @@ public virtual void FromSqlRaw_with_dbParameter() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_with_dbParameter_mixed() { using (var context = CreateContext()) @@ -795,7 +795,7 @@ public virtual void Include_does_not_close_user_opened_connection_for_empty_resu Fixture.TestStore.OpenConnection(); } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_with_db_parameters_called_multiple_times() { using (var context = CreateContext()) @@ -897,7 +897,7 @@ public virtual void Include_closed_connection_opened_by_it_when_buffering() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlInterpolated_with_inlined_db_parameter() { using (var context = CreateContext()) @@ -910,7 +910,7 @@ public virtual void FromSqlInterpolated_with_inlined_db_parameter() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlInterpolated_parameterization_issue_12213() { using (var context = CreateContext()) @@ -940,7 +940,7 @@ public virtual void FromSqlInterpolated_parameterization_issue_12213() } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_does_not_parameterize_interpolated_string() { using (var context = CreateContext()) diff --git a/test/EFCore.Relational.Specification.Tests/Query/FromSqlSprocQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/FromSqlSprocQueryTestBase.cs index 21388a9e8d9..b853cf9f819 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/FromSqlSprocQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/FromSqlSprocQueryTestBase.cs @@ -56,7 +56,7 @@ public virtual void From_sql_queryable_stored_procedure_projection() } } - [Fact] + [Fact(Skip = "Issue#15889")] public virtual void From_sql_queryable_stored_procedure_reprojection() { using (var context = CreateContext()) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncFromSqlSprocQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncFromSqlSprocQuerySqlServerTest.cs index ec63bf07091..9cac65a94c2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncFromSqlSprocQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncFromSqlSprocQuerySqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - internal class AsyncFromSqlSprocQuerySqlServerTest : AsyncFromSqlSprocQueryTestBase> + public class AsyncFromSqlSprocQuerySqlServerTest : AsyncFromSqlSprocQueryTestBase> { public AsyncFromSqlSprocQuerySqlServerTest(NorthwindQuerySqlServerFixture fixture) : base(fixture) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs index 49449549a3b..2278cf3abcc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs @@ -525,7 +525,7 @@ public override void FromSqlInterpolated_with_inlined_db_parameter() SELECT * FROM ""Customers"" WHERE ""CustomerID"" = @somename"); } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_in_subquery_with_dbParameter() { using (var context = CreateContext()) @@ -557,7 +557,7 @@ SELECT [c].[CustomerID] } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_in_subquery_with_positional_dbParameter_without_name() { using (var context = CreateContext()) @@ -592,7 +592,7 @@ SELECT [c].[CustomerID] } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_in_subquery_with_positional_dbParameter_with_name() { using (var context = CreateContext()) @@ -624,7 +624,7 @@ SELECT [c].[CustomerID] } } - [Fact(Skip = "#15750")] + [Fact] public virtual void FromSqlRaw_with_dbParameter_mixed_in_subquery() { using (var context = CreateContext()) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlSprocQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlSprocQuerySqlServerTest.cs index e7c095cf747..1f4aaf89fb9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlSprocQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlSprocQuerySqlServerTest.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - internal class FromSqlSprocQuerySqlServerTest : FromSqlSprocQueryTestBase> + public class FromSqlSprocQuerySqlServerTest : FromSqlSprocQueryTestBase> { public FromSqlSprocQuerySqlServerTest( NorthwindQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) @@ -40,7 +40,7 @@ public override void From_sql_queryable_stored_procedure_with_parameter() base.From_sql_queryable_stored_procedure_with_parameter(); Assert.Equal( - @"@p0='ALFKI' (Size = 4000) + @"p0='ALFKI' (Size = 4000) [dbo].[CustOrderHist] @CustomerID = @p0", Sql, diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerComplianceTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerComplianceTest.cs index b89e8fcd37c..fdae32b8bd2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerComplianceTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerComplianceTest.cs @@ -31,8 +31,6 @@ public class SqlServerComplianceTest : RelationalComplianceTestBase typeof(GearsOfWarFromSqlQueryTestBase<>), typeof(QueryNoClientEvalTestBase<>), typeof(WarningsTestBase<>), - typeof(AsyncFromSqlSprocQueryTestBase<>), - typeof(FromSqlSprocQueryTestBase<>), }; protected override Assembly TargetAssembly { get; } = typeof(SqlServerComplianceTest).Assembly;