Skip to content

Commit b068633

Browse files
committed
- Add possibility of disabling stable ordering.
- Add possibility of disabling default order by clause.
1 parent 11649ae commit b068633

File tree

5 files changed

+67
-16
lines changed

5 files changed

+67
-16
lines changed

AutoMapper.AspNetCore.OData.EF6/QueryableExtensions.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public static ICollection<TModel> Get<TModel, TData>(this IQueryable<TData> quer
2828
Expression<Func<TModel, bool>> filter = options.ToFilterExpression<TModel>
2929
(
3030
querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.Default,
31-
enableConstantParameterization: querySettings?.ODataSettings?.EnableConstantParameterization ?? true
31+
enableConstantParameterization: querySettings?.ODataSettings?.EnableConstantParameterization ?? true,
32+
ensureStableOrdering: querySettings?.ODataSettings?.EnsureStableOrdering ?? true
3233
);
3334
query.ApplyOptions(mapper, filter, options, querySettings);
3435
return query.Get
@@ -56,7 +57,8 @@ public static async Task<ICollection<TModel>> GetAsync<TModel, TData>(this IQuer
5657
Expression<Func<TModel, bool>> filter = options.ToFilterExpression<TModel>
5758
(
5859
querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.Default,
59-
enableConstantParameterization: querySettings?.ODataSettings?.EnableConstantParameterization ?? true
60+
enableConstantParameterization: querySettings?.ODataSettings?.EnableConstantParameterization ?? true,
61+
ensureStableOrdering: querySettings?.ODataSettings?.EnsureStableOrdering ?? true
6062
);
6163
await query.ApplyOptionsAsync(mapper, filter, options, querySettings);
6264
return await query.GetAsync
@@ -85,7 +87,8 @@ public static async Task<IQueryable<TModel>> GetQueryAsync<TModel, TData>(this I
8587
Expression<Func<TModel, bool>> filter = options.ToFilterExpression<TModel>
8688
(
8789
querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.False,
88-
enableConstantParameterization: querySettings?.ODataSettings?.EnableConstantParameterization ?? true
90+
enableConstantParameterization: querySettings?.ODataSettings?.EnableConstantParameterization ?? true,
91+
ensureStableOrdering: querySettings?.ODataSettings?.EnsureStableOrdering ?? true
8992
);
9093
await query.ApplyOptionsAsync(mapper, filter, options, querySettings);
9194
return query.GetQueryable(mapper, options, querySettings, filter);
@@ -107,7 +110,8 @@ public static IQueryable<TModel> GetQuery<TModel, TData>(this IQueryable<TData>
107110
Expression<Func<TModel, bool>> filter = options.ToFilterExpression<TModel>
108111
(
109112
querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.False,
110-
enableConstantParameterization: querySettings?.ODataSettings?.EnableConstantParameterization ?? true
113+
enableConstantParameterization: querySettings?.ODataSettings?.EnableConstantParameterization ?? true,
114+
ensureStableOrdering: querySettings?.ODataSettings?.EnsureStableOrdering ?? true
111115
);
112116
query.ApplyOptions(mapper, filter, options, querySettings);
113117
return query.GetQueryable(mapper, options, querySettings, filter);

AutoMapper.AspNetCore.OData.EFCore/LinqExtensions.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public static Expression<Func<T, bool>> ToFilterExpression<T>(
3636
this FilterQueryOption filterOption,
3737
HandleNullPropagationOption handleNullPropagation = HandleNullPropagationOption.Default,
3838
TimeZoneInfo timeZone = null,
39-
bool enableConstantParameterization = true)
39+
bool enableConstantParameterization = true,
40+
bool ensureStableOrdering = true)
4041
{
4142
if (filterOption == null)
4243
return null;
@@ -45,7 +46,7 @@ public static Expression<Func<T, bool>> ToFilterExpression<T>(
4546

4647
queryable = filterOption.ApplyTo(
4748
queryable,
48-
new ODataQuerySettings() { HandleNullPropagation = handleNullPropagation, TimeZone = timeZone, EnableConstantParameterization = enableConstantParameterization });
49+
new ODataQuerySettings() { HandleNullPropagation = handleNullPropagation, TimeZone = timeZone, EnableConstantParameterization = enableConstantParameterization, EnsureStableOrdering = ensureStableOrdering });
4950

5051
MethodCallExpression whereMethodCallExpression = (MethodCallExpression)queryable.Expression;
5152

@@ -62,15 +63,16 @@ public static Expression<Func<T, bool>> ToSearchExpression<T>(
6263
this SearchQueryOption filterOption,
6364
HandleNullPropagationOption handleNullPropagation = HandleNullPropagationOption.Default,
6465
TimeZoneInfo timeZone = null,
65-
bool enableConstantParameterization = true)
66+
bool enableConstantParameterization = true,
67+
bool ensureStableOrdering = true)
6668
{
6769
if (filterOption == null)
6870
return null;
6971

7072
IQueryable queryable = Enumerable.Empty<T>().AsQueryable();
7173
queryable = filterOption.ApplyTo(
7274
queryable,
73-
new ODataQuerySettings() { HandleNullPropagation = handleNullPropagation, TimeZone = timeZone, EnableConstantParameterization = enableConstantParameterization });
75+
new ODataQuerySettings() { HandleNullPropagation = handleNullPropagation, TimeZone = timeZone, EnableConstantParameterization = enableConstantParameterization, EnsureStableOrdering = ensureStableOrdering });
7476

7577
MethodCallExpression whereMethodCallExpression = (MethodCallExpression)queryable.Expression;
7678

@@ -80,7 +82,8 @@ public static Expression<Func<T, bool>> ToSearchExpression<T>(
8082
public static Expression<Func<T, bool>> ToFilterExpression<T>(this ODataQueryOptions<T> options,
8183
HandleNullPropagationOption handleNullPropagation = HandleNullPropagationOption.Default,
8284
TimeZoneInfo timeZone = null,
83-
bool enableConstantParameterization = true)
85+
bool enableConstantParameterization = true,
86+
bool ensureStableOrdering = true)
8487
{
8588
if (options is null || options.Filter is null && options.Search is null)
8689
{
@@ -92,14 +95,14 @@ public static Expression<Func<T, bool>> ToFilterExpression<T>(this ODataQueryOpt
9295
Expression filterExpression = null;
9396
if (options.Filter is not null)
9497
{
95-
var raw = options.Filter.ToFilterExpression<T>(handleNullPropagation, timeZone, enableConstantParameterization);
98+
var raw = options.Filter.ToFilterExpression<T>(handleNullPropagation, timeZone, enableConstantParameterization, ensureStableOrdering);
9699
filterExpression = raw.Body.ReplaceParameter(raw.Parameters[0], parameter);
97100
}
98101

99102
Expression searchExpression = null;
100103
if (options.Search is not null)
101104
{
102-
var raw = options.Search.ToSearchExpression<T>(handleNullPropagation, timeZone, enableConstantParameterization);
105+
var raw = options.Search.ToSearchExpression<T>(handleNullPropagation, timeZone, enableConstantParameterization, ensureStableOrdering);
103106
searchExpression = raw.Body.ReplaceParameter(raw.Parameters[0], parameter);
104107
}
105108

@@ -189,6 +192,9 @@ public static Expression GetQueryableMethod(this Expression expression,
189192
if (orderByClause is null && skip is null && top is null)
190193
return null;
191194

195+
if (orderByClause is null && !oDataSettings.EnsureStableOrdering)
196+
return expression.GetSkipCall(skip).GetTakeCall(top);
197+
192198
if (orderByClause is null && (skip is not null || top is not null))
193199
{
194200
var orderBySettings = context.FindSortableProperties(type);

AutoMapper.AspNetCore.OData.EFCore/ODataSettings.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class ODataSettings
4141
/// Default is true.
4242
/// </value>
4343
public bool EnableConstantParameterization { get; set; } = true;
44-
44+
4545
/// <summary>
4646
/// If sets to true, orderBy pk desc will always be present on main entity.
4747
/// </summary>
@@ -58,5 +58,14 @@ public class ODataSettings
5858
/// Default is false.
5959
/// </value>
6060
public bool AlwaysSortByPrimaryKey { get; set; }
61+
62+
/// <summary>
63+
/// Gets or sets a value indicating whether query composition should
64+
/// alter the original query when necessary to ensure a stable sort order.
65+
/// </summary>
66+
/// <value>
67+
/// Default is true.
68+
/// </value>
69+
public bool EnsureStableOrdering { get; set; } = true;
6170
}
6271
}

AutoMapper.AspNetCore.OData.EFCore/QueryableExtensions.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public static ICollection<TModel> Get<TModel, TData>(this IQueryable<TData> quer
1919
Expression<Func<TModel, bool>> filter = options.ToFilterExpression<TModel>(
2020
querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.False,
2121
querySettings?.ODataSettings?.TimeZone,
22-
querySettings?.ODataSettings?.EnableConstantParameterization ?? true);
22+
querySettings?.ODataSettings?.EnableConstantParameterization ?? true,
23+
querySettings?.ODataSettings?.EnsureStableOrdering ?? true);
2324

2425
query.ApplyOptions(mapper, filter, options, querySettings);
2526
return query.Get
@@ -37,7 +38,8 @@ public static async Task<ICollection<TModel>> GetAsync<TModel, TData>(this IQuer
3738
Expression<Func<TModel, bool>> filter = options.ToFilterExpression<TModel>(
3839
querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.False,
3940
querySettings?.ODataSettings?.TimeZone,
40-
querySettings?.ODataSettings?.EnableConstantParameterization ?? true);
41+
querySettings?.ODataSettings?.EnableConstantParameterization ?? true,
42+
querySettings?.ODataSettings?.EnsureStableOrdering ?? true);
4143
await query.ApplyOptionsAsync(mapper, filter, options, querySettings);
4244
return await query.GetAsync
4345
(
@@ -55,7 +57,8 @@ public static async Task<IQueryable<TModel>> GetQueryAsync<TModel, TData>(this I
5557
Expression<Func<TModel, bool>> filter = options.ToFilterExpression<TModel>(
5658
querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.False,
5759
querySettings?.ODataSettings?.TimeZone,
58-
querySettings?.ODataSettings?.EnableConstantParameterization ?? true);
60+
querySettings?.ODataSettings?.EnableConstantParameterization ?? true,
61+
querySettings?.ODataSettings?.EnsureStableOrdering ?? true);
5962

6063
await query.ApplyOptionsAsync(mapper, filter, options, querySettings);
6164
return query.GetQueryable(mapper, options, querySettings, filter);
@@ -67,7 +70,8 @@ public static IQueryable<TModel> GetQuery<TModel, TData>(this IQueryable<TData>
6770
Expression<Func<TModel, bool>> filter = options.ToFilterExpression<TModel>(
6871
querySettings?.ODataSettings?.HandleNullPropagation ?? HandleNullPropagationOption.False,
6972
querySettings?.ODataSettings?.TimeZone,
70-
querySettings?.ODataSettings?.EnableConstantParameterization ?? true);
73+
querySettings?.ODataSettings?.EnableConstantParameterization ?? true,
74+
querySettings?.ODataSettings?.EnsureStableOrdering ?? true);
7175
query.ApplyOptions(mapper, filter, options, querySettings);
7276
return query.GetQueryable(mapper, options, querySettings, filter);
7377
}

AutoMapper.OData.EFCore.Tests/GetQueryTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,34 @@ void Test(IQueryable<CoreBuilding> queryable)
896896
}
897897
}
898898

899+
[Fact]
900+
public void BuildingsFilterNameDisableStableOrdering()
901+
{
902+
string query = "/corebuilding?$filter=contains(Name, 'Two L2')&$top=10";
903+
Test(GetQuery<CoreBuilding, TBuilding>(query, querySettings: new() { ODataSettings = new() { EnsureStableOrdering = false } }));
904+
905+
void Test(IQueryable<CoreBuilding> queryable)
906+
{
907+
string sqlQuery = queryable.ToQueryString();
908+
Assert.Contains("TOP", sqlQuery);
909+
Assert.DoesNotContain("ORDER BY [o].[Identifier]", sqlQuery);
910+
}
911+
}
912+
913+
[Fact]
914+
public void BuildingsFilterNameEnableStableOrdering()
915+
{
916+
string query = "/corebuilding?$filter=contains(Name, 'Two L2')&$top=10";
917+
Test(GetQuery<CoreBuilding, TBuilding>(query, querySettings: new() { ODataSettings = new() { EnsureStableOrdering = true } }));
918+
919+
void Test(IQueryable<CoreBuilding> queryable)
920+
{
921+
string sqlQuery = queryable.ToQueryString();
922+
Assert.Contains("TOP", sqlQuery);
923+
Assert.Contains("ORDER BY [o].[Identifier]", sqlQuery);
924+
}
925+
}
926+
899927
[Fact]
900928
public async Task OpsTenantOrderByCountOfReference()
901929
{

0 commit comments

Comments
 (0)