diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
index 2d2db69af..d227d0ce6 100644
--- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
+++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
@@ -7639,6 +7639,11 @@
A value that corresponds to allowing the $apply query option.
+
+
+ A value that corresponds to allowing the $compute query option.
+
+
A value that corresponds to the default query options supported.
@@ -9704,6 +9709,11 @@
Gets the .
+
+
+ Gets the .
+
+
Gets the .
@@ -10343,6 +10353,42 @@
The that contains all the query application related settings.
The new after the filter query has been applied to.
+
+
+ This defines a $compute OData query option for querying.
+ The $compute system query option allows clients to define computed properties that can be used in a $select or within a $filter or $orderby expression.
+ Computed properties SHOULD be included as dynamic properties in the result and MUST be included if $select is specified with the computed property name, or star (*).
+
+
+
+
+ Initialize a new instance of based on the raw $compute value and
+ an EdmModel from .
+
+ The raw value for $filter query. It can be null or empty.
+ The which contains the and some type information
+ The which is used to parse the query option.
+
+
+
+ Gets the given .
+
+
+
+
+ ClrType for result of transformations
+
+
+
+
+ Gets the parsed for this query option.
+
+
+
+
+ Gets the raw $compute value.
+
+
Represents the value of the $count query option and exposes a way to retrieve the number of entities that satisfy a query.
@@ -10546,6 +10592,11 @@
Gets the raw $apply query value from the incoming request Uri if exists.
+
+
+ Gets the raw $compute query value from the incoming request Uri if exists.
+
+
Gets the raw $orderby query value from the incoming request Uri if exists.
diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
index e881e74cb..90c16045b 100644
--- a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
@@ -734,6 +734,7 @@ Microsoft.AspNetCore.OData.Query.AllowedLogicalOperators.Or = 1 -> Microsoft.Asp
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.All = Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.DeltaToken | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Supported -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Apply = 1024 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
+Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Compute = 2048 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Count = 64 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.DeltaToken = 512 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Expand = 2 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
@@ -744,7 +745,7 @@ Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.OrderBy = 8 -> Microsoft.As
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Select = 4 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Skip = 32 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.SkipToken = 256 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
-Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Supported = Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Filter | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Expand | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Select | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.OrderBy | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Top | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Skip | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Count | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Format | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.SkipToken | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Apply -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
+Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Supported = Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Filter | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Expand | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Select | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.OrderBy | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Top | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Skip | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Count | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Format | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.SkipToken | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Apply | Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Compute -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.AllowedQueryOptions.Top = 16 -> Microsoft.AspNetCore.OData.Query.AllowedQueryOptions
Microsoft.AspNetCore.OData.Query.ApplyQueryOption
Microsoft.AspNetCore.OData.Query.ApplyQueryOption.ApplyClause.get -> Microsoft.OData.UriParser.Aggregation.ApplyClause
@@ -753,6 +754,12 @@ Microsoft.AspNetCore.OData.Query.ApplyQueryOption.ApplyTo(System.Linq.IQueryable
Microsoft.AspNetCore.OData.Query.ApplyQueryOption.Context.get -> Microsoft.AspNetCore.OData.Query.ODataQueryContext
Microsoft.AspNetCore.OData.Query.ApplyQueryOption.RawValue.get -> string
Microsoft.AspNetCore.OData.Query.ApplyQueryOption.ResultClrType.get -> System.Type
+Microsoft.AspNetCore.OData.Query.ComputeQueryOption
+Microsoft.AspNetCore.OData.Query.ComputeQueryOption.ComputeClause.get -> Microsoft.OData.UriParser.ComputeClause
+Microsoft.AspNetCore.OData.Query.ComputeQueryOption.ComputeQueryOption(string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser) -> void
+Microsoft.AspNetCore.OData.Query.ComputeQueryOption.Context.get -> Microsoft.AspNetCore.OData.Query.ODataQueryContext
+Microsoft.AspNetCore.OData.Query.ComputeQueryOption.RawValue.get -> string
+Microsoft.AspNetCore.OData.Query.ComputeQueryOption.ResultClrType.get -> System.Type
Microsoft.AspNetCore.OData.Query.Container.IPropertyMapper
Microsoft.AspNetCore.OData.Query.Container.IPropertyMapper.MapProperty(string propertyName) -> string
Microsoft.AspNetCore.OData.Query.Container.ITruncatedCollection
@@ -922,6 +929,7 @@ Microsoft.AspNetCore.OData.Query.ODataQueryContext.Path.get -> Microsoft.OData.U
Microsoft.AspNetCore.OData.Query.ODataQueryContext.RequestContainer.get -> System.IServiceProvider
Microsoft.AspNetCore.OData.Query.ODataQueryOptions
Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Apply.get -> Microsoft.AspNetCore.OData.Query.ApplyQueryOption
+Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Compute.get -> Microsoft.AspNetCore.OData.Query.ComputeQueryOption
Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Context.get -> Microsoft.AspNetCore.OData.Query.ODataQueryContext
Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Count.get -> Microsoft.AspNetCore.OData.Query.CountQueryOption
Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Filter.get -> Microsoft.AspNetCore.OData.Query.FilterQueryOption
@@ -963,6 +971,7 @@ Microsoft.AspNetCore.OData.Query.ODataQuerySettings.TimeZone.get -> System.TimeZ
Microsoft.AspNetCore.OData.Query.ODataQuerySettings.TimeZone.set -> void
Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions
Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.Apply.get -> string
+Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.Compute.get -> string
Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.Count.get -> string
Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.DeltaToken.get -> string
Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions.Expand.get -> string
diff --git a/src/Microsoft.AspNetCore.OData/Query/AllowedQueryOptions.cs b/src/Microsoft.AspNetCore.OData/Query/AllowedQueryOptions.cs
index 20e123a3a..0e37564d1 100644
--- a/src/Microsoft.AspNetCore.OData/Query/AllowedQueryOptions.cs
+++ b/src/Microsoft.AspNetCore.OData/Query/AllowedQueryOptions.cs
@@ -75,14 +75,19 @@ public enum AllowedQueryOptions
///
Apply = 0x400,
+ ///
+ /// A value that corresponds to allowing the $compute query option.
+ ///
+ Compute = 0x800,
+
///
/// A value that corresponds to the default query options supported.
///
- Supported = Filter | OrderBy | Top | Skip | SkipToken | Count | Select | Expand | Format | Apply,
+ Supported = Filter | OrderBy | Top | Skip | SkipToken | Count | Select | Expand | Format | Apply | Compute,
///
/// A value that corresponds to allowing all query options.
///
- All = Filter | Expand | Select | OrderBy | Top | Skip | Count | Format | SkipToken | DeltaToken | Apply
+ All = Filter | Expand | Select | OrderBy | Top | Skip | Count | Format | SkipToken | DeltaToken | Apply | Compute
}
}
diff --git a/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs b/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs
index fc6b921a8..7261bbc6f 100644
--- a/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs
+++ b/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs
@@ -110,6 +110,11 @@ public ODataQueryOptions(ODataQueryContext context, HttpRequest request)
///
public ApplyQueryOption Apply { get; private set; }
+ ///
+ /// Gets the .
+ ///
+ public ComputeQueryOption Compute { get; private set; }
+
///
/// Gets the .
///
@@ -973,6 +978,11 @@ private void BuildQueryOptions(IDictionary queryParameters)
RawValues.Apply = kvp.Value;
Apply = new ApplyQueryOption(kvp.Value, Context, _queryOptionParser);
break;
+ case "$compute":
+ ThrowIfEmpty(kvp.Value, "$compute");
+ RawValues.Compute = kvp.Value;
+ Compute = new ComputeQueryOption(kvp.Value, Context, _queryOptionParser);
+ break;
default:
// we don't throw if we can't recognize the query
break;
diff --git a/src/Microsoft.AspNetCore.OData/Query/Query/ComputeQueryOption.cs b/src/Microsoft.AspNetCore.OData/Query/Query/ComputeQueryOption.cs
new file mode 100644
index 000000000..a69fbc463
--- /dev/null
+++ b/src/Microsoft.AspNetCore.OData/Query/Query/ComputeQueryOption.cs
@@ -0,0 +1,110 @@
+//-----------------------------------------------------------------------------
+//
+// Copyright (c) .NET Foundation and Contributors. All rights reserved.
+// See License.txt in the project root for license information.
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using Microsoft.OData.Edm;
+using Microsoft.OData.UriParser;
+
+namespace Microsoft.AspNetCore.OData.Query
+{
+ ///
+ /// This defines a $compute OData query option for querying.
+ /// The $compute system query option allows clients to define computed properties that can be used in a $select or within a $filter or $orderby expression.
+ /// Computed properties SHOULD be included as dynamic properties in the result and MUST be included if $select is specified with the computed property name, or star (*).
+ ///
+ public class ComputeQueryOption
+ {
+ private ComputeClause _computeClause;
+ private ODataQueryOptionParser _queryOptionParser;
+
+ ///
+ /// Initialize a new instance of based on the raw $compute value and
+ /// an EdmModel from .
+ ///
+ /// The raw value for $filter query. It can be null or empty.
+ /// The which contains the and some type information
+ /// The which is used to parse the query option.
+ public ComputeQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser)
+ {
+ if (string.IsNullOrEmpty(rawValue))
+ {
+ throw Error.ArgumentNullOrEmpty(nameof(rawValue));
+ }
+
+ if (context == null)
+ {
+ throw Error.ArgumentNull(nameof(context));
+ }
+
+ if (queryOptionParser == null)
+ {
+ throw Error.ArgumentNull(nameof(queryOptionParser));
+ }
+
+ Context = context;
+ RawValue = rawValue;
+ _queryOptionParser = queryOptionParser;
+ ResultClrType = Context.ElementClrType;
+ }
+
+ // This constructor is intended for unit testing only.
+ internal ComputeQueryOption(string rawValue, ODataQueryContext context)
+ {
+ if (string.IsNullOrEmpty(rawValue))
+ {
+ throw Error.ArgumentNullOrEmpty(nameof(rawValue));
+ }
+
+ if (context == null)
+ {
+ throw Error.ArgumentNull(nameof(context));
+ }
+
+ Context = context;
+ RawValue = rawValue;
+
+ _queryOptionParser = new ODataQueryOptionParser(
+ context.Model,
+ context.ElementType,
+ context.NavigationSource,
+ new Dictionary { { "$compute", rawValue } },
+ context.RequestContainer);
+ }
+
+ ///
+ /// Gets the given .
+ ///
+ public ODataQueryContext Context { get; }
+
+ ///
+ /// ClrType for result of transformations
+ ///
+ public Type ResultClrType { get; }
+
+ ///
+ /// Gets the parsed for this query option.
+ ///
+ public ComputeClause ComputeClause
+ {
+ get
+ {
+ if (_computeClause == null)
+ {
+ _computeClause = _queryOptionParser.ParseCompute();
+ }
+
+ return _computeClause;
+ }
+ }
+
+ ///
+ /// Gets the raw $compute value.
+ ///
+ public string RawValue { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.OData/Query/Query/ODataRawQueryOptions.cs b/src/Microsoft.AspNetCore.OData/Query/Query/ODataRawQueryOptions.cs
index 8e5b6a434..94225045c 100644
--- a/src/Microsoft.AspNetCore.OData/Query/Query/ODataRawQueryOptions.cs
+++ b/src/Microsoft.AspNetCore.OData/Query/Query/ODataRawQueryOptions.cs
@@ -22,6 +22,11 @@ public class ODataRawQueryOptions
///
public string Apply { get; internal set; }
+ ///
+ /// Gets the raw $compute query value from the incoming request Uri if exists.
+ ///
+ public string Compute { get; internal set; }
+
///
/// Gets the raw $orderby query value from the incoming request Uri if exists.
///
diff --git a/src/Microsoft.AspNetCore.OData/Query/Validator/ODataQueryValidator.cs b/src/Microsoft.AspNetCore.OData/Query/Validator/ODataQueryValidator.cs
index 5d6d81f49..908e0e736 100644
--- a/src/Microsoft.AspNetCore.OData/Query/Validator/ODataQueryValidator.cs
+++ b/src/Microsoft.AspNetCore.OData/Query/Validator/ODataQueryValidator.cs
@@ -34,6 +34,14 @@ public virtual void Validate(ODataQueryOptions options, ODataValidationSettings
}
// Validate each query options
+ if (options.Compute != null)
+ {
+ if (options.Compute.ComputeClause != null)
+ {
+ ValidateQueryOptionAllowed(AllowedQueryOptions.Compute, validationSettings.AllowedQueryOptions);
+ }
+ }
+
if (options.Apply != null)
{
if (options.Apply.ApplyClause != null)
diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl
index ce8c98fc8..042cb4adb 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl
+++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl
@@ -1161,8 +1161,9 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedLogicalOperators : int {
FlagsAttribute(),
]
public enum Microsoft.AspNetCore.OData.Query.AllowedQueryOptions : int {
- All = 2047
+ All = 4095
Apply = 1024
+ Compute = 2048
Count = 64
DeltaToken = 512
Expand = 2
@@ -1173,7 +1174,7 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedQueryOptions : int {
Select = 4
Skip = 32
SkipToken = 256
- Supported = 1535
+ Supported = 3583
Top = 16
}
@@ -1231,6 +1232,15 @@ public class Microsoft.AspNetCore.OData.Query.ApplyQueryOption {
public System.Linq.IQueryable ApplyTo (System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings)
}
+public class Microsoft.AspNetCore.OData.Query.ComputeQueryOption {
+ public ComputeQueryOption (string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser)
+
+ Microsoft.OData.UriParser.ComputeClause ComputeClause { public get; }
+ Microsoft.AspNetCore.OData.Query.ODataQueryContext Context { public get; }
+ string RawValue { public get; }
+ System.Type ResultClrType { public get; }
+}
+
public class Microsoft.AspNetCore.OData.Query.CountQueryOption {
public CountQueryOption (string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser)
@@ -1351,6 +1361,7 @@ public class Microsoft.AspNetCore.OData.Query.ODataQueryOptions {
public ODataQueryOptions (Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.AspNetCore.Http.HttpRequest request)
Microsoft.AspNetCore.OData.Query.ApplyQueryOption Apply { public get; }
+ Microsoft.AspNetCore.OData.Query.ComputeQueryOption Compute { public get; }
Microsoft.AspNetCore.OData.Query.ODataQueryContext Context { public get; }
Microsoft.AspNetCore.OData.Query.CountQueryOption Count { public get; }
Microsoft.AspNetCore.OData.Query.FilterQueryOption Filter { public get; }
@@ -1420,6 +1431,7 @@ public class Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions {
public ODataRawQueryOptions ()
string Apply { public get; }
+ string Compute { public get; }
string Count { public get; }
string DeltaToken { public get; }
string Expand { public get; }
diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl
index ce8c98fc8..042cb4adb 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl
+++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl
@@ -1161,8 +1161,9 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedLogicalOperators : int {
FlagsAttribute(),
]
public enum Microsoft.AspNetCore.OData.Query.AllowedQueryOptions : int {
- All = 2047
+ All = 4095
Apply = 1024
+ Compute = 2048
Count = 64
DeltaToken = 512
Expand = 2
@@ -1173,7 +1174,7 @@ public enum Microsoft.AspNetCore.OData.Query.AllowedQueryOptions : int {
Select = 4
Skip = 32
SkipToken = 256
- Supported = 1535
+ Supported = 3583
Top = 16
}
@@ -1231,6 +1232,15 @@ public class Microsoft.AspNetCore.OData.Query.ApplyQueryOption {
public System.Linq.IQueryable ApplyTo (System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings)
}
+public class Microsoft.AspNetCore.OData.Query.ComputeQueryOption {
+ public ComputeQueryOption (string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser)
+
+ Microsoft.OData.UriParser.ComputeClause ComputeClause { public get; }
+ Microsoft.AspNetCore.OData.Query.ODataQueryContext Context { public get; }
+ string RawValue { public get; }
+ System.Type ResultClrType { public get; }
+}
+
public class Microsoft.AspNetCore.OData.Query.CountQueryOption {
public CountQueryOption (string rawValue, Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.OData.UriParser.ODataQueryOptionParser queryOptionParser)
@@ -1351,6 +1361,7 @@ public class Microsoft.AspNetCore.OData.Query.ODataQueryOptions {
public ODataQueryOptions (Microsoft.AspNetCore.OData.Query.ODataQueryContext context, Microsoft.AspNetCore.Http.HttpRequest request)
Microsoft.AspNetCore.OData.Query.ApplyQueryOption Apply { public get; }
+ Microsoft.AspNetCore.OData.Query.ComputeQueryOption Compute { public get; }
Microsoft.AspNetCore.OData.Query.ODataQueryContext Context { public get; }
Microsoft.AspNetCore.OData.Query.CountQueryOption Count { public get; }
Microsoft.AspNetCore.OData.Query.FilterQueryOption Filter { public get; }
@@ -1420,6 +1431,7 @@ public class Microsoft.AspNetCore.OData.Query.ODataRawQueryOptions {
public ODataRawQueryOptions ()
string Apply { public get; }
+ string Compute { public get; }
string Count { public get; }
string DeltaToken { public get; }
string Expand { public get; }
diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Query/ComputeQueryOptionTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Query/ComputeQueryOptionTests.cs
new file mode 100644
index 000000000..8ea19be07
--- /dev/null
+++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Query/ComputeQueryOptionTests.cs
@@ -0,0 +1,127 @@
+//-----------------------------------------------------------------------------
+//
+// Copyright (c) .NET Foundation and Contributors. All rights reserved.
+// See License.txt in the project root for license information.
+//
+//------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.OData.Query;
+using Microsoft.AspNetCore.OData.TestCommon;
+using Microsoft.AspNetCore.OData.Tests.Commons;
+using Microsoft.OData.Edm;
+using Microsoft.OData.ModelBuilder;
+using Microsoft.OData.UriParser;
+using Xunit;
+
+namespace Microsoft.AspNetCore.OData.Tests.Query
+{
+ public class ComputeQueryOptionTests
+ {
+ private static IEdmModel _model = GetModel();
+
+ [Fact]
+ public void CtorComputeQueryOption_ThrowsArgumentNull_ForInputParameter()
+ {
+ // Arrange & Act & Assert
+ ExceptionAssert.ThrowsArgumentNullOrEmpty(() => new ComputeQueryOption(null, null, null), "rawValue");
+ ExceptionAssert.ThrowsArgumentNullOrEmpty(() => new ComputeQueryOption(string.Empty, null, null), "rawValue");
+
+ // Arrange & Act & Assert
+ ExceptionAssert.ThrowsArgumentNull(() => new ComputeQueryOption("groupby", null, null), "context");
+
+ // Arrange & Act & Assert
+ ODataQueryContext context = new ODataQueryContext(EdmCoreModel.Instance, typeof(int));
+ ExceptionAssert.ThrowsArgumentNull(() => new ComputeQueryOption("groupby", context, null), "queryOptionParser");
+ }
+
+ [Fact]
+ public void CtorComputeQueryOption_CanConstructValidComputeQuery()
+ {
+ // Arrange
+ IEdmModel model = _model;
+ ODataQueryContext context = new ODataQueryContext(model, typeof(ComputeCustomer));
+
+ // Act
+ ComputeQueryOption compute = new ComputeQueryOption("Price mul Qty as Total", context);
+
+ // Assert
+ Assert.Same(context, compute.Context);
+ Assert.Equal("Price mul Qty as Total", compute.RawValue);
+ }
+
+ [Fact]
+ public void CtorComputeQueryOption_GetQueryNodeParsesQuery()
+ {
+ // Arrange
+ IEdmModel model = _model;
+ ODataQueryContext context = new ODataQueryContext(model, typeof(ComputeCustomer)) { RequestContainer = new MockServiceProvider() };
+
+ // Act
+ ComputeQueryOption compute = new ComputeQueryOption("Price mul Qty as Total,Price mul 2.0 as Tax", context);
+ ComputeClause computeClause = compute.ComputeClause;
+
+ // Assert
+ Assert.Equal(2, computeClause.ComputedItems.Count());
+
+ Assert.Collection(computeClause.ComputedItems,
+ e =>
+ {
+ Assert.Equal("Total", e.Alias);
+
+ Assert.Equal(QueryNodeKind.BinaryOperator, e.Expression.Kind);
+ BinaryOperatorNode binaryNode = e.Expression as BinaryOperatorNode;
+ Assert.Equal(BinaryOperatorKind.Multiply, binaryNode.OperatorKind);
+ Assert.Equal(QueryNodeKind.Convert, binaryNode.Right.Kind);
+ ConvertNode convertNode = (ConvertNode)binaryNode.Right;
+ Assert.Equal("Qty", ((SingleValuePropertyAccessNode)convertNode.Source).Property.Name);
+
+ Assert.Equal(QueryNodeKind.SingleValuePropertyAccess, binaryNode.Left.Kind);
+ var propertyAccessNode = binaryNode.Left as SingleValuePropertyAccessNode;
+ Assert.Equal("Price", propertyAccessNode.Property.Name);
+ },
+ e =>
+ {
+ Assert.Equal("Tax", e.Alias);
+
+ Assert.Equal(QueryNodeKind.BinaryOperator, e.Expression.Kind);
+ BinaryOperatorNode binaryNode = e.Expression as BinaryOperatorNode;
+ Assert.Equal(BinaryOperatorKind.Multiply, binaryNode.OperatorKind);
+ Assert.Equal(QueryNodeKind.Constant, binaryNode.Right.Kind);
+ Assert.Equal(2.0, ((ConstantNode)binaryNode.Right).Value);
+
+ Assert.Equal(QueryNodeKind.SingleValuePropertyAccess, binaryNode.Left.Kind);
+ var propertyAccessNode = binaryNode.Left as SingleValuePropertyAccessNode;
+ Assert.Equal("Price", propertyAccessNode.Property.Name);
+ });
+ }
+
+ private static IEdmModel GetModel()
+ {
+ ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
+ builder.EntitySet("Customers");
+ return builder.GetEdmModel();
+ }
+ }
+
+ public class ComputeCustomer
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public int Age { get; set; }
+
+ public double Price { get; set; }
+
+ public int Qty { get; set; }
+
+ public IDictionary Dynamics { get; set; }
+ }
+
+ public class ComputeAddress
+ {
+ public string Street { get; set; }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Validator/ODataQueryValidatorTest.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Validator/ODataQueryValidatorTest.cs
index f5b7ba833..b78ee8920 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/Query/Validator/ODataQueryValidatorTest.cs
+++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Validator/ODataQueryValidatorTest.cs
@@ -8,14 +8,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Net.Http;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Query.Validator;
using Microsoft.AspNetCore.OData.TestCommon;
using Microsoft.AspNetCore.OData.Tests.Commons;
using Microsoft.AspNetCore.OData.Tests.Extensions;
using Microsoft.OData;
-using Microsoft.OData.ModelBuilder.Config;
using Moq;
using Xunit;
@@ -47,6 +45,7 @@ public static TheoryDataSet SupportedQueryO
{ AllowedQueryOptions.Skip, "$skip=5", "Skip" },
{ AllowedQueryOptions.Top, "$top=10", "Top" },
{ AllowedQueryOptions.Apply, "$apply=groupby((Name))", "Apply" },
+ { AllowedQueryOptions.Compute, "$compute=AmountSpent mul 2 as DoubleAmount", "Compute" },
{ AllowedQueryOptions.SkipToken, "$skiptoken=__skip__", "SkipToken" },
};
}