-
Notifications
You must be signed in to change notification settings - Fork 227
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
Added Greatest, Least and NullIf DbFunctions #2866
Conversation
@cloudlucky these three functions are standard SQL ones, which are supported pretty much across all databases the EF supports. That means that they should be added in EF itself, rather than in the PG provider specifically - I've opened dotnet/efcore#31681 and dotnet/efcore#31682 to track that. Adding support there should be similar to the work you've done. However, I'd be interested in what difficulties you ran into when trying to translate variadic functions (with |
@roji Perfect, I will give it a shot to implement these in EF itself. Also, about all these overloads, I agree with you, it's not great. In fact, my actual implementation is using I currently have this signature where there is one required parameter. public static T Greatest<T>(this DbFunctions _, T value, params T[] values)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Greatest))); The generated SQL wasn't good as the ... GREATEST(value, ARRAY[ values[0], values[1], values[2] ]) ... But what we need is to pass each element of the array as a parameter to the function ... GREATEST(value, values[0], values[1], values[2]) ... To achieve this, I created a method to extract SqlExpression in the Translator. private static IEnumerable<SqlExpression> ConvertParamsToArguments(SqlExpression sqlExpression)
{
if (sqlExpression is SqlConstantExpression sqlConstantExpression)
{
if (sqlConstantExpression.Value is not null && sqlConstantExpression is IEnumerable collection)
{
foreach (SqlExpression item in collection)
{
yield return item;
}
}
}
else if (sqlExpression is PostgresNewArrayExpression newArrayExpression)
{
foreach (var item in newArrayExpression.Expressions)
{
yield return item;
}
}
throw new NotSupportedException("The values (params) parameter was created outside of the Expression which is currently not supported");
} It works well but seems like a bit of a hack to me and not sure that is the best way to do this. Except, it doesn't work for one specific scenario. If you declare an array outside of the Expression and then use it as the var param = new[] { 1 };
var orderDetail = .... EF.Functions.Greatest(1, param) .....; In that specific case, it creates a SqlParameterExpression and I'm not sure what to do. This is why there is a throw NotSupportedException. So, I'm not sure what to do with this. Do you have any advice? |
Yes, handling an array parameter does require some special handling. Note that PostgresNewArrayExpression is PG-specific, and won't be available in the EF relational layer (where Greater/Least should be implemented). Unless I'm mistaken, that means that you also can't implement the translation in a regular MethodTranslator (as you've done in this PR), since the MethodTranslator infrastructure requires all arguments to be translatable, which isn't the case here. In other words, you'll have to perform the translation from RelationalSqlTranslatingExpressionVisitor.VisitMethodCall (which is also the thing that calls MethodTranslators at the bottom), e.g. by adding and calling something like TryTranslateGreatestLeast. At that point you have access to the pre-translation, non-SQL expression and can do what you want.
EF has the If you want, I'd suggest first doing a PR for NullIf in relational - which should be quite simple - and then tackling Greater/Least separately, as these will be more complex. For now, I'll go ahead and close this PG-specific PR - but feel free to post back if you want to discuss further. |
I added 3 new DbFunctions: Greatest, Least and NullIf.
Documentation: https://www.postgresql.org/docs/current/functions-conditional.html
About Greatest and Least functions, I tried with
params
but it didn't work well for all use cases, so I used method overloads.I know that
Math.Max
andMath.Min
generate Greatest and Least respectively but when you need to get the Greatest or Least number with 6 or 7 properties, the code is less readableAlso,
Math.Max
andMath.Min
only works with numbers which was an issue when I needed to get the greatest or least DateTime.