Skip to content

Commit 279f60d

Browse files
kosinskyxuzhg
andauthored
Added support for compute() transformation in $apply (#1877)
* Added support for functions and constants in aggregate expression * Tests for more cannonical functions * Time tests * Support for compute transformation in $apply * Support sorting for columns introduced in compute * Remove redundant code * Fix flakyness in AggregateNavigationPropertyWorks * Fix the ToInt16 overflow during build * Fix typos * Make _values field private * Explicit way to mutate element type and lambda param * Test to demonstrate that we could run compute on complex entity properties * Support for complex types * Simplify keepoing ElementType and LambdaParameter in sync Co-authored-by: Sam Xu <[email protected]>
1 parent ec69a7b commit 279f60d

File tree

19 files changed

+1768
-979
lines changed

19 files changed

+1768
-979
lines changed

src/Microsoft.AspNet.OData.Shared/Common/CollectionExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,5 +270,13 @@ private static Dictionary<TKey, TValue> ToDictionaryFastNoCheck<TKey, TValue>(IL
270270
}
271271
return dictionary;
272272
}
273+
274+
public static void MergeWithReplace<TKey, TValue>(this Dictionary<TKey, TValue> target, Dictionary<TKey, TValue> source)
275+
{
276+
foreach (var kvp in source)
277+
{
278+
target[kvp.Key] = kvp.Value;
279+
}
280+
}
273281
}
274282
}

src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@
165165
<Compile Include="$(MSBuildThisFileDirectory)Builder\OperationTitleAnnotation.cs" />
166166
<Compile Include="$(MSBuildThisFileDirectory)Builder\DynamicPropertyDictionaryAnnotation.cs" />
167167
<Compile Include="$(MSBuildThisFileDirectory)Query\Expressions\AggregationBinder.cs" />
168+
<Compile Include="$(MSBuildThisFileDirectory)Query\Expressions\ComputeBinder.cs" />
169+
<Compile Include="$(MSBuildThisFileDirectory)Query\Expressions\TransformationBinderBase.cs" />
168170
<Compile Include="$(MSBuildThisFileDirectory)Query\ApplyQueryOption.cs" />
169171
<Compile Include="$(MSBuildThisFileDirectory)Query\Expressions\DynamicTypeWrapper.cs" />
170172
<Compile Include="$(MSBuildThisFileDirectory)Query\Expressions\ExpressionBinderBase.cs" />

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings)
146146
query = binder.Bind(query);
147147
this.ResultClrType = binder.ResultClrType;
148148
}
149+
else if (transformation.Kind == TransformationNodeKind.Compute)
150+
{
151+
var binder = new ComputeBinder(updatedSettings, assembliesResolver, ResultClrType, Context.Model, (ComputeTransformationNode)transformation);
152+
query = binder.Bind(query);
153+
this.ResultClrType = binder.ResultClrType;
154+
}
149155
else if (transformation.Kind == TransformationNodeKind.Filter)
150156
{
151157
var filterTransformation = transformation as FilterTransformationNode;

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

Lines changed: 28 additions & 181 deletions
Large diffs are not rendered by default.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.Contracts;
7+
using System.Linq;
8+
using System.Linq.Expressions;
9+
using Microsoft.AspNet.OData.Interfaces;
10+
using Microsoft.OData.Edm;
11+
using Microsoft.OData.UriParser;
12+
using Microsoft.OData.UriParser.Aggregation;
13+
14+
namespace Microsoft.AspNet.OData.Query.Expressions
15+
{
16+
internal class ComputeBinder : TransformationBinderBase
17+
{
18+
private ComputeTransformationNode _transformation;
19+
private string _modelID;
20+
21+
internal ComputeBinder(ODataQuerySettings settings, IWebApiAssembliesResolver assembliesResolver, Type elementType,
22+
IEdmModel model, ComputeTransformationNode transformation)
23+
: base(settings, assembliesResolver, elementType, model)
24+
{
25+
Contract.Assert(transformation != null);
26+
27+
_transformation = transformation;
28+
_modelID = ModelContainer.GetModelID(model);
29+
30+
this.ResultClrType = typeof(ComputeWrapper<>).MakeGenericType(this.ElementType);
31+
}
32+
33+
public IQueryable Bind(IQueryable query)
34+
{
35+
PreprocessQuery(query);
36+
// compute(X add Y as Z, A mul B as C) adds new properties to the output
37+
// Should return following expression
38+
// .Select($it => new ComputeWrapper<T> {
39+
// Instance = $it,
40+
// ModelID = 'Guid',
41+
// Container => new AggregationPropertyContainer() {
42+
// Name = "X",
43+
// Value = $it.X + $it.Y,
44+
// Next = new LastInChain() {
45+
// Name = "C",
46+
// Value = $it.A * $it.B
47+
// }
48+
// })
49+
50+
List<MemberAssignment> wrapperTypeMemberAssignments = new List<MemberAssignment>();
51+
52+
// Set Instance property
53+
var wrapperProperty = this.ResultClrType.GetProperty("Instance");
54+
wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, this.LambdaParameter));
55+
var properties = new List<NamedPropertyExpression>();
56+
foreach (var computeExpression in this._transformation.Expressions)
57+
{
58+
properties.Add(new NamedPropertyExpression(Expression.Constant(computeExpression.Alias), CreateComputeExpression(computeExpression)));
59+
}
60+
61+
// Initialize property 'ModelID' on the wrapper class.
62+
// source = new Wrapper { ModelID = 'some-guid-id' }
63+
wrapperProperty = this.ResultClrType.GetProperty("ModelID");
64+
var wrapperPropertyValueExpression = QuerySettings.EnableConstantParameterization ?
65+
LinqParameterContainer.Parameterize(typeof(string), _modelID) :
66+
Expression.Constant(_modelID);
67+
wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, wrapperPropertyValueExpression));
68+
69+
// Set new compute properties
70+
wrapperProperty = ResultClrType.GetProperty("Container");
71+
wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(properties)));
72+
73+
var initilizedMember =
74+
Expression.MemberInit(Expression.New(ResultClrType), wrapperTypeMemberAssignments);
75+
var selectLambda = Expression.Lambda(initilizedMember, this.LambdaParameter);
76+
77+
var result = ExpressionHelpers.Select(query, selectLambda, this.ElementType);
78+
return result;
79+
}
80+
81+
private Expression CreateComputeExpression(ComputeExpression expression)
82+
{
83+
Expression body = BindAccessor(expression.Expression);
84+
return WrapConvert(body);
85+
}
86+
}
87+
}

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

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33

44
using System.Collections.Generic;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Diagnostics.Contracts;
67
using System.Linq;
8+
using Microsoft.AspNet.OData.Common;
9+
using Microsoft.AspNet.OData.Formatter;
10+
using Microsoft.AspNet.OData.Formatter.Serialization;
11+
using Microsoft.OData.Edm;
712
using Newtonsoft.Json;
813
[module: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Extra needed to workaorund EF issue with expression shape.")]
914

@@ -98,7 +103,7 @@ private void EnsureValues()
98103

99104
if (this.Container != null)
100105
{
101-
_values = _values.Concat(this.Container.ToDictionary(DefaultPropertyMapper)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
106+
_values.MergeWithReplace(this.Container.ToDictionary(DefaultPropertyMapper));
102107
}
103108
}
104109
}
@@ -123,5 +128,82 @@ internal class NoGroupByAggregationWrapper : GroupByWrapper
123128

124129
internal class EntitySetAggregationWrapper : GroupByWrapper
125130
{
126-
}
131+
}
132+
133+
internal class ComputeWrapper<T> : GroupByWrapper, IEdmEntityObject
134+
{
135+
public T Instance { get; set; }
136+
137+
/// <summary>
138+
/// An ID to uniquely identify the model in the <see cref="ModelContainer"/>.
139+
/// </summary>
140+
public string ModelID { get; set; }
141+
142+
public override Dictionary<string, object> Values
143+
{
144+
get
145+
{
146+
EnsureValues();
147+
return base.Values;
148+
}
149+
}
150+
151+
private bool _merged;
152+
private void EnsureValues()
153+
{
154+
if (!this._merged)
155+
{
156+
// Base properties available via Instance can be real OData properties or generated in previous transformations
157+
158+
var instanceContainer = this.Instance as DynamicTypeWrapper;
159+
if (instanceContainer != null)
160+
{
161+
// Add proeprties generated in previous transformations to the collection
162+
base.Values.MergeWithReplace(instanceContainer.Values);
163+
}
164+
else
165+
{
166+
// Add real OData properties to the collection
167+
// We need to use injected Model to real property names
168+
var edmType = GetEdmType() as IEdmStructuredTypeReference;
169+
170+
if (edmType is IEdmComplexTypeReference t)
171+
{
172+
_typedEdmStructuredObject = _typedEdmStructuredObject ??
173+
new TypedEdmComplexObject(Instance, t, GetModel());
174+
}
175+
else
176+
{
177+
_typedEdmStructuredObject = _typedEdmStructuredObject ??
178+
new TypedEdmEntityObject(Instance, edmType as IEdmEntityTypeReference, GetModel());
179+
}
180+
181+
var props = edmType.DeclaredStructuralProperties().Where(p => p.Type.IsPrimitive()).Select(p => p.Name);
182+
foreach (var propertyName in props)
183+
{
184+
object value;
185+
if (_typedEdmStructuredObject.TryGetPropertyValue(propertyName, out value))
186+
{
187+
base.Values[propertyName] = value;
188+
}
189+
}
190+
}
191+
this._merged = true;
192+
}
193+
}
194+
private TypedEdmStructuredObject _typedEdmStructuredObject;
195+
196+
private IEdmModel GetModel()
197+
{
198+
Contract.Assert(ModelID != null);
199+
200+
return ModelContainer.GetModel(ModelID);
201+
}
202+
203+
public IEdmTypeReference GetEdmType()
204+
{
205+
IEdmModel model = GetModel();
206+
return model.GetEdmTypeReference(typeof(T));
207+
}
208+
}
127209
}

0 commit comments

Comments
 (0)