diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/AsyncQueryMethodProvider.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/AsyncQueryMethodProvider.cs index 1973e49f38a..f49e9b73ec3 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/AsyncQueryMethodProvider.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/AsyncQueryMethodProvider.cs @@ -792,5 +792,87 @@ public IAsyncEnumerable Load(QueryContext queryContext, IInclude public void Dispose() => _includeCollectionIterator?.Dispose(); } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual MethodInfo InjectParametersMethod => _injectParametersMethodInfo; + + private static readonly MethodInfo _injectParametersMethodInfo + = typeof(AsyncQueryMethodProvider) + .GetTypeInfo().GetDeclaredMethod(nameof(_InjectParameters)); + + [UsedImplicitly] + // ReSharper disable once InconsistentNaming + private static IAsyncEnumerable _InjectParameters( + QueryContext queryContext, + IAsyncEnumerable source, + string[] parameterNames, + object[] parameterValues) + => new ParameterInjector(queryContext, source, parameterNames, parameterValues); + + private sealed class ParameterInjector : IAsyncEnumerable + { + private readonly QueryContext _queryContext; + private readonly IAsyncEnumerable _innerEnumerable; + private readonly string[] _parameterNames; + private readonly object[] _parameterValues; + + public ParameterInjector( + QueryContext queryContext, + IAsyncEnumerable innerEnumerable, + string[] parameterNames, + object[] parameterValues) + { + _queryContext = queryContext; + _innerEnumerable = innerEnumerable; + _parameterNames = parameterNames; + _parameterValues = parameterValues; + } + + IAsyncEnumerator IAsyncEnumerable.GetEnumerator() => new InjectParametersEnumerator(this); + + private sealed class InjectParametersEnumerator : IAsyncEnumerator + { + private readonly ParameterInjector _parameterInjector; + private readonly IAsyncEnumerator _innerEnumerator; + private bool _disposed; + + public InjectParametersEnumerator(ParameterInjector parameterInjector) + { + _parameterInjector = parameterInjector; + + for (var i = 0; i < _parameterInjector._parameterNames.Length; i++) + { + _parameterInjector._queryContext.AddParameter( + _parameterInjector._parameterNames[i], + _parameterInjector._parameterValues[i]); + } + + _innerEnumerator = _parameterInjector._innerEnumerable.GetEnumerator(); + } + + public TElement Current => _innerEnumerator.Current; + + public async Task MoveNext(CancellationToken cancellationToken) + => await _innerEnumerator.MoveNext(cancellationToken); + + public void Dispose() + { + if (!_disposed) + { + _innerEnumerator.Dispose(); + + foreach (var parameterName in _parameterInjector._parameterNames) + { + _parameterInjector._queryContext.RemoveParameter(parameterName); + } + + _disposed = true; + } + } + } + } } -} +} \ No newline at end of file diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/ResultTransformingExpressionVisitor.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/ResultTransformingExpressionVisitor.cs index b6fae26e96f..170c805f9e1 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/ResultTransformingExpressionVisitor.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/ResultTransformingExpressionVisitor.cs @@ -6,6 +6,7 @@ using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Extensions.Internal; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Clauses; @@ -57,6 +58,24 @@ protected override Expression VisitMethodCall(MethodCallExpression node) queryArguments)); } + if (node.Method.MethodIsClosedFormOf( + _relationalQueryCompilationContext.QueryMethodProvider.InjectParametersMethod)) + { + var sourceArgument = (MethodCallExpression)Visit(node.Arguments[1]); + if (sourceArgument.Method.MethodIsClosedFormOf( + _relationalQueryCompilationContext.QueryMethodProvider.GetResultMethod)) + { + var getResultArgument = sourceArgument.Arguments[0]; + var newGetResultArgument = Expression.Call( + _relationalQueryCompilationContext.QueryMethodProvider.InjectParametersMethod.MakeGenericMethod(typeof(ValueBuffer)), + node.Arguments[0], getResultArgument, node.Arguments[2], node.Arguments[3]); + + return Expression.Call(sourceArgument.Method, newGetResultArgument); + } + + return sourceArgument; + } + // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (var expression in node.Arguments) { diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs index 99c9edc0fd5..5dc1a915fec 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs @@ -617,7 +617,9 @@ var expression .BindMethodCallExpression(methodCallExpression, CreateAliasedColumnExpressionCore); } - return expression; + return expression == null + ? _queryModelVisitor.BindMethodToOuterQueryParameter(methodCallExpression) + : expression; } /// @@ -685,7 +687,9 @@ var selectExpression } } - return aliasExpression; + return aliasExpression == null + ? _queryModelVisitor.BindMemberToOuterQueryParameter(expression) + : aliasExpression; } private AliasExpression CreateAliasedColumnExpression( diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/IQueryMethodProvider.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/IQueryMethodProvider.cs index a2708aff20d..a7ac42b849c 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/IQueryMethodProvider.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/IQueryMethodProvider.cs @@ -94,6 +94,14 @@ public interface IQueryMethodProvider /// MethodInfo CreateCollectionRelatedEntitiesLoaderMethod { get; } + /// + /// Gets the inject parameters method. + /// + /// + /// The pre execute method. + /// + MethodInfo InjectParametersMethod { get; } + /// /// Gets the type of the group join include. /// diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs index 8dd3055ef98..ba2d86b45af 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs @@ -387,6 +387,19 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp methodCallExpression.Arguments); } + if (methodCallExpression.Method.MethodIsClosedFormOf( + _relationalQueryCompilationContext.QueryMethodProvider.InjectParametersMethod)) + { + var newSource = VisitMethodCall((MethodCallExpression)methodCallExpression.Arguments[1]); + + return Expression.Call( + methodCallExpression.Method, + methodCallExpression.Arguments[0], + newSource, + methodCallExpression.Arguments[2], + methodCallExpression.Arguments[3]); + } + return base.VisitMethodCall(methodCallExpression); } } diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/ShaperCommandContext.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/ShaperCommandContext.cs index 6768f4159b3..e1e0d86884a 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/ShaperCommandContext.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/Internal/ShaperCommandContext.cs @@ -62,6 +62,9 @@ public override bool Equals(object obj) } public override int GetHashCode() => 0; + + public CommandCacheKey Clone() => new CommandCacheKey( + new Dictionary((Dictionary)_parameterValues)); } private readonly IRelationalValueBufferFactoryFactory _valueBufferFactoryFactory; @@ -112,7 +115,7 @@ public virtual IRelationalCommand GetRelationalCommand( if (generator.IsCacheable) { - _commandCache.TryAdd(key, relationalCommand); + _commandCache.TryAdd(key.Clone(), relationalCommand); } return relationalCommand; diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/QueryMethodProvider.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/QueryMethodProvider.cs index b455d6862e7..82436465f6d 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/QueryMethodProvider.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/QueryMethodProvider.cs @@ -2,6 +2,7 @@ // 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.Linq; using System.Reflection; @@ -529,5 +530,92 @@ public IEnumerable Load(QueryContext queryContext, IIncludeKeyCo public void Dispose() => _includeCollectionIterator?.Dispose(); } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual MethodInfo InjectParametersMethod => _injectParametersMethodInfo; + + private static readonly MethodInfo _injectParametersMethodInfo + = typeof(QueryMethodProvider) + .GetTypeInfo().GetDeclaredMethod(nameof(_InjectParameters)); + + [UsedImplicitly] + // ReSharper disable once InconsistentNaming + private static IEnumerable _InjectParameters( + QueryContext queryContext, + IEnumerable source, + string[] parameterNames, + object[] parameterValues) + => new ParameterInjector(queryContext, source, parameterNames, parameterValues); + + private sealed class ParameterInjector : IEnumerable + { + private readonly QueryContext _queryContext; + private readonly IEnumerable _innerEnumerable; + private readonly string[] _parameterNames; + private readonly object[] _parameterValues; + + public ParameterInjector( + QueryContext queryContext, + IEnumerable innerEnumerable, + string[] parameterNames, + object[] parameterValues) + { + _queryContext = queryContext; + _innerEnumerable = innerEnumerable; + _parameterNames = parameterNames; + _parameterValues = parameterValues; + } + + public IEnumerator GetEnumerator() => new InjectParametersEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private sealed class InjectParametersEnumerator : IEnumerator + { + private readonly ParameterInjector _parameterInjector; + private readonly IEnumerator _innerEnumerator; + private bool _disposed; + + public InjectParametersEnumerator(ParameterInjector parameterInjector) + { + _parameterInjector = parameterInjector; + + for (var i = 0; i < _parameterInjector._parameterNames.Length; i++) + { + _parameterInjector._queryContext.AddParameter( + _parameterInjector._parameterNames[i], + _parameterInjector._parameterValues[i]); + } + + _innerEnumerator = _parameterInjector._innerEnumerable.GetEnumerator(); + } + + public TElement Current => _innerEnumerator.Current; + + object IEnumerator.Current => _innerEnumerator.Current; + + public bool MoveNext() => _innerEnumerator.MoveNext(); + + public void Reset() => _innerEnumerator.Reset(); + + public void Dispose() + { + if (!_disposed) + { + _innerEnumerator.Dispose(); + + foreach (var parameterName in _parameterInjector._parameterNames) + { + _parameterInjector._queryContext.RemoveParameter(parameterName); + } + + _disposed = true; + } + } + } + } } } diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/RelationalQueryCompilationContext.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/RelationalQueryCompilationContext.cs index d5f5621dab1..51089608adf 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/RelationalQueryCompilationContext.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/RelationalQueryCompilationContext.cs @@ -52,6 +52,7 @@ public RelationalQueryCompilationContext( Check.NotNull(queryMethodProvider, nameof(queryMethodProvider)); QueryMethodProvider = queryMethodProvider; + ParentQueryReferenceParameters = new List(); } /// @@ -62,6 +63,14 @@ public RelationalQueryCompilationContext( /// public virtual IQueryMethodProvider QueryMethodProvider { get; } + /// + /// Gets the list of parameter names that represent reference to a parent query. + /// + /// + /// The list of parameter names that represent reference to a parent query. + /// + public virtual IList ParentQueryReferenceParameters { get; } + /// /// Creates a query model visitor. /// diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/RelationalQueryModelVisitor.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/RelationalQueryModelVisitor.cs index feb548b3620..e41fe390f3d 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/RelationalQueryModelVisitor.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/RelationalQueryModelVisitor.cs @@ -452,7 +452,8 @@ var fromQuerySourceReferenceExpression { var previousSelectExpression = TryGetQuery(previousQuerySource); - if (previousSelectExpression != null) + if (previousSelectExpression != null + && CanFlattenSelectMany()) { if (!QueryCompilationContext.QuerySourceRequiresMaterialization(previousQuerySource)) { @@ -502,6 +503,57 @@ var fromQuerySourceReferenceExpression } } + private bool CanFlattenSelectMany() + { + var selectManyExpression = Expression as MethodCallExpression; + if (selectManyExpression == null + || !selectManyExpression.Method.MethodIsClosedFormOf(LinqOperatorProvider.SelectMany)) + { + return false; + } + + var outerShapedQuery = selectManyExpression.Arguments[0] as MethodCallExpression; + if (outerShapedQuery == null || outerShapedQuery.Arguments.Count != 3) + { + return false; + } + + var outerShaper = outerShapedQuery.Arguments[2] as ConstantExpression; + if (outerShaper == null || !(outerShaper.Value is Shaper)) + { + return false; + } + + var innerShapedQuery = (selectManyExpression.Arguments[1] as LambdaExpression)?.Body as MethodCallExpression; + if (innerShapedQuery == null) + { + return false; + } + + if (innerShapedQuery.Method.MethodIsClosedFormOf(LinqOperatorProvider.DefaultIfEmpty) + || innerShapedQuery.Method.MethodIsClosedFormOf(LinqOperatorProvider.DefaultIfEmptyArg)) + { + innerShapedQuery = innerShapedQuery.Arguments.Single() as MethodCallExpression; + if (innerShapedQuery == null) + { + return false; + } + } + + if (innerShapedQuery.Arguments.Count != 3) + { + return false; + } + + var innerShaper = innerShapedQuery.Arguments[2] as ConstantExpression; + if (innerShaper == null || !(innerShaper.Value is Shaper)) + { + return false; + } + + return true; + } + /// /// Compile an additional from clause expression. /// @@ -966,10 +1018,16 @@ var sqlTranslatingExpressionVisitor foreach (var ordering in orderByClause.Orderings) { + // we disable this for order by, because you can't have a parameter (that is integer) in the order by + var canBindPropertyToOuterParameter = _canBindPropertyToOuterParameter; + _canBindPropertyToOuterParameter = false; + var sqlOrderingExpression = sqlTranslatingExpressionVisitor .Visit(ordering.Expression); + _canBindPropertyToOuterParameter = canBindPropertyToOuterParameter; + if (sqlOrderingExpression == null) { break; @@ -1181,6 +1239,15 @@ private TResult BindMemberExpression( (property, qs) => BindMemberOrMethod(memberBinder, qs, property, bindSubQueries)); } + public virtual Expression BindMemberToOuterQueryParameter( + [NotNull] MemberExpression memberExpression) + { + return base.BindMemberExpression( + memberExpression, + null, + (property, qs) => BindPropertyToOuterParameter(qs, property, true)); + } + /// /// Bind a method call expression. /// @@ -1247,6 +1314,17 @@ public virtual Expression BindLocalMethodCallExpression( }); } + public virtual Expression BindMethodToOuterQueryParameter( + [NotNull] MethodCallExpression methodCallExpression) + { + Check.NotNull(methodCallExpression, nameof(methodCallExpression)); + + return base.BindMethodCallExpression( + methodCallExpression, + null, + (property, qs) => BindPropertyToOuterParameter(qs, property, false)); + } + private TResult BindMemberOrMethod( Func memberBinder, IQuerySource querySource, @@ -1291,5 +1369,84 @@ private TResult BindMemberOrMethod( } #endregion + + private bool _canBindPropertyToOuterParameter = true; + + private const string OuterQueryParameterNamePrefix = @"_outer_"; + + private ParameterExpression BindPropertyToOuterParameter(IQuerySource querySource, IProperty property, bool isMemberExpression) + { + if (querySource != null && _canBindPropertyToOuterParameter) + { + SelectExpression parentSelectExpression = null; + ParentQueryModelVisitor?.QueriesBySource.TryGetValue(querySource, out parentSelectExpression); + if (parentSelectExpression != null) + { + var parameterName = OuterQueryParameterNamePrefix + property.Name; + var parameterWithSamePrefixCount + = QueryCompilationContext.ParentQueryReferenceParameters.Count(p => p.StartsWith(parameterName, StringComparison.Ordinal)); + + if (parameterWithSamePrefixCount > 0) + { + parameterName += parameterWithSamePrefixCount; + } + + QueryCompilationContext.ParentQueryReferenceParameters.Add(parameterName); + Expression = CreateInjectParametersExpression(Expression, querySource, property, parameterName, isMemberExpression); + + return Expression.Parameter( + property.ClrType, + parameterName); + } + } + + return null; + } + + private Expression CreateInjectParametersExpression( + Expression expression, + IQuerySource querySource, + IProperty property, + string parameterName, + bool isMemberExpression) + { + var querySourceReference = new QuerySourceReferenceExpression(querySource); + var propertyExpression = isMemberExpression + ? Expression.Property(querySourceReference, property.GetPropertyInfo()) + : CreatePropertyExpression(querySourceReference, property); + + if (propertyExpression.Type.GetTypeInfo().IsValueType) + { + propertyExpression = Expression.Convert(propertyExpression, typeof(object)); + } + + var parameterNameExpressions = new List(); + var parameterValueExpressions = new List(); + + var methodCallExpression = expression as MethodCallExpression; + if (methodCallExpression != null + && methodCallExpression.Method.MethodIsClosedFormOf(QueryCompilationContext.QueryMethodProvider.InjectParametersMethod)) + { + var existingParamterNamesExpression = (NewArrayExpression)methodCallExpression.Arguments[2]; + parameterNameExpressions.AddRange(existingParamterNamesExpression.Expressions.Cast()); + + var existingParameterValuesExpression = (NewArrayExpression)methodCallExpression.Arguments[3]; + parameterValueExpressions.AddRange(existingParameterValuesExpression.Expressions); + + expression = methodCallExpression.Arguments[1]; + } + + parameterNameExpressions.Add(Expression.Constant(parameterName)); + parameterValueExpressions.Add(propertyExpression); + + var elementType = expression.Type.GetGenericArguments().Single(); + + return Expression.Call( + QueryCompilationContext.QueryMethodProvider.InjectParametersMethod.MakeGenericMethod(elementType), + QueryContextParameter, + expression, + Expression.NewArrayInit(typeof(string), parameterNameExpressions), + Expression.NewArrayInit(typeof(object), parameterValueExpressions)); + } } } diff --git a/src/Microsoft.EntityFrameworkCore.Specification.Tests/AsyncQueryTestBase.cs b/src/Microsoft.EntityFrameworkCore.Specification.Tests/AsyncQueryTestBase.cs index 7bbb7a60c34..cc61bc870d3 100644 --- a/src/Microsoft.EntityFrameworkCore.Specification.Tests/AsyncQueryTestBase.cs +++ b/src/Microsoft.EntityFrameworkCore.Specification.Tests/AsyncQueryTestBase.cs @@ -1426,13 +1426,35 @@ var efObjects }); } + [ConditionalFact] + public virtual async Task Select_correlated_subquery_filtered() + { + await AssertQuery((cs, os) => + from c in cs + select os.Where(o => o.CustomerID == c.CustomerID), + asserter: + (l2oResults, efResults) => + { + var l2oObjects + = l2oResults + .SelectMany(q1 => (IEnumerable)q1) + .OrderBy(o => o.OrderID); + + var efObjects + = efResults + .SelectMany(q1 => (IEnumerable)q1) + .OrderBy(o => o.OrderID); + + Assert.Equal(l2oObjects, efObjects); + }); + } + [ConditionalFact] public virtual async Task Select_correlated_subquery_ordered() { await AssertQuery((cs, os) => from c in cs - select os - .OrderBy(o => c.CustomerID), + select os.OrderBy(o => c.CustomerID), asserter: (l2oResults, efResults) => { diff --git a/src/Microsoft.EntityFrameworkCore.Specification.Tests/GearsOfWarQueryTestBase.cs b/src/Microsoft.EntityFrameworkCore.Specification.Tests/GearsOfWarQueryTestBase.cs index e7754f24629..c8eb2c4d028 100644 --- a/src/Microsoft.EntityFrameworkCore.Specification.Tests/GearsOfWarQueryTestBase.cs +++ b/src/Microsoft.EntityFrameworkCore.Specification.Tests/GearsOfWarQueryTestBase.cs @@ -1616,6 +1616,63 @@ public virtual void Optional_navigation_type_compensation_throws_rasonable_excep } } + [ConditionalFact] + public virtual void Select_correlated_filtered_collection() + { + using (var context = CreateContext()) + { + var query = context.Gears + .Where(g => g.CityOfBirth.Name == "Ephyra" || g.CityOfBirth.Name == "Hanover") + .Select(g => g.Weapons.Where(w => w.Name != "Lancer")); + var result = query.ToList(); + + Assert.Equal(2, result.Count); + + var resultList = result.Select(r => r.ToList()).ToList(); + var coleWeapons = resultList.Where(l => l.All(w => w.Name.Contains("Cole's"))).Single(); + var domWeapons = resultList.Where(l => l.All(w => w.Name.Contains("Dom's"))).Single(); + + Assert.Equal(2, coleWeapons.Count); + Assert.True(coleWeapons.Select(w => w.Name).Contains("Cole's Gnasher")); + + Assert.Equal(2, domWeapons.Count); + Assert.True(domWeapons.Select(w => w.Name).Contains("Dom's Hammerburst")); + Assert.True(domWeapons.Select(w => w.Name).Contains("Dom's Gnasher")); + } + } + + [ConditionalFact] + public virtual void Select_correlated_filtered_collection_with_composite_key() + { + using (var context = CreateContext()) + { + var query = context.Gears.OfType().Select(g => g.Reports.Where(r => r.Nickname != "Dom")); + var result = query.ToList(); + + Assert.Equal(2, result.Count); + + var resultList = result.Select(r => r.ToList()).ToList(); + var bairdReports = resultList.Where(l => l.Count == 1).Single(); + var marcusReports = resultList.Where(l => l.Count == 2).Single(); + + Assert.True(bairdReports.Select(g => g.FullName).Contains("Garron Paduk")); + Assert.True(marcusReports.Select(g => g.FullName).Contains("Augustus Cole")); + Assert.True(marcusReports.Select(g => g.FullName).Contains("Damon Baird")); + } + } + + [ConditionalFact] + public virtual void Select_correlated_filtered_collection_works_with_caching() + { + using (var context = CreateContext()) + { + var query = context.Tags.Select(t => context.Gears.Where(g => g.Nickname == t.GearNickName)); + var result = query.ToList(); + + var resultList = result.Select(r => r.ToList()).ToList(); + } + } + protected GearsOfWarContext CreateContext() => Fixture.CreateContext(TestStore); protected GearsOfWarQueryTestBase(TFixture fixture) diff --git a/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryNavigationsTestBase.cs b/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryNavigationsTestBase.cs index 0eb10ff3aca..d68371f9aec 100644 --- a/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryNavigationsTestBase.cs +++ b/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryNavigationsTestBase.cs @@ -112,7 +112,7 @@ from dynamic l2oItem in l2oItems public virtual void Take_Select_Navigation() { AssertQuery( - cs => cs.Take(2) + cs => cs.OrderBy(c => c.CustomerID).Take(2) .Select(c => c.Orders.FirstOrDefault())); } @@ -121,28 +121,28 @@ public virtual void Take_Select_Navigation() public virtual void Select_collection_FirstOrDefault_project_single_column1() { AssertQuery( - cs => cs.Take(2).Select(c => c.Orders.FirstOrDefault().CustomerID)); + cs => cs.OrderBy(c => c.CustomerID).Take(2).Select(c => c.Orders.FirstOrDefault().CustomerID)); } [ConditionalFact] public virtual void Select_collection_FirstOrDefault_project_single_column2() { AssertQuery( - cs => cs.Take(2).Select(c => c.Orders.Select(o => o.CustomerID).FirstOrDefault())); + cs => cs.OrderBy(c => c.CustomerID).Take(2).Select(c => c.Orders.Select(o => o.CustomerID).FirstOrDefault())); } [ConditionalFact] public virtual void Select_collection_FirstOrDefault_project_anonymous_type() { AssertQuery( - cs => cs.Take(2).Select(c => c.Orders.Select(o => new { o.CustomerID, o.OrderID }).FirstOrDefault())); + cs => cs.OrderBy(c => c.CustomerID).Take(2).Select(c => c.Orders.Select(o => new { o.CustomerID, o.OrderID }).FirstOrDefault())); } [ConditionalFact] public virtual void Select_collection_FirstOrDefault_project_entity() { AssertQuery( - cs => cs.Take(2).Select(c => c.Orders.FirstOrDefault())); + cs => cs.OrderBy(c => c.CustomerID).Take(2).Select(c => c.Orders.FirstOrDefault())); } [ConditionalFact] @@ -322,6 +322,7 @@ public virtual void Select_collection_navigation_simple() AssertQuery( cs => from c in cs where c.CustomerID.StartsWith("A") + orderby c.CustomerID select new { c.CustomerID, c.Orders }, asserter: (l2oItems, efItems) => { @@ -415,8 +416,10 @@ public virtual void Collection_select_nav_prop_all_client() { AssertQuery( cs => from c in cs + orderby c.CustomerID select new { All = c.Orders.All(o => o.ShipCity == "London") }, cs => from c in cs + orderby c.CustomerID select new { All = (c.Orders ?? new List()).All(o => o.ShipCity == "London") }); } @@ -440,6 +443,7 @@ public virtual void Collection_where_nav_prop_all_client() { var customers = (from c in context.Set() + orderby c.CustomerID where c.Orders.All(o => o.ShipCity == "London") select c).ToList(); @@ -566,8 +570,10 @@ public virtual void Collection_select_nav_prop_first_or_default() { AssertQuery( cs => from c in cs + orderby c.CustomerID select new { First = c.Orders.FirstOrDefault() }, cs => from c in cs + orderby c.CustomerID select new { First = (c.Orders ?? new List()).FirstOrDefault() }); } @@ -578,8 +584,10 @@ public virtual void Collection_select_nav_prop_first_or_default_then_nav_prop() AssertQuery( cs => from c in cs.Where(e => e.CustomerID.StartsWith("A")) + orderby c.CustomerID select new { c.Orders.Where(e => orderIds.Contains(e.OrderID)).FirstOrDefault().Customer }, cs => from c in cs.Where(e => e.CustomerID.StartsWith("A")) + orderby c.CustomerID select new { Customer = c.Orders != null && c.Orders.Where(e => orderIds.Contains(e.OrderID)).Any() ? c.Orders.Where(e => orderIds.Contains(e.OrderID)).First().Customer : null }); @@ -723,6 +731,7 @@ public virtual void Where_subquery_on_navigation_client_eval() { AssertQuery( (cs, os) => from c in cs + orderby c.CustomerID where c.Orders.Select(o => o.OrderID) .Contains( os.OrderByDescending(o => ClientMethod(o.OrderID)).Select(o => o.OrderID).FirstOrDefault()) diff --git a/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs b/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs index fa933cb38a8..5e2243d5d90 100644 --- a/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs +++ b/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs @@ -928,7 +928,7 @@ public virtual void Where_subquery_correlated() public virtual void Where_subquery_correlated_client_eval() { AssertQuery( - cs => cs.Where(c1 => cs.Any(c2 => c1.CustomerID == c2.CustomerID && c2.IsLondon)), + cs => cs.OrderBy(c1 => c1.CustomerID).Where(c1 => cs.Any(c2 => c1.CustomerID == c2.CustomerID && c2.IsLondon)), entryCount: 6); } @@ -2007,6 +2007,7 @@ public virtual void Select_correlated_subquery_projection() { AssertQuery((cs, os) => from c in cs + orderby c.CustomerID select os .Where(o => o.CustomerID == c.CustomerID), asserter: @@ -2027,26 +2028,49 @@ var efObjects } [ConditionalFact] - public virtual void Select_correlated_subquery_ordered() + public virtual void Select_correlated_subquery_filtered() { AssertQuery((cs, os) => from c in cs select os.Where(o => o.CustomerID == c.CustomerID), asserter: (l2oResults, efResults) => - { - var l2oObjects - = l2oResults - .SelectMany(q1 => ((IEnumerable)q1)) - .OrderBy(o => o.OrderID); + { + var l2oObjects + = l2oResults + .SelectMany(q1 => ((IEnumerable)q1)) + .OrderBy(o => o.OrderID); - var efObjects - = efResults - .SelectMany(q1 => ((IEnumerable)q1)) - .OrderBy(o => o.OrderID); + var efObjects + = efResults + .SelectMany(q1 => ((IEnumerable)q1)) + .OrderBy(o => o.OrderID); - Assert.Equal(l2oObjects, efObjects); - }); + Assert.Equal(l2oObjects, efObjects); + }); + } + + [ConditionalFact] + public virtual void Select_correlated_subquery_ordered() + { + AssertQuery((cs, os) => + from c in cs + select os.OrderBy(o => c.CustomerID), + asserter: + (l2oResults, efResults) => + { + var l2oObjects + = l2oResults + .SelectMany(q1 => ((IEnumerable)q1)) + .OrderBy(o => o.OrderID); + + var efObjects + = efResults + .SelectMany(q1 => ((IEnumerable)q1)) + .OrderBy(o => o.OrderID); + + Assert.Equal(l2oObjects, efObjects); + }); } // TODO: Re-linq parser @@ -3181,6 +3205,7 @@ public virtual void Let_any_subquery_anonymous() { AssertQuery((cs, os) => from c in cs + orderby c.CustomerID let hasOrders = os.Any(o => o.CustomerID == c.CustomerID) select new { c, hasOrders }); } @@ -5379,7 +5404,7 @@ public virtual void Does_not_change_ordering_of_projection_with_complex_projecti { using (var context = CreateContext()) { - var q = from c in context.Customers.Include(e => e.Orders).Where(c => c.ContactTitle == "Owner") + var q = from c in context.Customers.Include(e => e.Orders).Where(c => c.ContactTitle == "Owner").OrderBy(c => c.CustomerID) select new { Id = c.CustomerID, diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/AsyncQuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/AsyncQuerySqlServerTest.cs index 0d7b0c1e2f4..4ce614f23c2 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/AsyncQuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/AsyncQuerySqlServerTest.cs @@ -77,6 +77,11 @@ public async Task Concurrent_async_queries_are_serialized() } } + public override Task Select_nested_collection_deep() + { + return base.Select_nested_collection_deep(); + } + public AsyncQuerySqlServerTest(NorthwindQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs index 079b0cf4b24..ff1e5639a0e 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs @@ -403,9 +403,12 @@ public override void Join_navigation_translated_to_subquery_nested() Sql); Assert.Contains( - @"SELECT [subQuery].[Id], [subQuery].[Level1_Optional_Id], [subQuery].[Level1_Required_Id], [subQuery].[Name], [subQuery].[OneToMany_Optional_InverseId], [subQuery].[OneToMany_Optional_Self_InverseId], [subQuery].[OneToMany_Required_InverseId], [subQuery].[OneToMany_Required_Self_InverseId], [subQuery].[OneToOne_Optional_PK_InverseId], [subQuery].[OneToOne_Optional_SelfId], [subQuery.OneToOne_Optional_FK].[Id], [subQuery.OneToOne_Optional_FK].[Level2_Optional_Id], [subQuery.OneToOne_Optional_FK].[Level2_Required_Id], [subQuery.OneToOne_Optional_FK].[Name], [subQuery.OneToOne_Optional_FK].[OneToMany_Optional_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Optional_Self_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Required_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Required_Self_InverseId], [subQuery.OneToOne_Optional_FK].[OneToOne_Optional_PK_InverseId], [subQuery.OneToOne_Optional_FK].[OneToOne_Optional_SelfId] + @"@_outer_Id: ? + +SELECT [subQuery].[Id], [subQuery].[Level1_Optional_Id], [subQuery].[Level1_Required_Id], [subQuery].[Name], [subQuery].[OneToMany_Optional_InverseId], [subQuery].[OneToMany_Optional_Self_InverseId], [subQuery].[OneToMany_Required_InverseId], [subQuery].[OneToMany_Required_Self_InverseId], [subQuery].[OneToOne_Optional_PK_InverseId], [subQuery].[OneToOne_Optional_SelfId], [subQuery.OneToOne_Optional_FK].[Id], [subQuery.OneToOne_Optional_FK].[Level2_Optional_Id], [subQuery.OneToOne_Optional_FK].[Level2_Required_Id], [subQuery.OneToOne_Optional_FK].[Name], [subQuery.OneToOne_Optional_FK].[OneToMany_Optional_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Optional_Self_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Required_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Required_Self_InverseId], [subQuery.OneToOne_Optional_FK].[OneToOne_Optional_PK_InverseId], [subQuery.OneToOne_Optional_FK].[OneToOne_Optional_SelfId] FROM [Level2] AS [subQuery] LEFT JOIN [Level3] AS [subQuery.OneToOne_Optional_FK] ON [subQuery].[Id] = [subQuery.OneToOne_Optional_FK].[Level2_Optional_Id] +WHERE [subQuery].[Level1_Required_Id] = @_outer_Id ORDER BY [subQuery].[Id]", Sql); } @@ -425,9 +428,12 @@ public override void Join_navigation_translated_to_subquery_deeply_nested_non_ke Sql); Assert.Contains( - @"SELECT [subQuery].[Id], [subQuery].[Level1_Optional_Id], [subQuery].[Level1_Required_Id], [subQuery].[Name], [subQuery].[OneToMany_Optional_InverseId], [subQuery].[OneToMany_Optional_Self_InverseId], [subQuery].[OneToMany_Required_InverseId], [subQuery].[OneToMany_Required_Self_InverseId], [subQuery].[OneToOne_Optional_PK_InverseId], [subQuery].[OneToOne_Optional_SelfId], [subQuery.OneToOne_Optional_FK].[Id], [subQuery.OneToOne_Optional_FK].[Level2_Optional_Id], [subQuery.OneToOne_Optional_FK].[Level2_Required_Id], [subQuery.OneToOne_Optional_FK].[Name], [subQuery.OneToOne_Optional_FK].[OneToMany_Optional_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Optional_Self_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Required_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Required_Self_InverseId], [subQuery.OneToOne_Optional_FK].[OneToOne_Optional_PK_InverseId], [subQuery.OneToOne_Optional_FK].[OneToOne_Optional_SelfId] + @"@_outer_Id: ? + +SELECT [subQuery].[Id], [subQuery].[Level1_Optional_Id], [subQuery].[Level1_Required_Id], [subQuery].[Name], [subQuery].[OneToMany_Optional_InverseId], [subQuery].[OneToMany_Optional_Self_InverseId], [subQuery].[OneToMany_Required_InverseId], [subQuery].[OneToMany_Required_Self_InverseId], [subQuery].[OneToOne_Optional_PK_InverseId], [subQuery].[OneToOne_Optional_SelfId], [subQuery.OneToOne_Optional_FK].[Id], [subQuery.OneToOne_Optional_FK].[Level2_Optional_Id], [subQuery.OneToOne_Optional_FK].[Level2_Required_Id], [subQuery.OneToOne_Optional_FK].[Name], [subQuery.OneToOne_Optional_FK].[OneToMany_Optional_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Optional_Self_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Required_InverseId], [subQuery.OneToOne_Optional_FK].[OneToMany_Required_Self_InverseId], [subQuery.OneToOne_Optional_FK].[OneToOne_Optional_PK_InverseId], [subQuery.OneToOne_Optional_FK].[OneToOne_Optional_SelfId] FROM [Level2] AS [subQuery] LEFT JOIN [Level3] AS [subQuery.OneToOne_Optional_FK] ON [subQuery].[Id] = [subQuery.OneToOne_Optional_FK].[Level2_Optional_Id] +WHERE [subQuery].[Level1_Required_Id] = @_outer_Id ORDER BY [subQuery].[Id]", Sql); } @@ -1347,29 +1353,15 @@ public override void Correlated_nested_subquery_doesnt_project_unnecessary_colum { base.Correlated_nested_subquery_doesnt_project_unnecessary_columns_in_top_level(); - Assert.StartsWith( - @"SELECT [l1].[Name] + Assert.Equal( + @"SELECT DISTINCT [l1].[Name] FROM [Level1] AS [l1] - -SELECT [l20].[Id] -FROM [Level2] AS [l20] - -SELECT CASE - WHEN EXISTS ( - SELECT 1 - FROM [Level3] AS [l32]) - THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) -END - -SELECT [l20].[Id] -FROM [Level2] AS [l20] - -SELECT CASE - WHEN EXISTS ( +WHERE EXISTS ( + SELECT 1 + FROM [Level2] AS [l2] + WHERE EXISTS ( SELECT 1 - FROM [Level3] AS [l32]) - THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) -END", + FROM [Level3] AS [l3]))", Sql); } diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs index 0645bb85e6e..63387ce7070 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs @@ -1656,6 +1656,81 @@ LEFT JOIN [Gear] AS [t.Gear] ON ([t].[GearNickName] = [t.Gear].[Nickname]) AND ( Sql); } + public override void Select_correlated_filtered_collection() + { + base.Select_correlated_filtered_collection(); + + Assert.Equal( + @"SELECT [g].[FullName] +FROM [Gear] AS [g] +WHERE (([g].[Discriminator] = N'Officer') OR ([g].[Discriminator] = N'Gear')) AND [g].[CityOrBirthName] IN (N'Ephyra', N'Hanover') + +@_outer_FullName: Augustus Cole (Size = 450) + +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapon] AS [w] +WHERE (([w].[Name] <> N'Lancer') OR [w].[Name] IS NULL) AND ((@_outer_FullName = [w].[OwnerFullName]) AND [w].[OwnerFullName] IS NOT NULL) + +@_outer_FullName: Dominic Santiago (Size = 450) + +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapon] AS [w] +WHERE (([w].[Name] <> N'Lancer') OR [w].[Name] IS NULL) AND ((@_outer_FullName = [w].[OwnerFullName]) AND [w].[OwnerFullName] IS NOT NULL)", + Sql); + } + + public override void Select_correlated_filtered_collection_with_composite_key() + { + base.Select_correlated_filtered_collection_with_composite_key(); + + Assert.Equal( + @"SELECT [t].[Nickname], [t].[SquadId] +FROM ( + SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOrBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank] + FROM [Gear] AS [g0] + WHERE [g0].[Discriminator] = N'Officer' +) AS [t] + +@_outer_Nickname: Baird (Size = 4000) +@_outer_SquadId: 1 + +SELECT [r].[Nickname], [r].[SquadId], [r].[AssignedCityName], [r].[CityOrBirthName], [r].[Discriminator], [r].[FullName], [r].[HasSoulPatch], [r].[LeaderNickname], [r].[LeaderSquadId], [r].[Rank] +FROM [Gear] AS [r] +WHERE ([r].[Discriminator] IN (N'Officer', N'Gear') AND ([r].[Nickname] <> N'Dom')) AND ((@_outer_Nickname = [r].[LeaderNickname]) AND (@_outer_SquadId = [r].[LeaderSquadId])) + +@_outer_Nickname: Marcus (Size = 4000) +@_outer_SquadId: 1 + +SELECT [r].[Nickname], [r].[SquadId], [r].[AssignedCityName], [r].[CityOrBirthName], [r].[Discriminator], [r].[FullName], [r].[HasSoulPatch], [r].[LeaderNickname], [r].[LeaderSquadId], [r].[Rank] +FROM [Gear] AS [r] +WHERE ([r].[Discriminator] IN (N'Officer', N'Gear') AND ([r].[Nickname] <> N'Dom')) AND ((@_outer_Nickname = [r].[LeaderNickname]) AND (@_outer_SquadId = [r].[LeaderSquadId]))", + Sql); + } + + public override void Select_correlated_filtered_collection_works_with_caching() + { + base.Select_correlated_filtered_collection_works_with_caching(); + + Assert.Contains( + @"SELECT [t].[GearNickName] +FROM [CogTag] AS [t]", + Sql); + + Assert.Contains( + @"@_outer_GearNickName: Cole Train (Size = 450) + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gear] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[Nickname] = @_outer_GearNickName)", + Sql); + + Assert.Contains( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gear] AS [g] +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND [g].[Nickname] IS NULL", + Sql); + } + public GearsOfWarQuerySqlServerTest(GearsOfWarQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs index 1de81154a48..e7ef2584b21 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs @@ -40,18 +40,27 @@ public override void Take_Select_Navigation() { base.Take_Select_Navigation(); - Assert.StartsWith( + Assert.Equal( @"@__p_0: 2 SELECT [t].[CustomerID] FROM ( SELECT TOP(@__p_0) [c0].* FROM [Customers] AS [c0] + ORDER BY [c0].[CustomerID] ) AS [t] -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +@_outer_CustomerID: ALFKI (Size = 450) + +SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -", +WHERE @_outer_CustomerID = [o].[CustomerID] + +@_outer_CustomerID: ANATR (Size = 450) + +SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID]", Sql); } @@ -70,6 +79,7 @@ FROM [Orders] AS [o] FROM ( SELECT TOP(@__p_0) [c0].* FROM [Customers] AS [c0] + ORDER BY [c0].[CustomerID] ) AS [t]", Sql); } @@ -89,6 +99,7 @@ FROM [Orders] AS [o] FROM ( SELECT TOP(@__p_0) [c0].* FROM [Customers] AS [c0] + ORDER BY [c0].[CustomerID] ) AS [t]", Sql); } @@ -97,20 +108,27 @@ public override void Select_collection_FirstOrDefault_project_anonymous_type() { base.Select_collection_FirstOrDefault_project_anonymous_type(); - Assert.StartsWith( + Assert.Equal( @"@__p_0: 2 SELECT [t].[CustomerID] FROM ( SELECT TOP(@__p_0) [c0].* FROM [Customers] AS [c0] + ORDER BY [c0].[CustomerID] ) AS [t] -SELECT [o].[CustomerID], [o].[OrderID] +@_outer_CustomerID: ALFKI (Size = 450) + +SELECT TOP(1) [o].[CustomerID], [o].[OrderID] FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID] -SELECT [o].[CustomerID], [o].[OrderID] -FROM [Orders] AS [o]", +@_outer_CustomerID: ANATR (Size = 450) + +SELECT TOP(1) [o].[CustomerID], [o].[OrderID] +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID]", Sql); } @@ -118,20 +136,27 @@ public override void Select_collection_FirstOrDefault_project_entity() { base.Select_collection_FirstOrDefault_project_entity(); - Assert.StartsWith( + Assert.Equal( @"@__p_0: 2 SELECT [t].[CustomerID] FROM ( SELECT TOP(@__p_0) [c0].* FROM [Customers] AS [c0] + ORDER BY [c0].[CustomerID] ) AS [t] -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +@_outer_CustomerID: ALFKI (Size = 450) + +SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID] -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o]", +@_outer_CustomerID: ANATR (Size = 450) + +SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID]", Sql); } @@ -366,18 +391,31 @@ public override void Select_collection_navigation_simple() @"SELECT [c].[CustomerID] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A' + N'%' +ORDER BY [c].[CustomerID] + +@_outer_CustomerID: ALFKI (Size = 450) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID] + +@_outer_CustomerID: ANATR (Size = 450) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID] + +@_outer_CustomerID: ANTON (Size = 450) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID] + +@_outer_CustomerID: AROUT (Size = 450) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o]", +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID]", Sql); } @@ -500,9 +538,19 @@ public override void Collection_select_nav_prop_all_client() Assert.StartsWith( @"SELECT [c].[CustomerID] FROM [Customers] AS [c] +ORDER BY [c].[CustomerID] + +@_outer_CustomerID: ALFKI (Size = 450) SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] -FROM [Orders] AS [o1]", +FROM [Orders] AS [o1] +WHERE @_outer_CustomerID = [o1].[CustomerID] + +@_outer_CustomerID: ANATR (Size = 450) + +SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] +FROM [Orders] AS [o1] +WHERE @_outer_CustomerID = [o1].[CustomerID]", Sql); } @@ -527,9 +575,19 @@ public override void Collection_where_nav_prop_all_client() Assert.StartsWith( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] +ORDER BY [c].[CustomerID] + +@_outer_CustomerID: ALFKI (Size = 450) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o]", +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID] + +@_outer_CustomerID: ANATR (Size = 450) + +SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID]", Sql); } @@ -677,13 +735,22 @@ public override void Collection_select_nav_prop_first_or_default() { base.Collection_select_nav_prop_first_or_default(); - // TODO: Projection sub-query lifting Assert.StartsWith( @"SELECT [c].[CustomerID] FROM [Customers] AS [c] +ORDER BY [c].[CustomerID] -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o]", +@_outer_CustomerID: ALFKI (Size = 450) + +SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID] + +@_outer_CustomerID: ANATR (Size = 450) + +SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID]", Sql); } @@ -691,22 +758,26 @@ public override void Collection_select_nav_prop_first_or_default_then_nav_prop() { base.Collection_select_nav_prop_first_or_default_then_nav_prop(); - // TODO: Projection sub-query lifting Assert.StartsWith( @"SELECT [e].[CustomerID] FROM [Customers] AS [e] WHERE [e].[CustomerID] LIKE N'A' + N'%' +ORDER BY [e].[CustomerID] + +@_outer_CustomerID: ALFKI (Size = 450) SELECT [e0].[OrderID], [e0].[CustomerID], [e0].[EmployeeID], [e0].[OrderDate], [e.Customer].[CustomerID], [e.Customer].[Address], [e.Customer].[City], [e.Customer].[CompanyName], [e.Customer].[ContactName], [e.Customer].[ContactTitle], [e.Customer].[Country], [e.Customer].[Fax], [e.Customer].[Phone], [e.Customer].[PostalCode], [e.Customer].[Region] FROM [Orders] AS [e0] LEFT JOIN [Customers] AS [e.Customer] ON [e0].[CustomerID] = [e.Customer].[CustomerID] -WHERE [e0].[OrderID] IN (10643, 10692, 10702, 10835, 10952, 11011) +WHERE [e0].[OrderID] IN (10643, 10692, 10702, 10835, 10952, 11011) AND (@_outer_CustomerID = [e0].[CustomerID]) ORDER BY [e0].[CustomerID] +@_outer_CustomerID: ANATR (Size = 450) + SELECT [e0].[OrderID], [e0].[CustomerID], [e0].[EmployeeID], [e0].[OrderDate], [e.Customer].[CustomerID], [e.Customer].[Address], [e.Customer].[City], [e.Customer].[CompanyName], [e.Customer].[ContactName], [e.Customer].[ContactTitle], [e.Customer].[Country], [e.Customer].[Fax], [e.Customer].[Phone], [e.Customer].[PostalCode], [e.Customer].[Region] FROM [Orders] AS [e0] LEFT JOIN [Customers] AS [e.Customer] ON [e0].[CustomerID] = [e.Customer].[CustomerID] -WHERE [e0].[OrderID] IN (10643, 10692, 10702, 10835, 10952, 11011) +WHERE [e0].[OrderID] IN (10643, 10692, 10702, 10835, 10952, 11011) AND (@_outer_CustomerID = [e0].[CustomerID]) ORDER BY [e0].[CustomerID]", Sql); } @@ -897,17 +968,29 @@ public override void Where_subquery_on_navigation_client_eval() Assert.StartsWith( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] +ORDER BY [c].[CustomerID] + +SELECT [o4].[OrderID] +FROM [Orders] AS [o4] -SELECT [o3].[OrderID] -FROM [Orders] AS [o3] +@_outer_CustomerID: ALFKI (Size = 450) -SELECT [o2].[CustomerID], [o2].[OrderID] +SELECT [o2].[OrderID] FROM [Orders] AS [o2] +WHERE @_outer_CustomerID = [o2].[CustomerID] -SELECT [o3].[OrderID] -FROM [Orders] AS [o3]", - Sql); +SELECT [o4].[OrderID] +FROM [Orders] AS [o4] + +@_outer_CustomerID: ANATR (Size = 450) +SELECT [o2].[OrderID] +FROM [Orders] AS [o2] +WHERE @_outer_CustomerID = [o2].[CustomerID] + +SELECT [o4].[OrderID] +FROM [Orders] AS [o4]", + Sql); } public override void Navigation_in_subquery_referencing_outer_query() diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs index 6a7d832cb68..02932e3e3ad 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs @@ -163,12 +163,31 @@ public override void Where_query_composition_is_null() { base.Where_query_composition_is_null(); - Assert.StartsWith( + Assert.Contains( @"SELECT [e1].[EmployeeID], [e1].[City], [e1].[Country], [e1].[FirstName], [e1].[ReportsTo], [e1].[Title] -FROM [Employees] AS [e1] +FROM [Employees] AS [e1]", + Sql); -SELECT [e2].[EmployeeID], [e2].[City], [e2].[Country], [e2].[FirstName], [e2].[ReportsTo], [e2].[Title] -FROM [Employees] AS [e2]", + Assert.Contains( + @"@_outer_ReportsTo: 2 (Nullable = true) + +SELECT TOP(2) [e2].[EmployeeID], [e2].[City], [e2].[Country], [e2].[FirstName], [e2].[ReportsTo], [e2].[Title] +FROM [Employees] AS [e2] +WHERE [e2].[EmployeeID] = @_outer_ReportsTo", + Sql); + + Assert.Contains( + @"@_outer_ReportsTo: 5 (Nullable = true) + +SELECT TOP(2) [e2].[EmployeeID], [e2].[City], [e2].[Country], [e2].[FirstName], [e2].[ReportsTo], [e2].[Title] +FROM [Employees] AS [e2] +WHERE [e2].[EmployeeID] = @_outer_ReportsTo", + Sql); + + Assert.Contains( + @"SELECT TOP(2) [e2].[EmployeeID], [e2].[City], [e2].[Country], [e2].[FirstName], [e2].[ReportsTo], [e2].[Title] +FROM [Employees] AS [e2] +WHERE [e2].[EmployeeID] IS NULL", Sql); } @@ -176,12 +195,31 @@ public override void Where_query_composition_is_not_null() { base.Where_query_composition_is_null(); - Assert.StartsWith( + Assert.Contains( @"SELECT [e1].[EmployeeID], [e1].[City], [e1].[Country], [e1].[FirstName], [e1].[ReportsTo], [e1].[Title] -FROM [Employees] AS [e1] +FROM [Employees] AS [e1]", + Sql); -SELECT [e2].[EmployeeID], [e2].[City], [e2].[Country], [e2].[FirstName], [e2].[ReportsTo], [e2].[Title] -FROM [Employees] AS [e2]", + Assert.Contains( + @"@_outer_ReportsTo: 2 (Nullable = true) + +SELECT TOP(2) [e2].[EmployeeID], [e2].[City], [e2].[Country], [e2].[FirstName], [e2].[ReportsTo], [e2].[Title] +FROM [Employees] AS [e2] +WHERE [e2].[EmployeeID] = @_outer_ReportsTo", + Sql); + + Assert.Contains( + @"@_outer_ReportsTo: 5 (Nullable = true) + +SELECT TOP(2) [e2].[EmployeeID], [e2].[City], [e2].[Country], [e2].[FirstName], [e2].[ReportsTo], [e2].[Title] +FROM [Employees] AS [e2] +WHERE [e2].[EmployeeID] = @_outer_ReportsTo", + Sql); + + Assert.Contains( + @"SELECT TOP(2) [e2].[EmployeeID], [e2].[City], [e2].[Country], [e2].[FirstName], [e2].[ReportsTo], [e2].[Title] +FROM [Employees] AS [e2] +WHERE [e2].[EmployeeID] IS NULL", Sql); } @@ -189,12 +227,31 @@ public override void Where_query_composition_entity_equality_one_element_SingleO { base.Where_query_composition_entity_equality_one_element_SingleOrDefault(); - Assert.StartsWith( + Assert.Contains( @"SELECT [e1].[EmployeeID], [e1].[City], [e1].[Country], [e1].[FirstName], [e1].[ReportsTo], [e1].[Title] -FROM [Employees] AS [e1] +FROM [Employees] AS [e1]", + Sql); + + Assert.Contains( + @"@_outer_ReportsTo: 2 (Nullable = true) -SELECT [e20].[EmployeeID] -FROM [Employees] AS [e20]", +SELECT TOP(2) [e20].[EmployeeID] +FROM [Employees] AS [e20] +WHERE [e20].[EmployeeID] = @_outer_ReportsTo", + Sql); + + Assert.Contains( + @"@_outer_ReportsTo: 5 (Nullable = true) + +SELECT TOP(2) [e20].[EmployeeID] +FROM [Employees] AS [e20] +WHERE [e20].[EmployeeID] = @_outer_ReportsTo", + Sql); + + Assert.Contains( + @"SELECT TOP(2) [e20].[EmployeeID] +FROM [Employees] AS [e20] +WHERE [e20].[EmployeeID] IS NULL", Sql); } @@ -295,11 +352,17 @@ public override void Select_Subquery_Single() SELECT TOP(@__p_0) [od].[OrderID] FROM [Order Details] AS [od] -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +@_outer_OrderID: 10285 + +SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] +WHERE @_outer_OrderID = [o].[OrderID] -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o]", +@_outer_OrderID: 10294 + +SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE @_outer_OrderID = [o].[OrderID]", Sql); } @@ -311,11 +374,17 @@ public override void Select_Where_Subquery_Deep_Single() @"SELECT [od].[OrderID], [od].[ProductID], [od].[Discount], [od].[Quantity], [od].[UnitPrice] FROM [Order Details] AS [od] -SELECT [o0].[OrderID], [o0].[CustomerID] +@_outer_OrderID: 10248 + +SELECT TOP(2) [o0].[CustomerID] FROM [Orders] AS [o0] +WHERE @_outer_OrderID = [o0].[OrderID] -SELECT [c2].[CustomerID], [c2].[City] -FROM [Customers] AS [c2]", +@_outer_CustomerID1: VINET (Size = 450) + +SELECT TOP(2) [c2].[City] +FROM [Customers] AS [c2] +WHERE @_outer_CustomerID1 = [c2].[CustomerID]", Sql); } @@ -362,9 +431,28 @@ FROM [Order Details] AS [o4] SELECT [c5].[CustomerID], [c5].[Country] FROM [Customers] AS [c5] -SELECT [o23].[OrderID], [c6].[Country] +@_outer_OrderID1: 10285 + +SELECT TOP(1) [c6].[Country] FROM [Orders] AS [o23] -INNER JOIN [Customers] AS [c6] ON [o23].[CustomerID] = [c6].[CustomerID]", +INNER JOIN [Customers] AS [c6] ON [o23].[CustomerID] = [c6].[CustomerID] +WHERE [o23].[OrderID] = @_outer_OrderID1 + +SELECT [c5].[CustomerID], [c5].[Country] +FROM [Customers] AS [c5] + +@_outer_OrderID1: 10294 + +SELECT TOP(1) [c6].[Country] +FROM [Orders] AS [o23] +INNER JOIN [Customers] AS [c6] ON [o23].[CustomerID] = [c6].[CustomerID] +WHERE [o23].[OrderID] = @_outer_OrderID1 + +SELECT [t1].[OrderID] +FROM ( + SELECT TOP(2) [o4].[OrderID], [o4].[ProductID], [o4].[Discount], [o4].[Quantity], [o4].[UnitPrice] + FROM [Order Details] AS [o4] +) AS [t1]", Sql); } @@ -408,9 +496,19 @@ public override void Where_subquery_correlated_client_eval() Assert.StartsWith( @"SELECT [c1].[CustomerID], [c1].[Address], [c1].[City], [c1].[CompanyName], [c1].[ContactName], [c1].[ContactTitle], [c1].[Country], [c1].[Fax], [c1].[Phone], [c1].[PostalCode], [c1].[Region] FROM [Customers] AS [c1] +ORDER BY [c1].[CustomerID] + +@_outer_CustomerID: ALFKI (Size = 450) SELECT [c2].[CustomerID], [c2].[Address], [c2].[City], [c2].[CompanyName], [c2].[ContactName], [c2].[ContactTitle], [c2].[Country], [c2].[Fax], [c2].[Phone], [c2].[PostalCode], [c2].[Region] -FROM [Customers] AS [c2]", +FROM [Customers] AS [c2] +WHERE @_outer_CustomerID = [c2].[CustomerID] + +@_outer_CustomerID: ANATR (Size = 450) + +SELECT [c2].[CustomerID], [c2].[Address], [c2].[City], [c2].[CompanyName], [c2].[ContactName], [c2].[ContactTitle], [c2].[Country], [c2].[Fax], [c2].[Phone], [c2].[PostalCode], [c2].[Region] +FROM [Customers] AS [c2] +WHERE @_outer_CustomerID = [c2].[CustomerID]", Sql); } @@ -444,9 +542,27 @@ THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END ) FROM [Customers] AS [c] +ORDER BY [c].[CustomerID] -SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] -FROM [Orders] AS [o1]", +@_outer_CustomerID: ALFKI (Size = 450) + +SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM [Orders] AS [o1] + WHERE [o1].[CustomerID] = @_outer_CustomerID) + THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) +END + +@_outer_CustomerID: ANATR (Size = 450) + +SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM [Orders] AS [o1] + WHERE [o1].[CustomerID] = @_outer_CustomerID) + THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) +END", Sql); } @@ -3094,20 +3210,20 @@ public override void SelectMany_Joined_DefaultIfEmpty() { base.SelectMany_Joined_DefaultIfEmpty(); - Assert.StartsWith( - @"SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [c].[ContactName] + Assert.Equal( + @"SELECT [t2].[OrderID], [t2].[CustomerID], [t2].[EmployeeID], [t2].[OrderDate], [c].[ContactName] FROM [Customers] AS [c] CROSS APPLY ( - SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] FROM ( SELECT NULL AS [empty] - ) AS [empty0] + ) AS [empty10] LEFT JOIN ( SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] FROM [Orders] AS [o0] WHERE [o0].[CustomerID] = [c].[CustomerID] - ) AS [t] ON 1 = 1 -) AS [t1]", + ) AS [t1] ON 1 = 1 +) AS [t2]", Sql); } @@ -3115,20 +3231,20 @@ public override void SelectMany_Joined_DefaultIfEmpty2() { base.SelectMany_Joined_DefaultIfEmpty2(); - Assert.StartsWith( - @"SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + Assert.Equal( + @"SELECT [t2].[OrderID], [t2].[CustomerID], [t2].[EmployeeID], [t2].[OrderDate] FROM [Customers] AS [c] CROSS APPLY ( - SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] FROM ( SELECT NULL AS [empty] - ) AS [empty0] + ) AS [empty10] LEFT JOIN ( SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] FROM [Orders] AS [o0] WHERE [o0].[CustomerID] = [c].[CustomerID] - ) AS [t] ON 1 = 1 -) AS [t1]", + ) AS [t1] ON 1 = 1 +) AS [t2]", Sql); } @@ -4299,6 +4415,11 @@ ORDER BY [c].[CustomerID] Sql); } + public override void Select_nested_collection_deep() + { + base.Select_nested_collection_deep(); + } + public override void Where_math_power() { base.Where_math_power(); @@ -4653,18 +4774,53 @@ public override void Select_nested_collection() { base.Select_nested_collection(); - Assert.StartsWith( + Assert.Equal( @"SELECT [c].[CustomerID] FROM [Customers] AS [c] WHERE [c].[City] = N'London' ORDER BY [c].[CustomerID] -SELECT [o].[CustomerID], [o].[OrderID] +@_outer_CustomerID: AROUT (Size = 450) + +SELECT [o].[OrderID] FROM [Orders] AS [o] -WHERE DATEPART(year, [o].[OrderDate]) = 1997 +WHERE ([o].[CustomerID] = @_outer_CustomerID) AND (DATEPART(year, [o].[OrderDate]) = 1997) ORDER BY [o].[OrderID] -", +@_outer_CustomerID: BSBEV (Size = 450) + +SELECT [o].[OrderID] +FROM [Orders] AS [o] +WHERE ([o].[CustomerID] = @_outer_CustomerID) AND (DATEPART(year, [o].[OrderDate]) = 1997) +ORDER BY [o].[OrderID] + +@_outer_CustomerID: CONSH (Size = 450) + +SELECT [o].[OrderID] +FROM [Orders] AS [o] +WHERE ([o].[CustomerID] = @_outer_CustomerID) AND (DATEPART(year, [o].[OrderDate]) = 1997) +ORDER BY [o].[OrderID] + +@_outer_CustomerID: EASTC (Size = 450) + +SELECT [o].[OrderID] +FROM [Orders] AS [o] +WHERE ([o].[CustomerID] = @_outer_CustomerID) AND (DATEPART(year, [o].[OrderDate]) = 1997) +ORDER BY [o].[OrderID] + +@_outer_CustomerID: NORTS (Size = 450) + +SELECT [o].[OrderID] +FROM [Orders] AS [o] +WHERE ([o].[CustomerID] = @_outer_CustomerID) AND (DATEPART(year, [o].[OrderDate]) = 1997) +ORDER BY [o].[OrderID] + +@_outer_CustomerID: SEVES (Size = 450) + +SELECT [o].[OrderID] +FROM [Orders] AS [o] +WHERE ([o].[CustomerID] = @_outer_CustomerID) AND (DATEPART(year, [o].[OrderDate]) = 1997) +ORDER BY [o].[OrderID]", Sql); } @@ -4675,11 +4831,19 @@ public override void Select_correlated_subquery_projection() Assert.StartsWith( @"SELECT [c].[CustomerID] FROM [Customers] AS [c] +ORDER BY [c].[CustomerID] + +@_outer_CustomerID: ALFKI (Size = 450) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] +WHERE [o].[CustomerID] = @_outer_CustomerID -", +@_outer_CustomerID: ANATR (Size = 450) + +SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE [o].[CustomerID] = @_outer_CustomerID", Sql); } @@ -5334,9 +5498,19 @@ FROM [Orders] AS [o1] ) FROM [Customers] AS [e] WHERE [e].[ContactTitle] = N'Owner' +ORDER BY [e].[CustomerID] -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o]", +@_outer_CustomerID: ANATR (Size = 450) + +SELECT COUNT(*) +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID] + +@_outer_CustomerID: ANTON (Size = 450) + +SELECT COUNT(*) +FROM [Orders] AS [o] +WHERE @_outer_CustomerID = [o].[CustomerID]", Sql); }