Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ Make SelectExpandBinder and AggregationBinder public and let override there methods #1921

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ public static IContainerBuilder AddDefaultWebApiServices(this IContainerBuilder
builder.AddService<ODataMetadataSerializer>(ServiceLifetime.Singleton);
builder.AddService<ODataRawValueSerializer>(ServiceLifetime.Singleton);

// BinderProvider.
builder.AddService<ODataBinderProvider, DefaultODataBinderProvider>(ServiceLifetime.Singleton);

// Binders.
builder.AddService<ODataQuerySettings>(ServiceLifetime.Scoped);
builder.AddService<FilterBinder>(ServiceLifetime.Transient);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
<Compile Include="$(MSBuildThisFileDirectory)ODataNullValueMessageHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PerRouteContainerBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Query\DefaultSkipTokenHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Query\Expressions\DefaultODataBinderProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Query\Expressions\ODataBinderProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Query\OrderByCountNode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Query\SkipTokenHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Query\Expressions\DynamicTypeWrapperConverter.cs" />
Expand Down
23 changes: 8 additions & 15 deletions src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class ApplyQueryOption
{
private ApplyClause _applyClause;
private ODataQueryOptionParser _queryOptionParser;
private ODataBinderProvider _binderProvider;

/// <summary>
/// Initialize a new instance of <see cref="ApplyQueryOption"/> based on the raw $apply value and
Expand Down Expand Up @@ -54,6 +55,11 @@ public ApplyQueryOption(string rawValue, ODataQueryContext context, ODataQueryOp
//Validator = new FilterQueryValidator();
_queryOptionParser = queryOptionParser;
ResultClrType = Context.ElementClrType;
if (context.RequestContainer != null)
{
_binderProvider = context.RequestContainer.GetRequiredService<ODataBinderProvider>();
}
_binderProvider = _binderProvider ?? new DefaultODataBinderProvider();
}

/// <summary>
Expand Down Expand Up @@ -124,25 +130,12 @@ public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings)
Contract.Assert(applyClause != null);

ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query);

// The IWebApiAssembliesResolver service is internal and can only be injected by WebApi.
// This code path may be used in cases when the service container is not available
// and the service container is available but may not contain an instance of IWebApiAssembliesResolver.
IWebApiAssembliesResolver assembliesResolver = WebApiAssembliesResolver.Default;
if (Context.RequestContainer != null)
{
IWebApiAssembliesResolver injectedResolver = Context.RequestContainer.GetService<IWebApiAssembliesResolver>();
if (injectedResolver != null)
{
assembliesResolver = injectedResolver;
}
}


foreach (var transformation in applyClause.Transformations)
{
if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy)
{
var binder = new AggregationBinder(updatedSettings, assembliesResolver, ResultClrType, Context.Model, transformation);
var binder = _binderProvider.GetAggregationBinder(updatedSettings, Context.RequestContainer, ResultClrType, Context.Model, transformation);
query = binder.Bind(query);
this.ResultClrType = binder.ResultClrType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,30 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNet.OData.Adapters;
using Microsoft.AspNet.OData.Common;
using Microsoft.AspNet.OData.Formatter;
using Microsoft.AspNet.OData.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser;
using Microsoft.OData.UriParser.Aggregation;

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

private bool _classicEF = false;

/// <summary>
/// Initializes a new instance of the <see cref="AggregationBinder"/> class.
/// </summary>
/// <param name="settings">The <see cref="ODataQuerySettings"/> to use during binding.</param>
/// <param name="requestContainer">The request container.</param>
/// <param name="elementType">ClrType for result of transformations.</param>
/// <param name="model">The EDM model.</param>
/// <param name="transformation">The transformation node.</param>
protected internal AggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer, Type elementType,
IEdmModel model, TransformationNode transformation)
: this(settings, (requestContainer != null ? requestContainer.GetService<IWebApiAssembliesResolver>() : null) ?? WebApiAssembliesResolver.Default,
elementType, model, transformation)
{
// Notes for: ?? WebApiAssembliesResolver.Default
// The IWebApiAssembliesResolver service is internal and can only be injected by WebApi.
// This code path may be used in cases when the service container is not available
// and the service container is available but may not contain an instance of IWebApiAssembliesResolver.
}

internal AggregationBinder(ODataQuerySettings settings, IWebApiAssembliesResolver assembliesResolver, Type elementType,
IEdmModel model, TransformationNode transformation)
: base(model, assembliesResolver, settings)
Expand Down Expand Up @@ -140,12 +167,16 @@ public Type ResultClrType
get; private set;
}

public IEdmTypeReference ResultType
internal IEdmTypeReference ResultType
{
get; private set;
}

public IQueryable Bind(IQueryable query)
/// <summary>
/// Applies aggregate or groupby transformations of $apply query option to the given <see cref="IQueryable"/>.
/// </summary>
/// <param name="query">The original <see cref="IQueryable"/>.</param>
public virtual IQueryable Bind(IQueryable query)
{
Contract.Assert(query != null);

Expand Down Expand Up @@ -353,8 +384,16 @@ private Expression CreateAggregationExpression(ParameterExpression accum, Aggreg
}
}

private Expression CreateEntitySetAggregateExpression(
ParameterExpression accum, EntitySetAggregateExpression expression, Type baseType)
/// <summary>
/// Binds a <see cref="EntitySetAggregateExpression"/> to create a LINQ <see cref="Expression"/> that
/// represents the semantics of the <see cref="EntitySetAggregateExpression"/>.
/// </summary>
/// <param name="accumulativeParameter"></param>
/// <param name="expression">The node to bind.</param>
/// <param name="baseType"></param>
/// <returns>The LINQ <see cref="Expression"/> created.</returns>
protected virtual Expression CreateEntitySetAggregateExpression(
ParameterExpression accumulativeParameter, EntitySetAggregateExpression expression, Type baseType)
{
// Should return following expression
// $it => $it.AsQueryable()
Expand All @@ -371,7 +410,7 @@ private Expression CreateEntitySetAggregateExpression(

List<MemberAssignment> wrapperTypeMemberAssignments = new List<MemberAssignment>();
var asQueryableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType);
Expression asQueryableExpression = Expression.Call(null, asQueryableMethod, accum);
Expression asQueryableExpression = Expression.Call(null, asQueryableMethod, accumulativeParameter);

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

private Expression CreatePropertyAggregateExpression(ParameterExpression accum, AggregateExpression expression, Type baseType)
/// <summary>
/// Binds a <see cref="AggregateExpression"/> to create a LINQ <see cref="Expression"/> that
/// represents the semantics of the <see cref="AggregateExpression"/>.
/// </summary>
/// <param name="accumulativeParameter"></param>
/// <param name="expression">The node to bind.</param>
/// <param name="baseType"></param>
/// <returns>The LINQ <see cref="Expression"/> created.</returns>
protected virtual Expression CreatePropertyAggregateExpression(ParameterExpression accumulativeParameter, AggregateExpression expression, Type baseType)
{
// accum type is IGrouping<,baseType> that implements IEnumerable<baseType>
// we need cast it to IEnumerable<baseType> during expression building (IEnumerable)$it
Expand All @@ -442,12 +489,12 @@ private Expression CreatePropertyAggregateExpression(ParameterExpression accum,
if (_classicEF)
{
var asQuerableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType);
asQuerableExpression = Expression.Call(null, asQuerableMethod, accum);
asQuerableExpression = Expression.Call(null, asQuerableMethod, accumulativeParameter);
}
else
{
var queryableType = typeof(IEnumerable<>).MakeGenericType(baseType);
asQuerableExpression = Expression.Convert(accum, queryableType);
asQuerableExpression = Expression.Convert(accumulativeParameter, queryableType);
}

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

private Expression CreatePropertyAccessExpression(Expression source, IEdmProperty property, string propertyPath = null)
/// <summary>
/// Returns an <see cref="Expression"/> that represents access to <paramref name="edmProperty"/>.
/// </summary>
/// <param name="edmProperty">The EDM property which access expression to return.</param>
/// <param name="source">The source that contains the <paramref name="edmProperty"/>.</param>
/// <returns>The property access <see cref="Expression"/>.</returns>
protected virtual Expression CreatePropertyAccessExpression(Expression source, IEdmProperty edmProperty)
{
return CreatePropertyAccessExpression(source, edmProperty, null);
}

/// <summary>
/// Returns an <see cref="Expression"/> that represents access to <paramref name="edmProperty"/>.
/// </summary>
/// <param name="edmProperty">The EDM property which access expression to return.</param>
/// <param name="source">The source that contains the <paramref name="edmProperty"/>.</param>
/// <param name="propertyPath"></param>
/// <returns>The property access <see cref="Expression"/>.</returns>
protected virtual Expression CreatePropertyAccessExpression(Expression source, IEdmProperty edmProperty, string propertyPath)
{
string propertyName = EdmLibHelpers.GetClrPropertyName(property, Model);
string propertyName = EdmLibHelpers.GetClrPropertyName(edmProperty, Model);
propertyPath = propertyPath ?? propertyName;
if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type) &&
source != this._lambdaParameter)
Expand All @@ -666,7 +731,13 @@ private Expression CreatePropertyAccessExpression(Expression source, IEdmPropert
}
}

private Expression CreateOpenPropertyAccessExpression(SingleValueOpenPropertyAccessNode openNode)
/// <summary>
/// Binds a <see cref="SingleValueOpenPropertyAccessNode"/> to create a LINQ <see cref="Expression"/> that
/// represents the semantics of the <see cref="SingleValueOpenPropertyAccessNode"/>.
/// </summary>
/// <param name="openNode">The node to bind.</param>
/// <returns>The LINQ <see cref="Expression"/> created.</returns>
protected virtual Expression CreateOpenPropertyAccessExpression(SingleValueOpenPropertyAccessNode openNode)
{
Expression sourceAccessor = BindAccessor(openNode.Source);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser.Aggregation;

namespace Microsoft.AspNet.OData.Query.Expressions
{
/// <summary>
/// The default <see cref="ODataBinderProvider"/>.
/// </summary>
public class DefaultODataBinderProvider : ODataBinderProvider
{
/// <inheritdoc />
public override SelectExpandBinder GetSelectExpandBinder(ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery)
{
return new SelectExpandBinder(settings, selectExpandQuery);
}

/// <inheritdoc />
public override AggregationBinder GetAggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer, Type elementType,
IEdmModel model, TransformationNode transformation)
{
return new AggregationBinder(settings, requestContainer, elementType, model, transformation);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser.Aggregation;

namespace Microsoft.AspNet.OData.Query.Expressions
{
/// <summary>
/// An ODataBinderProvider is a factory for creating OData binders.
/// </summary>
public abstract class ODataBinderProvider
{
/// <summary>
/// Gets a <see cref="SelectExpandBinder"/>.
/// </summary>
/// <param name="settings">The <see cref="ODataQuerySettings"/> to use during binding.</param>
/// <param name="selectExpandQuery">The <see cref="SelectExpandQueryOption"/> that contains the OData $select and $expand query options.</param>
/// <returns>The <see cref="SelectExpandBinder"/>.</returns>
public abstract SelectExpandBinder GetSelectExpandBinder(ODataQuerySettings settings,
SelectExpandQueryOption selectExpandQuery);

/// <summary>
/// Gets a <see cref="AggregationBinder"/>.
/// </summary>
/// <param name="settings">The <see cref="ODataQuerySettings"/> to use during binding.</param>
/// <param name="elementType">ClrType for result of transformations.</param>
/// <param name="requestContainer">The request container.</param>
/// <param name="model">The EDM model.</param>
/// <param name="transformation">The transformation node.</param>
/// <returns>The <see cref="AggregationBinder"/>.</returns>
public abstract AggregationBinder GetAggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer,
Type elementType, IEdmModel model, TransformationNode transformation);
}
}
Loading