diff --git a/RESTier.sln b/RESTier.sln
index 16290cd1..b244d77d 100644
--- a/RESTier.sln
+++ b/RESTier.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.25123.0
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D5E947EB-03CB-4D04-8937-FF2131BB1F04}"
EndProject
diff --git a/src/GlobalSuppressions.cs b/src/GlobalSuppressions.cs
index b790cae3..394d0939 100644
--- a/src/GlobalSuppressions.cs
+++ b/src/GlobalSuppressions.cs
@@ -136,6 +136,9 @@
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#GetEnumerableItemType(System.Type)")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#GetSelectExpandElementType(System.Type)")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#OfType(System.Linq.IQueryable,System.Type)")]
+[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#RemoveUnneededStatement(System.Linq.Expressions.MethodCallExpression)")]
+[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#RemoveSelectExpandStatement(System.Linq.Expressions.MethodCallExpression)")]
+[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#RemoveAppendWhereStatement(System.Linq.Expressions.Expression)")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#Select(System.Linq.IQueryable,System.Linq.Expressions.LambdaExpression)")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#SelectMany(System.Linq.IQueryable,System.Linq.Expressions.LambdaExpression,System.Type)")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#StripQueryMethod(System.Linq.Expressions.Expression,System.String)")]
diff --git a/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs b/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs
index 8343a988..5684be17 100644
--- a/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs
@@ -312,6 +312,15 @@ internal static string QuerySourcerMissing {
}
}
+ ///
+ /// Looks up a localized string similar to The request resource is not found..
+ ///
+ internal static string ResourceNotFound {
+ get {
+ return ResourceManager.GetString("ResourceNotFound", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Should specify an interface type T for the handler..
///
diff --git a/src/Microsoft.Restier.Core/Properties/Resources.resx b/src/Microsoft.Restier.Core/Properties/Resources.resx
index b3d934b1..3b3b7c31 100644
--- a/src/Microsoft.Restier.Core/Properties/Resources.resx
+++ b/src/Microsoft.Restier.Core/Properties/Resources.resx
@@ -213,4 +213,7 @@
{0} is not a supported EDM type.
+
+ The request resource is not found.
+
\ No newline at end of file
diff --git a/src/Microsoft.Restier.Core/Query/DefaultQueryHandler.cs b/src/Microsoft.Restier.Core/Query/DefaultQueryHandler.cs
index af917f40..e415dfa8 100644
--- a/src/Microsoft.Restier.Core/Query/DefaultQueryHandler.cs
+++ b/src/Microsoft.Restier.Core/Query/DefaultQueryHandler.cs
@@ -2,12 +2,14 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.OData.Edm;
+using Microsoft.Restier.Core.Exceptions;
using Microsoft.Restier.Core.Properties;
namespace Microsoft.Restier.Core.Query
@@ -17,6 +19,10 @@ namespace Microsoft.Restier.Core.Query
///
internal static class DefaultQueryHandler
{
+ private const string ExpressionMethodNameOfWhere = "Where";
+ private const string ExpressionMethodNameOfSelect = "Select";
+ private const string ExpressionMethodNameOfSelectMany = "SelectMany";
+
///
/// Asynchronously executes the query flow.
///
@@ -76,6 +82,9 @@ public static async Task QueryAsync(
};
var task = method.Invoke(executor, parameters) as Task;
result = await task;
+
+ await CheckSubExpressionResult(
+ context, cancellationToken, result.Results, visitor, executor, expression);
}
else
{
@@ -98,6 +107,123 @@ public static async Task QueryAsync(
return result;
}
+ private static async Task CheckSubExpressionResult(
+ QueryContext context,
+ CancellationToken cancellationToken,
+ IEnumerable enumerableResult,
+ QueryExpressionVisitor visitor,
+ IQueryExecutor executor,
+ Expression expression)
+ {
+ if (enumerableResult.GetEnumerator().MoveNext())
+ {
+ // If there is some result, will not have additional processing
+ return;
+ }
+
+ var methodCallExpression = expression as MethodCallExpression;
+
+ // This will remove unneeded statement which includes $expand, $select,$top,$skip,$orderby
+ methodCallExpression = methodCallExpression.RemoveUnneededStatement();
+ if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2)
+ {
+ return;
+ }
+
+ if (methodCallExpression.Method.Name == ExpressionMethodNameOfWhere)
+ {
+ // Throw exception if key as last where statement, or remove $filter where statement
+ methodCallExpression = CheckWhereCondition(methodCallExpression);
+ if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2)
+ {
+ return;
+ }
+
+ // Call without $filter where statement and with Key where statement
+ if (methodCallExpression.Method.Name == ExpressionMethodNameOfWhere)
+ {
+ // The last where from $filter is removed and run with key where statement
+ await ExecuteSubExpression(context, cancellationToken, visitor, executor, methodCallExpression);
+ return;
+ }
+ }
+
+ if (methodCallExpression.Method.Name != ExpressionMethodNameOfSelect
+ && methodCallExpression.Method.Name != ExpressionMethodNameOfSelectMany)
+ {
+ // If last statement is not select property, will no further checking
+ return;
+ }
+
+ var subExpression = methodCallExpression.Arguments[0];
+
+ // Remove appended statement like Where(Param_0 => (Param_0.Prop != null)) if there is one
+ subExpression = subExpression.RemoveAppendWhereStatement();
+
+ await ExecuteSubExpression(context, cancellationToken, visitor, executor, subExpression);
+ }
+
+ private static async Task ExecuteSubExpression(
+ QueryContext context,
+ CancellationToken cancellationToken,
+ QueryExpressionVisitor visitor,
+ IQueryExecutor executor,
+ Expression expression)
+ {
+ // get element type
+ Type elementType = null;
+ var queryType = expression.Type.FindGenericType(typeof(IQueryable<>));
+ if (queryType != null)
+ {
+ elementType = queryType.GetGenericArguments()[0];
+ }
+
+ var query = visitor.BaseQuery.Provider.CreateQuery(expression);
+ var method = typeof(IQueryExecutor)
+ .GetMethod("ExecuteQueryAsync")
+ .MakeGenericMethod(elementType);
+ var parameters = new object[]
+ {
+ context, query, cancellationToken
+ };
+ var task = method.Invoke(executor, parameters) as Task;
+ var result = await task;
+
+ var any = result.Results.Cast