Converting ODataQueryOptions into LINQ Expressions in C#
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
using System; | |
namespace Net.Appclusive.Public.Types | |
{ | |
public abstract class Boxed : IConvertible | |
{ | |
public virtual TypeCode GetTypeCode() | |
{ | |
return TypeCode.Object; | |
} | |
public virtual bool ToBoolean(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual char ToChar(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual sbyte ToSByte(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual byte ToByte(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual short ToInt16(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual ushort ToUInt16(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual int ToInt32(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual uint ToUInt32(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual long ToInt64(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual ulong ToUInt64(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual float ToSingle(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual double ToDouble(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual decimal ToDecimal(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual DateTime ToDateTime(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual string ToString(IFormatProvider provider) | |
{ | |
return null != provider | |
? string.Format(provider, base.ToString()) | |
: base.ToString(); | |
} | |
public abstract object ToType(Type conversionType, IFormatProvider provider); | |
} | |
} |
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
using System; | |
using System.Diagnostics.Contracts; | |
using System.Linq.Expressions; | |
using System.Runtime.CompilerServices; | |
using BoxedType=System.Linq.Expressions.LambdaExpression; | |
namespace Net.Appclusive.Public.Types | |
{ | |
public sealed class BoxedLambdaExpression : Boxed<BoxedType> | |
{ | |
private const int PARAMETER_COUNT = 1; | |
private const int PARAMETER_INDEX = 0; | |
private const string PARAMETER_NAME = "$it"; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static BoxedType Combine(BoxedLambdaExpression expression1, BoxedType expression2, bool isOrElse) | |
{ | |
Contract.Assert(PARAMETER_COUNT == expression1?.Value.Parameters.Count); | |
Contract.Assert(PARAMETER_COUNT == expression2?.Parameters.Count); | |
Contract.Assert(expression1.Value.Parameters[PARAMETER_INDEX].Type == expression2.Parameters[PARAMETER_INDEX].Type); | |
var combinedExpression = isOrElse | |
? Expression.OrElse(expression1.Value.Body, expression2.Body) | |
: Expression.AndAlso(expression1.Value.Body, expression2.Body); | |
var visitor = new MemberExpressionVisitor(Expression.Parameter(expression1.Value.Parameters[PARAMETER_INDEX].Type, PARAMETER_NAME)); | |
var replacedExpression = visitor.Visit(combinedExpression); | |
var lambdaType = typeof(Func<,>).MakeGenericType(visitor.Parameter.Type, expression1.Value.ReturnType); | |
Contract.Assert(null != lambdaType); | |
var lambdaExpression = Expression.Lambda(lambdaType, replacedExpression, visitor.Parameter); | |
return lambdaExpression; | |
} | |
public static implicit operator BoxedType(BoxedLambdaExpression lambdaExpression) | |
{ | |
return lambdaExpression.Value; | |
} | |
public static implicit operator BoxedLambdaExpression(BoxedType boxedExpression) | |
{ | |
return new BoxedLambdaExpression | |
{ | |
Value = boxedExpression | |
}; | |
} | |
public static BoxedType operator +(BoxedLambdaExpression expression1, BoxedType expression2) | |
{ | |
return Combine(expression1, expression2, false); | |
} | |
public static BoxedType operator &(BoxedLambdaExpression expression1, BoxedType expression2) | |
{ | |
return Combine(expression1, expression2, false); | |
} | |
public static BoxedType operator |(BoxedLambdaExpression expression1, BoxedType expression2) | |
{ | |
return Combine(expression1, expression2, true); | |
} | |
public override object ToType(Type conversionType, IFormatProvider provider) | |
{ | |
// DFTODO - determine if and how we should implement a type conversion | |
throw new NotImplementedException(); | |
} | |
private class MemberExpressionVisitor : ExpressionVisitor | |
{ | |
public readonly ParameterExpression Parameter; | |
public MemberExpressionVisitor(ParameterExpression parameter) | |
{ | |
Contract.Requires(null != parameter); | |
Parameter = parameter; | |
} | |
protected override Expression VisitMember(MemberExpression node) | |
{ | |
return Expression.Property | |
( | |
Parameter, | |
node.Member.Name | |
); | |
} | |
} | |
} | |
} |
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
namespace Net.Appclusive.Public.Types | |
{ | |
public abstract class Boxed<T> : Boxed | |
{ | |
public T Value { get; set; } | |
} | |
} |
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
/* | |
* OData-WebAPI | |
* | |
* Copyright (c) Microsoft. All rights reserved. | |
* | |
* Material in this repository is made available under the following terms: | |
* 1. Code is licensed under the MIT license, reproduced below. | |
* 2. Documentation is licensed under the Creative Commons Attribution 3.0 United States (Unported) License. | |
* The text of the license can be found here: http://creativecommons.org/licenses/by/3.0/legalcode | |
* | |
* The MIT License (MIT) | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | |
* associated documentation files (the "Software"), to deal in the Software without restriction, | |
* including without limitation the rights to use, copy, modify, merge, publish, distribute, | |
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all copies or substantial | |
* portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | |
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES | |
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
namespace Net.Appclusive.Core.Linq | |
{ | |
public class ExpressionHelperMethods | |
{ | |
public static MethodInfo QueryableOrderByGeneric { get; } = GenericMethodOf(e_ => default(IQueryable<int>).OrderBy(default(Expression<Func<int, int>>))); | |
public static MethodInfo QueryableOrderByDescendingGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).OrderByDescending(default(Expression<Func<int, int>>))); | |
public static MethodInfo QueryableThenByGeneric { get; } = GenericMethodOf(_ => default(IOrderedQueryable<int>).ThenBy(default(Expression<Func<int, int>>))); | |
public static MethodInfo QueryableThenByDescendingGeneric { get; } = GenericMethodOf(_ => default(IOrderedQueryable<int>).ThenByDescending(default(Expression<Func<int, int>>))); | |
public static MethodInfo QueryableCountGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).LongCount()); | |
public static MethodInfo QueryableTakeGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).Take(0)); | |
public static MethodInfo EnumerableTakeGeneric { get; } = GenericMethodOf(_ => default(IEnumerable<int>).Take(0)); | |
public static MethodInfo QueryableSkipGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).Skip(0)); | |
public static MethodInfo QueryableWhereGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).Where(default(Expression<Func<int, bool>>))); | |
public static MethodInfo QueryableSelectGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).Select(i => i)); | |
public static MethodInfo EnumerableSelectGeneric { get; } = GenericMethodOf(_ => default(IEnumerable<int>).Select(i => i)); | |
public static MethodInfo QueryableEmptyAnyGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).Any()); | |
public static MethodInfo QueryableNonEmptyAnyGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).Any(default(Expression<Func<int, bool>>))); | |
public static MethodInfo QueryableAllGeneric { get; } = GenericMethodOf(_ => default(IQueryable<int>).All(default(Expression<Func<int, bool>>))); | |
public static MethodInfo EnumerableEmptyAnyGeneric { get; } = GenericMethodOf(_ => default(IEnumerable<int>).Any()); | |
public static MethodInfo EnumerableNonEmptyAnyGeneric { get; } = GenericMethodOf(_ => default(IEnumerable<int>).Any(default(Func<int, bool>))); | |
public static MethodInfo EnumerableAllGeneric { get; } = GenericMethodOf(_ => default(IEnumerable<int>).All(default(Func<int, bool>))); | |
public static MethodInfo EnumerableOfType { get; } = GenericMethodOf(_ => default(IEnumerable).OfType<int>()); | |
public static MethodInfo QueryableOfType { get; } = GenericMethodOf(_ => default(IQueryable).OfType<int>()); | |
private static MethodInfo GenericMethodOf<TReturn>(Expression<Func<object, TReturn>> expression) => GenericMethodOf((Expression)expression); | |
// ReSharper disable PossibleNullReferenceException | |
private static MethodInfo GenericMethodOf(Expression expression) => ((expression as LambdaExpression).Body as MethodCallExpression).Method.GetGenericMethodDefinition(); | |
// ReSharper restore PossibleNullReferenceException | |
} | |
} |
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
/* | |
* OData-WebAPI | |
* | |
* Copyright (c) Microsoft. All rights reserved. | |
* | |
* Material in this repository is made available under the following terms: | |
* 1. Code is licensed under the MIT license, reproduced below. | |
* 2. Documentation is licensed under the Creative Commons Attribution 3.0 United States (Unported) License. | |
* The text of the license can be found here: http://creativecommons.org/licenses/by/3.0/legalcode | |
* | |
* The MIT License (MIT) | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | |
* associated documentation files (the "Software"), to deal in the Software without restriction, | |
* including without limitation the rights to use, copy, modify, merge, publish, distribute, | |
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all copies or substantial | |
* portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | |
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES | |
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
using System.Linq; | |
using System.Linq.Expressions; | |
using Net.Appclusive.Internal.Domain; | |
namespace Net.Appclusive.Core.Linq | |
{ | |
public static class ExpressionHelpers | |
{ | |
public static IQueryable<TDataEntity> Where<TDataEntity>(IQueryable source, Expression expression) | |
where TDataEntity : DataEntity | |
{ | |
return ExpressionHelperMethods | |
.QueryableWhereGeneric | |
.MakeGenericMethod(typeof(TDataEntity)) | |
.Invoke | |
( | |
null, | |
new object[] | |
{ | |
source, | |
expression | |
} | |
) as IQueryable<TDataEntity>; | |
} | |
public static IQueryable<TDataEntity> OrderBy<TDataEntity>(IQueryable source, LambdaExpression expression, bool descending, bool alreadyOrdered = false) | |
where TDataEntity : DataEntity | |
{ | |
var bodyType = expression.Body.Type; | |
IOrderedQueryable orderedQueryable; | |
if (alreadyOrdered) | |
{ | |
var methodInfo = !descending | |
? ExpressionHelperMethods.QueryableThenByGeneric.MakeGenericMethod(typeof(TDataEntity), bodyType) | |
: ExpressionHelperMethods.QueryableThenByDescendingGeneric.MakeGenericMethod(typeof(TDataEntity), bodyType); | |
var orderedQueryable2 = source as IOrderedQueryable; | |
orderedQueryable = methodInfo.Invoke(null, new[] | |
{ | |
orderedQueryable2, | |
(object) expression | |
}) as IOrderedQueryable; | |
} | |
else | |
{ | |
var methodInfo = !descending | |
? ExpressionHelperMethods.QueryableOrderByGeneric.MakeGenericMethod(typeof(TDataEntity), bodyType) | |
: ExpressionHelperMethods.QueryableOrderByDescendingGeneric.MakeGenericMethod(typeof(TDataEntity), bodyType); | |
orderedQueryable = methodInfo.Invoke(null, new[] | |
{ | |
source, | |
(object) expression | |
}) as IOrderedQueryable; | |
} | |
return orderedQueryable as IQueryable<TDataEntity>; | |
} | |
} | |
} |
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics.Contracts; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Runtime.CompilerServices; | |
using Net.Appclusive.Internal.Domain; | |
using Net.Appclusive.Public.Domain; | |
using Net.Appclusive.Public.Linq; | |
namespace Net.Appclusive.Core.Domain | |
{ | |
public class QueryOptions | |
{ | |
public static QueryOptions Default => new QueryOptions(); | |
public int SkipCount { get; set; } | |
public int TopCount { get; set; } | |
public ICollection<string> Expands { get; set; } | |
public Expression FilterExpression { get; set; } | |
public Expression OrderByExpression { get; set; } | |
public bool IsDescending { get; set; } | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private QueryOptions TransformInternal<TPublicEntity, TDataEntity>() | |
where TPublicEntity : PublicEntity | |
where TDataEntity : class | |
{ | |
var replacer = new MemberExpressionVisitor<TPublicEntity>(Expression.Parameter(typeof(TDataEntity), "$it")); | |
// convert Filter clause | |
if (null != FilterExpression && ExpressionType.Lambda != FilterExpression.NodeType) | |
{ | |
var methodCallExpressionFilter = FilterExpression as MethodCallExpression; | |
Contract.Assert(null != methodCallExpressionFilter); | |
Contract.Assert(2 == methodCallExpressionFilter.Arguments.Count); | |
var unquotedLambdaExpressionFilter = replacer.Visit(methodCallExpressionFilter.Arguments[1]) | |
.Unquote() as LambdaExpression; | |
Contract.Assert(null != unquotedLambdaExpressionFilter); | |
var expressionLambda = Expression.Lambda<Func<TDataEntity, bool>>(unquotedLambdaExpressionFilter.Body, replacer.Parameter); | |
FilterExpression = expressionLambda; | |
} | |
// convert OrderBy clause | |
if (null != OrderByExpression && ExpressionType.Lambda != OrderByExpression.NodeType) | |
{ | |
var methodCallExpressionOrderBy = OrderByExpression as MethodCallExpression; | |
Contract.Assert(null != methodCallExpressionOrderBy); | |
Contract.Assert(2 == methodCallExpressionOrderBy.Arguments.Count); | |
var unquotedLambdaExpressionOrderBy = replacer.Visit(methodCallExpressionOrderBy.Arguments[1]) | |
.Unquote() as LambdaExpression; | |
Contract.Assert(null != unquotedLambdaExpressionOrderBy); | |
IsDescending = nameof(Enumerable.OrderBy) != methodCallExpressionOrderBy.Method.Name; | |
var lambdaType = typeof(Func<,>).MakeGenericType(typeof(TDataEntity), unquotedLambdaExpressionOrderBy.ReturnType); | |
Contract.Assert(null != lambdaType); | |
var expressionLambdaOrderBy = Expression.Lambda(lambdaType, unquotedLambdaExpressionOrderBy.Body, replacer.Parameter); | |
OrderByExpression = expressionLambdaOrderBy; | |
} | |
return this; | |
} | |
public QueryOptions Transform<TPublicEntity, TDataEntity>() | |
where TPublicEntity : PublicEntity | |
where TDataEntity : DataEntity | |
{ | |
return TransformInternal<TPublicEntity, TDataEntity>(); | |
} | |
public QueryOptions Transform<TPublicEntity>() | |
where TPublicEntity : PublicEntity | |
{ | |
return TransformInternal<TPublicEntity, TPublicEntity>(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment