Skip to content

Commit

Permalink
At least partially fix equals comparison in EF Core 3. #3
Browse files Browse the repository at this point in the history
  • Loading branch information
paulirwin committed Dec 4, 2019
1 parent 1e4e4c8 commit bfc5163
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 11 deletions.
2 changes: 1 addition & 1 deletion F23.ODataLite/F23.ODataLite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageTags>odata asp.net core lite</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>2019, feature[23]</Copyright>
<Version>2.0.0-beta1</Version>
<Version>2.0.0-beta2</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
21 changes: 19 additions & 2 deletions F23.ODataLite/Internal/ExpressionQueryTokenVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ namespace F23.ODataLite.Internal
internal class ExpressionQueryTokenVisitor : ISyntacticTreeVisitor<Expression>
{
private static readonly MethodInfo _hasFlagMethod = typeof(Enum).GetMethod(nameof(Enum.HasFlag), BindingFlags.Instance | BindingFlags.Public);
private static readonly MethodInfo _toStringMethod = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public);
private static readonly MethodInfo _fuzzyEqualsMethod = typeof(ExpressionQueryTokenVisitor).GetMethod(nameof(FuzzyEquals), BindingFlags.Static | BindingFlags.NonPublic);

private readonly ParameterExpression _parameter;
private readonly IEnumerable<PropertyInfo> _properties;
private readonly bool _inMemoryEvaluation;

public ExpressionQueryTokenVisitor(ParameterExpression parameterExpression, IEnumerable<PropertyInfo> properties)
public ExpressionQueryTokenVisitor(ParameterExpression parameterExpression, IEnumerable<PropertyInfo> properties, bool inMemoryEvaluation)
{
_parameter = parameterExpression;
_properties = properties;
_inMemoryEvaluation = inMemoryEvaluation;
}

private static bool FuzzyEquals(object left, object right)
Expand Down Expand Up @@ -77,7 +80,21 @@ public Expression Visit(BinaryOperatorToken tokenIn)

private Expression GetEqualsExpression(BinaryOperatorToken tokenIn)
{
return Expression.Call(null, _fuzzyEqualsMethod, Expression.Convert(tokenIn.Left.Accept(this), typeof(object)), Expression.Convert(tokenIn.Right.Accept(this), typeof(object)));
if (_inMemoryEvaluation)
return Expression.Call(null, _fuzzyEqualsMethod, Expression.Convert(tokenIn.Left.Accept(this), typeof(object)), Expression.Convert(tokenIn.Right.Accept(this), typeof(object)));

var left = tokenIn.Left.Accept(this);
var right = tokenIn.Right.Accept(this);

return Expression.Or(
Expression.Equal(left, right),
Expression.And(
Expression.And(
Expression.NotEqual(left, Expression.Constant(null)),
Expression.NotEqual(right, Expression.Constant(null))),
Expression.Equal(Expression.Call(left, _toStringMethod), Expression.Call(right, _toStringMethod))
)
);
}

public Expression Visit(InToken tokenIn)
Expand Down
4 changes: 2 additions & 2 deletions F23.ODataLite/Internal/ODataFilterOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ namespace F23.ODataLite.Internal
{
internal static class ODataFilterOperator
{
public static IQueryable<T> Apply<T>(IQueryable<T> data, IEnumerable<PropertyInfo> properties, string parameter)
public static IQueryable<T> Apply<T>(IQueryable<T> data, IEnumerable<PropertyInfo> properties, string parameter, bool isQueryable)
{
var parser = new UriQueryExpressionParser(10);

var token = parser.ParseFilter(parameter);

var param = Expression.Parameter(typeof(T));

var visitor = new ExpressionQueryTokenVisitor(param, properties);
var visitor = new ExpressionQueryTokenVisitor(param, properties, !isQueryable);

var expr = token.Accept(visitor);

Expand Down
25 changes: 19 additions & 6 deletions F23.ODataLite/ODataLiteAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,23 @@ public override async Task OnResultExecutionAsync(ResultExecutingContext context
return;
}

if (!ok.Value.IsQueryableOrEnumerable(out var rawData)
&& (ok.Value is HypermediaResponse hypermedia && !hypermedia.Content.IsQueryableOrEnumerable(out rawData)))
bool isQueryable = ok.Value.IsQueryable(out var rawData);

if (!isQueryable && !ok.Value.IsEnumerable(out rawData))
{
throw new InvalidOperationException("Data (or HypermediaResponse.Content) must be IQueryable<T> or IEnumerable<T> to use ODataLite. Pass a queryable or enumerable, or remove this attribute.");
if (ok.Value is HypermediaResponse hypermedia)
{
isQueryable = hypermedia.Content.IsQueryable(out rawData);

if (!isQueryable && !hypermedia.Content.IsEnumerable(out rawData))
{
throw new InvalidOperationException("HypermediaResponse.Content must be IQueryable<T> or IEnumerable<T> to use ODataLite. Pass a queryable or enumerable, or remove this attribute.");
}
}
else
{
throw new InvalidOperationException("Data must be IQueryable<T> or IEnumerable<T> to use ODataLite. Pass a queryable or enumerable, or remove this attribute.");
}
}

var itemType = rawData.GetType().GetGenericArguments().First();
Expand All @@ -55,15 +68,15 @@ public override async Task OnResultExecutionAsync(ResultExecutingContext context

applyMethod = applyMethod.MakeGenericMethod(itemType);

if (applyMethod.Invoke(null, new object[] { context, rawData, ok.Value as HypermediaResponse }) is Task<ObjectResult> result)
if (applyMethod.Invoke(null, new object[] { context, rawData, ok.Value as HypermediaResponse, isQueryable }) is Task<ObjectResult> result)
{
context.Result = await result;
}

await base.OnResultExecutionAsync(context, next);
}

private static async Task<ObjectResult> ApplyODataAsync<T>(ActionContext context, IQueryable rawData, HypermediaResponse hypermediaResponse)
private static async Task<ObjectResult> ApplyODataAsync<T>(ActionContext context, IQueryable rawData, HypermediaResponse hypermediaResponse, bool isQueryable)
{
var data = (IQueryable<T>)rawData;

Expand All @@ -74,7 +87,7 @@ private static async Task<ObjectResult> ApplyODataAsync<T>(ActionContext context

if (query.HasParam("$filter", out var filterValue))
{
data = ODataFilterOperator.Apply(data, properties.Value, filterValue);
data = ODataFilterOperator.Apply(data, properties.Value, filterValue, isQueryable);
}

if (query.HasParam("$orderby", out var orderByValue))
Expand Down

0 comments on commit bfc5163

Please sign in to comment.