Skip to content

Commit 0e24194

Browse files
author
Sergey Saveliev
committed
Make AggregationBinder public and provide possibility to override its functionality (OData#1900)
1 parent e0172ad commit 0e24194

File tree

6 files changed

+147
-27
lines changed

6 files changed

+147
-27
lines changed

src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class ApplyQueryOption
2323
{
2424
private ApplyClause _applyClause;
2525
private ODataQueryOptionParser _queryOptionParser;
26+
private ODataBinderProvider _binderProvider;
2627

2728
/// <summary>
2829
/// Initialize a new instance of <see cref="ApplyQueryOption"/> based on the raw $apply value and
@@ -54,6 +55,8 @@ public ApplyQueryOption(string rawValue, ODataQueryContext context, ODataQueryOp
5455
//Validator = new FilterQueryValidator();
5556
_queryOptionParser = queryOptionParser;
5657
ResultClrType = Context.ElementClrType;
58+
_binderProvider = context.RequestContainer?.GetRequiredService<ODataBinderProvider>() ??
59+
new DefaultODataBinderProvider();
5760
}
5861

5962
/// <summary>
@@ -124,25 +127,12 @@ public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings)
124127
Contract.Assert(applyClause != null);
125128

126129
ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query);
127-
128-
// The IWebApiAssembliesResolver service is internal and can only be injected by WebApi.
129-
// This code path may be used in cases when the service container is not available
130-
// and the service container is available but may not contain an instance of IWebApiAssembliesResolver.
131-
IWebApiAssembliesResolver assembliesResolver = WebApiAssembliesResolver.Default;
132-
if (Context.RequestContainer != null)
133-
{
134-
IWebApiAssembliesResolver injectedResolver = Context.RequestContainer.GetService<IWebApiAssembliesResolver>();
135-
if (injectedResolver != null)
136-
{
137-
assembliesResolver = injectedResolver;
138-
}
139-
}
140-
130+
141131
foreach (var transformation in applyClause.Transformations)
142132
{
143133
if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy)
144134
{
145-
var binder = new AggregationBinder(updatedSettings, assembliesResolver, ResultClrType, Context.Model, transformation);
135+
var binder = _binderProvider.GetAggregationBinder(updatedSettings, Context.RequestContainer, ResultClrType, Context.Model, transformation);
146136
query = binder.Bind(query);
147137
this.ResultClrType = binder.ResultClrType;
148138
}

src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Diagnostics.Contracts;
78
using System.Globalization;
89
using System.Linq;
910
using System.Linq.Expressions;
1011
using System.Reflection;
12+
using Microsoft.AspNet.OData.Adapters;
1113
using Microsoft.AspNet.OData.Common;
1214
using Microsoft.AspNet.OData.Formatter;
1315
using Microsoft.AspNet.OData.Interfaces;
16+
using Microsoft.Extensions.DependencyInjection;
1417
using Microsoft.OData;
1518
using Microsoft.OData.Edm;
1619
using Microsoft.OData.UriParser;
1720
using Microsoft.OData.UriParser.Aggregation;
1821

1922
namespace Microsoft.AspNet.OData.Query.Expressions
2023
{
21-
internal class AggregationBinder : ExpressionBinderBase
24+
/// <summary>
25+
/// Translates an OData aggregate or groupby transformations of $apply parse tree represented by <see cref="TransformationNode"/> to
26+
/// an <see cref="Expression"/> and applies it to an <see cref="IQueryable"/>.
27+
/// </summary>
28+
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many ODataLib classes.")]
29+
public class AggregationBinder : ExpressionBinderBase
2230
{
2331
private const string GroupByContainerProperty = "GroupByContainer";
2432
private Type _elementType;
@@ -33,6 +41,25 @@ internal class AggregationBinder : ExpressionBinderBase
3341

3442
private bool _classicEF = false;
3543

44+
/// <summary>
45+
/// Initializes a new instance of the <see cref="AggregationBinder"/> class.
46+
/// </summary>
47+
/// <param name="settings">The <see cref="ODataQuerySettings"/> to use during binding.</param>
48+
/// <param name="requestContainer">The request container.</param>
49+
/// <param name="elementType">ClrType for result of transformations.</param>
50+
/// <param name="model">The EDM model.</param>
51+
/// <param name="transformation">The transformation node.</param>
52+
protected internal AggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer, Type elementType,
53+
IEdmModel model, TransformationNode transformation)
54+
: this(settings, requestContainer?.GetService<IWebApiAssembliesResolver>() ?? WebApiAssembliesResolver.Default,
55+
elementType, model, transformation)
56+
{
57+
// Notes for: ?? WebApiAssembliesResolver.Default
58+
// The IWebApiAssembliesResolver service is internal and can only be injected by WebApi.
59+
// This code path may be used in cases when the service container is not available
60+
// and the service container is available but may not contain an instance of IWebApiAssembliesResolver.
61+
}
62+
3663
internal AggregationBinder(ODataQuerySettings settings, IWebApiAssembliesResolver assembliesResolver, Type elementType,
3764
IEdmModel model, TransformationNode transformation)
3865
: base(model, assembliesResolver, settings)
@@ -140,12 +167,16 @@ public Type ResultClrType
140167
get; private set;
141168
}
142169

143-
public IEdmTypeReference ResultType
170+
internal IEdmTypeReference ResultType
144171
{
145172
get; private set;
146173
}
147174

148-
public IQueryable Bind(IQueryable query)
175+
/// <summary>
176+
/// Applies aggregate or groupby transformations of $apply query option to the given <see cref="IQueryable"/>.
177+
/// </summary>
178+
/// <param name="query">The original <see cref="IQueryable"/>.</param>
179+
public virtual IQueryable Bind(IQueryable query)
149180
{
150181
Contract.Assert(query != null);
151182

@@ -353,8 +384,16 @@ private Expression CreateAggregationExpression(ParameterExpression accum, Aggreg
353384
}
354385
}
355386

356-
private Expression CreateEntitySetAggregateExpression(
357-
ParameterExpression accum, EntitySetAggregateExpression expression, Type baseType)
387+
/// <summary>
388+
/// Binds a <see cref="EntitySetAggregateExpression"/> to create a LINQ <see cref="Expression"/> that
389+
/// represents the semantics of the <see cref="EntitySetAggregateExpression"/>.
390+
/// </summary>
391+
/// <param name="accumulativeParameter"></param>
392+
/// <param name="expression">The node to bind.</param>
393+
/// <param name="baseType"></param>
394+
/// <returns>The LINQ <see cref="Expression"/> created.</returns>
395+
protected virtual Expression CreateEntitySetAggregateExpression(
396+
ParameterExpression accumulativeParameter, EntitySetAggregateExpression expression, Type baseType)
358397
{
359398
// Should return following expression
360399
// $it => $it.AsQueryable()
@@ -371,7 +410,7 @@ private Expression CreateEntitySetAggregateExpression(
371410

372411
List<MemberAssignment> wrapperTypeMemberAssignments = new List<MemberAssignment>();
373412
var asQueryableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType);
374-
Expression asQueryableExpression = Expression.Call(null, asQueryableMethod, accum);
413+
Expression asQueryableExpression = Expression.Call(null, asQueryableMethod, accumulativeParameter);
375414

376415
// Create lambda to access the entity set from expression
377416
var source = BindAccessor(expression.Expression.Source);
@@ -433,7 +472,15 @@ MethodInfo selectManyMethod
433472
return Expression.Call(null, selectMethod, groupedEntitySet, selectLambda);
434473
}
435474

436-
private Expression CreatePropertyAggregateExpression(ParameterExpression accum, AggregateExpression expression, Type baseType)
475+
/// <summary>
476+
/// Binds a <see cref="AggregateExpression"/> to create a LINQ <see cref="Expression"/> that
477+
/// represents the semantics of the <see cref="AggregateExpression"/>.
478+
/// </summary>
479+
/// <param name="accumulativeParameter"></param>
480+
/// <param name="expression">The node to bind.</param>
481+
/// <param name="baseType"></param>
482+
/// <returns>The LINQ <see cref="Expression"/> created.</returns>
483+
protected virtual Expression CreatePropertyAggregateExpression(ParameterExpression accumulativeParameter, AggregateExpression expression, Type baseType)
437484
{
438485
// accum type is IGrouping<,baseType> that implements IEnumerable<baseType>
439486
// we need cast it to IEnumerable<baseType> during expression building (IEnumerable)$it
@@ -442,12 +489,12 @@ private Expression CreatePropertyAggregateExpression(ParameterExpression accum,
442489
if (_classicEF)
443490
{
444491
var asQuerableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType);
445-
asQuerableExpression = Expression.Call(null, asQuerableMethod, accum);
492+
asQuerableExpression = Expression.Call(null, asQuerableMethod, accumulativeParameter);
446493
}
447494
else
448495
{
449496
var queryableType = typeof(IEnumerable<>).MakeGenericType(baseType);
450-
asQuerableExpression = Expression.Convert(accum, queryableType);
497+
asQuerableExpression = Expression.Convert(accumulativeParameter, queryableType);
451498
}
452499

453500
// $count is a virtual property, so there's not a propertyLambda to create.
@@ -639,9 +686,27 @@ private Expression BindAccessor(QueryNode node, Expression baseElement = null)
639686
}
640687
}
641688

642-
private Expression CreatePropertyAccessExpression(Expression source, IEdmProperty property, string propertyPath = null)
689+
/// <summary>
690+
/// Returns an <see cref="Expression"/> that represents access to <paramref name="edmProperty"/>.
691+
/// </summary>
692+
/// <param name="edmProperty">The EDM property which access expression to return.</param>
693+
/// <param name="source">The source that contains the <paramref name="edmProperty"/>.</param>
694+
/// <returns>The property access <see cref="Expression"/>.</returns>
695+
protected virtual Expression CreatePropertyAccessExpression(Expression source, IEdmProperty edmProperty)
696+
{
697+
return CreatePropertyAccessExpression(source, edmProperty, null);
698+
}
699+
700+
/// <summary>
701+
/// Returns an <see cref="Expression"/> that represents access to <paramref name="edmProperty"/>.
702+
/// </summary>
703+
/// <param name="edmProperty">The EDM property which access expression to return.</param>
704+
/// <param name="source">The source that contains the <paramref name="edmProperty"/>.</param>
705+
/// <param name="propertyPath"></param>
706+
/// <returns>The property access <see cref="Expression"/>.</returns>
707+
protected virtual Expression CreatePropertyAccessExpression(Expression source, IEdmProperty edmProperty, string propertyPath)
643708
{
644-
string propertyName = EdmLibHelpers.GetClrPropertyName(property, Model);
709+
string propertyName = EdmLibHelpers.GetClrPropertyName(edmProperty, Model);
645710
propertyPath = propertyPath ?? propertyName;
646711
if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type) &&
647712
source != this._lambdaParameter)
@@ -666,7 +731,13 @@ private Expression CreatePropertyAccessExpression(Expression source, IEdmPropert
666731
}
667732
}
668733

669-
private Expression CreateOpenPropertyAccessExpression(SingleValueOpenPropertyAccessNode openNode)
734+
/// <summary>
735+
/// Binds a <see cref="SingleValueOpenPropertyAccessNode"/> to create a LINQ <see cref="Expression"/> that
736+
/// represents the semantics of the <see cref="SingleValueOpenPropertyAccessNode"/>.
737+
/// </summary>
738+
/// <param name="openNode">The node to bind.</param>
739+
/// <returns>The LINQ <see cref="Expression"/> created.</returns>
740+
protected virtual Expression CreateOpenPropertyAccessExpression(SingleValueOpenPropertyAccessNode openNode)
670741
{
671742
Expression sourceAccessor = BindAccessor(openNode.Source);
672743

src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System;
5+
using Microsoft.OData.Edm;
6+
using Microsoft.OData.UriParser.Aggregation;
7+
48
namespace Microsoft.AspNet.OData.Query.Expressions
59
{
610
/// <summary>
@@ -13,5 +17,12 @@ public override SelectExpandBinder GetSelectExpandBinder(ODataQuerySettings sett
1317
{
1418
return new SelectExpandBinder(settings, selectExpandQuery);
1519
}
20+
21+
/// <inheritdoc />
22+
public override AggregationBinder GetAggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer, Type elementType,
23+
IEdmModel model, TransformationNode transformation)
24+
{
25+
return new AggregationBinder(settings, requestContainer, elementType, model, transformation);
26+
}
1627
}
1728
}

src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System;
5+
using Microsoft.OData.Edm;
6+
using Microsoft.OData.UriParser.Aggregation;
7+
48
namespace Microsoft.AspNet.OData.Query.Expressions
59
{
610
/// <summary>
@@ -16,5 +20,17 @@ public abstract class ODataBinderProvider
1620
/// <returns>The <see cref="SelectExpandBinder"/>.</returns>
1721
public abstract SelectExpandBinder GetSelectExpandBinder(ODataQuerySettings settings,
1822
SelectExpandQueryOption selectExpandQuery);
23+
24+
/// <summary>
25+
/// Gets a <see cref="AggregationBinder"/>.
26+
/// </summary>
27+
/// <param name="settings">The <see cref="ODataQuerySettings"/> to use during binding.</param>
28+
/// <param name="elementType">ClrType for result of transformations.</param>
29+
/// <param name="requestContainer">The request container.</param>
30+
/// <param name="model">The EDM model.</param>
31+
/// <param name="transformation">The transformation node.</param>
32+
/// <returns>The <see cref="AggregationBinder"/>.</returns>
33+
public abstract AggregationBinder GetAggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer,
34+
Type elementType, IEdmModel model, TransformationNode transformation);
1935
}
2036
}

test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3358,12 +3358,28 @@ public abstract class Microsoft.AspNet.OData.Query.Expressions.ExpressionBinderB
33583358
public abstract class Microsoft.AspNet.OData.Query.Expressions.ODataBinderProvider {
33593359
protected ODataBinderProvider ()
33603360

3361+
public abstract AggregationBinder GetAggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation)
33613362
public abstract SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery)
33623363
}
33633364

3365+
public class Microsoft.AspNet.OData.Query.Expressions.AggregationBinder : ExpressionBinderBase {
3366+
protected AggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation)
3367+
3368+
System.Type ResultClrType { public get; }
3369+
3370+
public virtual System.Linq.IQueryable Bind (System.Linq.IQueryable query)
3371+
protected virtual System.Linq.Expressions.Expression CreateEntitySetAggregateExpression (System.Linq.Expressions.ParameterExpression accumulativeParameter, Microsoft.OData.UriParser.Aggregation.EntitySetAggregateExpression expression, System.Type baseType)
3372+
protected virtual System.Linq.Expressions.Expression CreateOpenPropertyAccessExpression (Microsoft.OData.UriParser.SingleValueOpenPropertyAccessNode openNode)
3373+
protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty)
3374+
protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty, string propertyPath)
3375+
protected virtual System.Linq.Expressions.Expression CreatePropertyAggregateExpression (System.Linq.Expressions.ParameterExpression accumulativeParameter, Microsoft.OData.UriParser.Aggregation.AggregateExpression expression, System.Type baseType)
3376+
internal virtual bool IsClassicEF (System.Linq.IQueryable query)
3377+
}
3378+
33643379
public class Microsoft.AspNet.OData.Query.Expressions.DefaultODataBinderProvider : ODataBinderProvider {
33653380
public DefaultODataBinderProvider ()
33663381

3382+
public virtual AggregationBinder GetAggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation)
33673383
public virtual SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery)
33683384
}
33693385

test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3489,12 +3489,28 @@ public abstract class Microsoft.AspNet.OData.Query.Expressions.ExpressionBinderB
34893489
public abstract class Microsoft.AspNet.OData.Query.Expressions.ODataBinderProvider {
34903490
protected ODataBinderProvider ()
34913491

3492+
public abstract AggregationBinder GetAggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation)
34923493
public abstract SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery)
34933494
}
34943495

3496+
public class Microsoft.AspNet.OData.Query.Expressions.AggregationBinder : ExpressionBinderBase {
3497+
protected AggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation)
3498+
3499+
System.Type ResultClrType { public get; }
3500+
3501+
public virtual System.Linq.IQueryable Bind (System.Linq.IQueryable query)
3502+
protected virtual System.Linq.Expressions.Expression CreateEntitySetAggregateExpression (System.Linq.Expressions.ParameterExpression accumulativeParameter, Microsoft.OData.UriParser.Aggregation.EntitySetAggregateExpression expression, System.Type baseType)
3503+
protected virtual System.Linq.Expressions.Expression CreateOpenPropertyAccessExpression (Microsoft.OData.UriParser.SingleValueOpenPropertyAccessNode openNode)
3504+
protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty)
3505+
protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty, string propertyPath)
3506+
protected virtual System.Linq.Expressions.Expression CreatePropertyAggregateExpression (System.Linq.Expressions.ParameterExpression accumulativeParameter, Microsoft.OData.UriParser.Aggregation.AggregateExpression expression, System.Type baseType)
3507+
internal virtual bool IsClassicEF (System.Linq.IQueryable query)
3508+
}
3509+
34953510
public class Microsoft.AspNet.OData.Query.Expressions.DefaultODataBinderProvider : ODataBinderProvider {
34963511
public DefaultODataBinderProvider ()
34973512

3513+
public virtual AggregationBinder GetAggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation)
34983514
public virtual SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery)
34993515
}
35003516

0 commit comments

Comments
 (0)