Skip to content

Instantly share code, notes, and snippets.

@dfch
Last active August 28, 2021 22:19
Show Gist options
  • Save dfch/dda3711d6e6d0a6e225614a4e787d330 to your computer and use it in GitHub Desktop.
Save dfch/dda3711d6e6d0a6e225614a4e787d330 to your computer and use it in GitHub Desktop.
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