Skip to content

Commit

Permalink
Query: Copy order by from subquery on need basis only
Browse files Browse the repository at this point in the history
- We dont need to copy always now since new IncludeCompiler lifts order by at query model level
- Move RowNumberPagingExpressionVisitor to SqlServerQuerySqlGenerator
- Introduce ISqlServerOptions, SqlServerOptions to flow SqlServerDbOptionsExtension to SqlServerQuerySqlGenerator as singleton service to get value of RowNumberPaging
- Remove SqlServerQueryModelVisitor
- Add explicit tests for #6736
  • Loading branch information
smitpatel committed Apr 7, 2017
1 parent 79ad1a1 commit debcb14
Show file tree
Hide file tree
Showing 15 changed files with 429 additions and 305 deletions.
54 changes: 38 additions & 16 deletions src/EFCore.Relational/Query/Expressions/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ public virtual Expression Limit
if (value != null && _limit != null)
{
PushDownSubquery();
LiftOrderBy();
}

_limit = value;
Expand All @@ -204,6 +205,7 @@ public virtual Expression Offset
&& value != null)
{
PushDownSubquery();
LiftOrderBy();
}

_offset = value;
Expand Down Expand Up @@ -401,30 +403,50 @@ public virtual SelectExpression PushDownSubquery()

IsProjectStar = true;

// This code is to preserve the ordering in the result when we add extra ordering like we do for grouping/include
foreach (var ordering in subquery.OrderBy.ToList())
if (subquery.Limit == null
&& subquery.Offset == null)
{
var expression = ordering.Expression;
subquery.ClearOrderBy();
}

return subquery;
}

if (expression is NullableExpression nullableExpression)
/// <summary>
/// Ensure that order by expressions from Project Star table of this select expression
/// are copied on outer level to preserve ordering.
/// </summary>
public virtual void LiftOrderBy()
{
if (_projectStarTable is SelectExpression subquery)
{
if (subquery._orderBy.Count == 0)
{
expression = nullableExpression.Operand;
subquery.LiftOrderBy();
}

var expressionToAdd
= expression.LiftExpressionFromSubquery(subquery)
?? subquery.Projection[subquery.AddToProjection(expression, resetProjectStar: false)].LiftExpressionFromSubquery(subquery);
foreach (var ordering in subquery.OrderBy.ToList())
{
var expression = ordering.Expression;

_orderBy.Add(new Ordering(expressionToAdd, ordering.OrderingDirection));
}
if (expression is NullableExpression nullableExpression)
{
expression = nullableExpression.Operand;
}

if (subquery.Limit == null
&& subquery.Offset == null)
{
subquery.ClearOrderBy();
}
var expressionToAdd
= expression.LiftExpressionFromSubquery(subquery)
?? subquery.Projection[subquery.AddToProjection(expression, resetProjectStar: false)].LiftExpressionFromSubquery(subquery);

return subquery;
_orderBy.Add(new Ordering(expressionToAdd, ordering.OrderingDirection));
}

if (subquery.Limit == null
&& subquery.Offset == null)
{
subquery.ClearOrderBy();
}
}
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,8 @@ var joinExpression
return true;
}

outerSelectExpression.LiftOrderBy();

var outerJoinOrderingExtractor = new OuterJoinOrderingExtractor();
outerJoinOrderingExtractor.Visit(predicate);

Expand Down
21 changes: 20 additions & 1 deletion src/EFCore.Specification.Tests/QueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ orderby _c1
}
}

[ConditionalFact]
[ConditionalFact(Skip = "Bug in projection shaper. See Issue #8095")]
public virtual void Lifting_when_subquery_nested_order_by_anonymous()
{
using (var context = CreateContext())
Expand Down Expand Up @@ -3320,6 +3320,16 @@ join o in os on c.CustomerID equals o.CustomerID into orders
select new { cust = c, ords = orders.Count() });
}

[ConditionalFact]
public virtual void GroupJoin_customers_orders_count_preserves_ordering()
{
AssertQuery<Customer, Order>((cs, os) =>
from c in cs.Where(c => c.CustomerID != "VAFFE").OrderBy(c => c.City).Take(5)
join o in os on c.CustomerID equals o.CustomerID into orders
select new { cust = c, ords = orders.Count() },
assertOrder: true);
}

[ConditionalFact]
public virtual void Default_if_empty_top_level()
{
Expand Down Expand Up @@ -7386,6 +7396,15 @@ public virtual void Anonymous_subquery_orderby()
}).OrderBy(n => n.A));
}

[ConditionalFact]
public virtual void Include_with_orderby_skip_preserves_ordering()
{
AssertQuery<Customer>(
cs => cs.Include(c => c.Orders).Where(c => c.CustomerID != "VAFFE").OrderBy(c => c.City).Skip(40).Take(5),
entryCount: 53,
assertOrder: true);
}

private static IEnumerable<TElement> ClientDefaultIfEmpty<TElement>(IEnumerable<TElement> source)
{
return source?.Count() == 0 ? new[] { default(TElement) } : source;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,16 @@ public static IServiceCollection AddEntityFrameworkSqlServer([NotNull] this ISer
.TryAdd<IMigrationsSqlGenerator, SqlServerMigrationsSqlGenerator>()
.TryAdd<IRelationalDatabaseCreator, SqlServerDatabaseCreator>()
.TryAdd<IHistoryRepository, SqlServerHistoryRepository>()
.TryAdd<IEntityQueryModelVisitorFactory, SqlServerQueryModelVisitorFactory>()
.TryAdd<ICompiledQueryCacheKeyGenerator, SqlServerCompiledQueryCacheKeyGenerator>()
.TryAdd<IExecutionStrategyFactory, SqlServerExecutionStrategyFactory>()
.TryAdd<IQueryCompilationContextFactory, SqlServerQueryCompilationContextFactory>()
.TryAdd<IMemberTranslator, SqlServerCompositeMemberTranslator>()
.TryAdd<IMethodCallTranslator, SqlServerCompositeMethodCallTranslator>()
.TryAdd<IQuerySqlGeneratorFactory, SqlServerQuerySqlGeneratorFactory>()
.TryAdd<ISingletonOptions, ISqlServerOptions>(p => p.GetService<ISqlServerOptions>())
.TryAddProviderSpecificServices(b => b
.TryAddSingleton<ISqlServerValueGeneratorCache, SqlServerValueGeneratorCache>()
.TryAddSingleton<ISqlServerOptions, SqlServerOptions>()
.TryAddScoped<ISqlServerUpdateSqlGenerator, SqlServerUpdateSqlGenerator>()
.TryAddScoped<ISqlServerSequenceValueGeneratorFactory, SqlServerSequenceValueGeneratorFactory>()
.TryAddScoped<ISqlServerConnection, SqlServerConnection>());
Expand Down
18 changes: 18 additions & 0 deletions src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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;

namespace Microsoft.EntityFrameworkCore.Infrastructure.Internal
{
/// <summary>
/// Options set at the <see cref="IServiceProvider" /> singleton level to control SqlServer specific options.
/// </summary>
public interface ISqlServerOptions : ISingletonOptions
{
/// <summary>
/// Reflects the option set by <see cref="SqlServerDbContextOptionsBuilder.UseRowNumberForPaging" />.
/// </summary>
bool RowNumberPagingEnabled { get; }
}
}
50 changes: 50 additions & 0 deletions src/EFCore.SqlServer/Internal/SqlServerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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 Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;

namespace Microsoft.EntityFrameworkCore.Internal
{
/// <summary>
/// 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.
/// </summary>
public class SqlServerOptions : ISqlServerOptions
{
/// <summary>
/// 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.
/// </summary>
public virtual void Initialize(IDbContextOptions options)
{
var sqlServerOptions = options.FindExtension<SqlServerOptionsExtension>() ?? new SqlServerOptionsExtension();

RowNumberPagingEnabled = sqlServerOptions.RowNumberPaging ?? false;
}

/// <summary>
/// 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.
/// </summary>
public virtual void Validate(IDbContextOptions options)
{
var sqlServerOptions = options.FindExtension<SqlServerOptionsExtension>() ?? new SqlServerOptionsExtension();

if (RowNumberPagingEnabled != (sqlServerOptions.RowNumberPaging ?? false))
{
throw new InvalidOperationException(
CoreStrings.SingletonOptionChanged(
nameof(SqlServerDbContextOptionsBuilder.UseRowNumberForPaging),
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
}
}

/// <summary>
/// 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.
/// </summary>
public virtual bool RowNumberPagingEnabled { get; private set; }
}
}
165 changes: 0 additions & 165 deletions src/EFCore.SqlServer/Query/Internal/SqlServerQueryModelVisitor.cs

This file was deleted.

Loading

0 comments on commit debcb14

Please sign in to comment.