Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix date() and time() function translation (#792) #1407

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

jamerst
Copy link

@jamerst jamerst commented Feb 5, 2025

Fix #792 - date() function not being translated. Also fixes similar issue of time() function not being translated.

The comments previously implied that the Date and TimeOfDay properties of DateTime and DateTimeOffset were not supported by EF, but this is not the case with EF Core (see function mappings documentation).

The translation has been updated to use these properties for the functions so that they work correctly. Test cases have been added for when they are used in $orderby specifically.

Note that this issue was not present in most circumstances when the date() function was used in $filter due to the type conversion used when dealing with Date types in binary expressions resulting in dates being compared via an integer representation instead:

private static Expression CreateDateBinaryExpression(Expression source, ODataQuerySettings querySettings)
{
source = ConvertToDateTimeRelatedConstExpression(source);
// Year, Month, Day
Expression year = GetProperty(source, ClrCanonicalFunctions.YearFunctionName, querySettings);
Expression month = GetProperty(source, ClrCanonicalFunctions.MonthFunctionName, querySettings);
Expression day = GetProperty(source, ClrCanonicalFunctions.DayFunctionName, querySettings);
// return (year * 10000 + month * 100 + day)
Expression result =
Expression.Add(
Expression.Add(Expression.Multiply(year, Expression.Constant(10000)),
Expression.Multiply(month, Expression.Constant(100))), day);
return CreateFunctionCallWithNullPropagation(result, new[] { source }, querySettings);
}

This won't work with EF6 as it doesn't support translating the Date property. Not sure if this is a problem, does this library even officially support EF6? Attempting to work around it feels somewhat out of scope.

@jamerst
Copy link
Author

jamerst commented Feb 5, 2025

@microsoft-github-policy-service agree

PropertyInfo property = type.GetProperty(nameof(DateTime.Date));

Expression propertyAccessExpr = ExpressionBinderHelper.MakePropertyAccess(property, arguments[0], QuerySettings);
return ExpressionBinderHelper.CreateFunctionCallWithNullPropagation(propertyAccessExpr, arguments, QuerySettings);
Copy link
Contributor

@WanjohiSammy WanjohiSammy Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this fix.

Just to confirm..
Will this change break for users still using older versions of Entity Framework other than EF6? Why not add a condition to ensure it supports older versions of EF.

PropertyInfo property = type.GetProperty(nameof(DateTime.TimeOfDay));

Expression propertyAccessExpr = ExpressionBinderHelper.MakePropertyAccess(property, arguments[0], QuerySettings);
return ExpressionBinderHelper.CreateFunctionCallWithNullPropagation(propertyAccessExpr, arguments, QuerySettings);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here:
Older versions might not support new features from EF6. Adding a condition ensures compatibility.

PropertyInfo property = type.GetProperty(nameof(DateTime.Date));

Expression propertyAccessExpr = ExpressionBinderHelper.MakePropertyAccess(property, arguments[0], context.QuerySettings);
return ExpressionBinderHelper.CreateFunctionCallWithNullPropagation(propertyAccessExpr, arguments, context.QuerySettings);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to tell if the query provider is EF6 from here?

The only place I can find that checks if it is EF6 is in TransformationBinderBase, and it uses the type of IQueryable.Provider to do so.

That would probably work for the translation in ExpressionBinderBase since TransformationBinderBase derives from it, but QueryBinder isn't exposed to the queryable at any point?

internal virtual bool IsClassicEF(IQueryable query)
{
var providerNS = query.Provider.GetType().Namespace;
return (providerNS == HandleNullPropagationOptionHelper.ObjectContextQueryProviderNamespaceEF6
|| providerNS == HandleNullPropagationOptionHelper.EntityFrameworkQueryProviderNamespace);
}

@@ -273,6 +273,18 @@ public static TheoryDataSet<string, string, string> OrderByData

new[] {"$orderby=NullableTimeOfDay", "3 > 1 > 2 > 4 > 5"},
new[] {"$orderby=NullableTimeOfDay desc", "5 > 4 > 2 > 1 > 3"},

new[] {"$orderby=date(SameDayDateTime), Id desc", "5 > 4 > 3 > 2 > 1"},
new[] {"$orderby=date(SameDayNullableDateTime), Id desc", "4 > 2 > 5 > 3 > 1"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using EF here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

date() function is not translated
2 participants