-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Translate COALESCE as ISNULL #34171
base: main
Are you sure you want to change the base?
Translate COALESCE as ISNULL #34171
Conversation
At least a test for the type propagation is needed |
Another application for this is that COALESCE cannot be used for Indexed Views in SQL Server, however ISNULL is supported in case of aggregations. This makes it difficult/impossible to make EF Core generated queries use the indexed views as I suppose the query processor thinks they're different. For example:
|
@ranma42, gentle nudge on this. We like the |
@cincuranet I will rebase the PR and check the new test results ASAP (worst case: next weekend) Last time I kind of got stuck because of some typing issues as mentioned in #34171 (comment) I will try to point out explicitly the issues that must be solved (ideally as questions) to unblock this |
public virtual Task Coalesce_Correct_Type(bool async) | ||
=> AssertQuery( | ||
async, | ||
ss => ss.Set<Customer>().Select(c => c.Region ?? "no region specified")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is designed to ensure that EFCore takes care of the difference in type propagation between ISNULL
and COALESCE
.
From the docs:
Data type determination of the resulting expression is different. ISNULL uses the data type of the first parameter, COALESCE follows the CASE expression rules and returns the data type of value with the highest precedence.
Specifically, in this case c.Region
is an nvarchar(15)
, while "no region specified"
cannot be represented as a value of that type (it's longer than 15).
The implementation I did in this PR would take care of this, under the assumption that the typeMapping
for CONVERT
can be used to determine the type (and/or whether any casting/conversion is needed), but unfortunately EFCore currently computes it as nvarchar(15)
even though (I believe) its result is actually an nvarchar(18)
.
See https://dbfiddle.uk/ITGS6ZVJ
DECLARE @what sql_variant;
DECLARE @foo varchar(15) = NULL;
SELECT @what = ISNULL(@foo, 'no region selected');
SELECT
@what,
SQL_VARIANT_PROPERTY(@what, 'BaseType'),
SQL_VARIANT_PROPERTY(@what, 'MaxLength');
SELECT @what = COALESCE(@foo, 'no region selected');
SELECT
@what,
SQL_VARIANT_PROPERTY(@what, 'BaseType'),
SQL_VARIANT_PROPERTY(@what, 'MaxLength');
What is the best way forward for the type issue? I see that the type inference already handles string concatenation in a special way, but it looks like nothing at all happens for other values/operations ( NB: I am not sure I actually grasp what typeMappings are supposed to represent (hence how they should be computed/used), so maybe I am simply misusing it in this PR 😇 |
Are you talking about the
Likely just SQL Server, given the
It should result in double, I believe. |
Yes. More in general, I am referring to the mismatch between the
I am unsure whether the expression/comparison behavior is intentional (it is not mentioned as a limitation of value conversion); I'll prepare an issue to showcase the behavior I am seeing and understand if I am simply misreading the docs or if the query conversion has some problems in this corner case. In this case I am not looking for a representation of the C#/SQL conversion, but rather the (SQL) type of the efcore/src/EFCore.Relational/Query/SqlExpressionFactory.cs Lines 252 to 265 in ac5bd7b
If
👍 other providers might want to use a similar type propagation mechanism, but as long as it is only used in the SqlServer provider, we can look for a reasonable API/implementation in there; I'll move along this direction
This would be my expectation as well; I'll double check (actually, I'll add a test), but IIRC it currently results in |
In most cases this is not actually a problem, as the compiler inserts casts to ensure that the type of |
`COALESCE`is a syntactic shortcut for the CASE expression. As such, the input values are evaluated multiple times. `ISNULL` does not have this shortcoming. Fixes dotnet#32519.
COALESCE
is a syntactic shortcut for the CASE expression. As such, the input values are evaluated multiple times.ISNULL
does not have this shortcoming.Fixes #32519.