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

EF Core Changing my Parameter Types (adding precision to decimals and random numbers to floats) #35548

Closed
DasNaughtie opened this issue Jan 29, 2025 · 10 comments

Comments

@DasNaughtie
Copy link

DasNaughtie commented Jan 29, 2025

Question

Can someone explain this behaviour to me please:

I have this dto:

Image

When I execute this code with the Expense set to 0.005 and the shares set to 0.0004:
var result =
_context.Holding.Where(
h =>
(h.ClientRef == holding.ClientRef) &&
(h.SecurityRef == holding.SecurityRef) &&
(!holding.Expense.HasValue || h.Expense == holding.Expense) && -- 0.005
(!holding.Shares.HasValue || h.Shares == holding.Shares) && -- 0.0004
(!holding.AvExpense.HasValue || h.AvExpense == holding.AvExpense) &&
(!holding.Proceeds.HasValue || h.Proceeds == holding.Proceeds) &&
(!holding.Valuation.HasValue || h.Valuation == holding.Valuation) &&
(!holding.Truncation.HasValue || h.Truncation == holding.Truncation) &&
(!holding.Indexation.HasValue || h.Indexation == holding.Indexation) &&
(!holding.QEExpense.HasValue || h.QEExpense == holding.QEExpense) &&
(!holding.QEProceeds.HasValue || h.QEProceeds == holding.QEProceeds) &&
(!holding.Price.HasValue || h.Price == holding.Price));

What I see in the profiler is this:
exec sp_executesql N'SELECT [h].[HoldingRef], [h].[AvExpense], [h].[CGTExpense], [h].[ClientRef], [h].[Expense], [h].[Indexation], [h].[Price], [h].[Proceeds], [h].[QEExpense], [h].[QEProceeds], [h].[SecurityRef], [h].[Shares], [h].[SuspectMarker], [h].[Truncation], [h].[UpdateDate], [h].[Valuation], [h].[ValueDate] FROM [Holding] AS [h] WHERE [h].[ClientRef] = @__holding_ClientRef_0 AND [h].[SecurityRef] = @__holding_SecurityRef_1 AND [h].[Expense] = @__holding_Expense_2 AND [h].[Shares] = @__holding_Shares_3',N'@__holding_ClientRef_0 int,@__holding_SecurityRef_1 int,@__holding_Expense_2 decimal(18,2),@__holding_Shares_3 float',@__holding_ClientRef_0=5755894,@__holding_SecurityRef_1=5734051,@__holding_Expense_2=1,@__holding_Shares_3=0.0004000000000000000191686944095437183932517655

Notice the following
@__holding_Expense_2=1 -- this should be 0.005
@__holding_Shares_3=0.0004000000000000000191686944095437183932517655 -- this should be 0.0004

Why is this happening and how do I stop it from happening? I have not specified '@__holding_Expense_2 decimal(18,2),@__holding_Shares_3 float' anywhere in my code

Your code

Stack traces


Verbose output


EF Core version

8.0.3

Database provider

No response

Target framework

.net 8

Operating system

No response

IDE

No response

@DasNaughtie
Copy link
Author

dotnet/runtime#1643

In summary, Visual Studio doesn't display the truth and you have to use profiler to see that the actual data being sent to the DB looks nothing like the linq statement you're debugging. How can you justify releasing this?

0.005 being converted to 1 - wtf?! So now I can't perform a basic linq statement with a double or a float?

@tannergooding
Copy link
Member

As explained on dotnet/runtime#1643 (comment), this is how double/float (which are IEEE 754 standardized types) work in all languages that provide them. It is not something unique to .NET, EF, or Microsoft. It is a basic part of computing.

You likely have some other issue in your code, such as in how you're initializing Expense which is a decimal, not a double, and for which 0.005m would be exactly representable. There is not enough context to give a more concrete answer as to what the issue is.

@weltkante
Copy link

0.005 being converted to 1

that doesn't seem related to floating point representation and warrants further debugging to see what happens between EF Core and SQL statement generation

@DasNaughtie
Copy link
Author

DasNaughtie commented Jan 29, 2025

As explained on dotnet/runtime#1643 (comment), this is how double/float (which are IEEE 754 standardized types) work in all languages that provide them. It is not something unique to .NET, EF, or Microsoft. It is a basic part of computing.

You likely have some other issue in your code, such as in how you're initializing Expense which is a decimal, not a double, and for which 0.005m would be exactly representable. There is not enough context to give a more concrete answer as to what the issue is.

Apologies...been a long night and day.

I've posted the code and it works in EF6 but doesn't in EF Core. The Holding class is a DTO (also posted above) - I literally call new Holding { Expense = 0.005m, Shares = 0.0004 }.

This is what I see in the debugger:

Image

@DasNaughtie
Copy link
Author

OK, I've debugged the old version and the Shares float behaviour is identical to above...I accept I'm incorrect here and have learnt something new, which I appreciate. There are some other differences to note: EF6 appears to declare the Expense as a decimal(3,3) and has a value of 5 - once again, I don't understand this behaviour but this does return the correct record.

I also checked the SQL that was being generated in EF Core by using .ToQueryString() on the IQueryable but the sql looks fine - the Expense matches what I have in the test (0.005).

@roji
Copy link
Member

roji commented Jan 30, 2025

Closing as there doesn't seem to be any issue with EF here. If I've misunderstood, please post back with a clear EF-specific repro and we can revisit.

@roji roji closed this as not planned Won't fix, can't repro, duplicate, stale Jan 30, 2025
@DasNaughtie
Copy link
Author

DasNaughtie commented Jan 30, 2025

Closing as there doesn't seem to be any issue with EF here. If I've misunderstood, please post back with a clear EF-specific repro and we can revisit.

So the decimal 0.005 being incorrectly represented as 1 isn't an issue? @weltkante Is this normal behaviour? The old version did something difference and works...EF Core doesn't work here. I should be able to select a decimal from the database using the code above as it worked fine before upgrading to .Net 8 and EF Core. Multiple individuals mention there must be something wrong with the code what's wrong with this:

Image

So why can't I retrieve my record from the DB? Why do I have to change code that works perfectly before?

Can anyone explain why this doesn't work in EF Core but works fine in EF6?

@weltkante
Copy link

weltkante commented Jan 30, 2025

Closing as there doesn't seem to be any issue with EF here. If I've misunderstood, please post back with a clear EF-specific repro and we can revisit.

Seems you misunderstood, but I agree, the proper way is to create a repro scenario that you can investigate instead of code-screenshots ;-)

So the decimal 0.005 being incorrectly represented as 1 isn't an issue?

They probably missed that particular detail of the discussion, there was a lot of distraction going on. I already agreed it looks wrong and its probably worth debugging how .NET Core gets from 0.005 to 1 while Desktop Framework doesn't - I'd not expect this conversion, same as you, and I'm not aware of any "obvious" reason why that should happen.

Whether or not "its an issue of EF Core" depends on why its happening and only can be determined once its understood what happens. Currently it looks like the most likely the conversion happens inside EF Core.

The normal approach is to either create a reproduction scenario that can be posted in the issue as code, so the EF Core team can investigate. Posting screenshots and describing your issue in plaintext only helps if you made an "obvious" mistake which someone else already knows what its being caused by. Nobody can really do further investigation on a screenshot.

Alternatively you can continue investigating yourself and look into why this conversions happens. There are multiple ways to "debug into" a library, for example you could try to set breakpoints in EF Core functions (by function name) and look at the stacktrace you break and repeat until you close in on the place it happens, or you could turn off "just my code" in VS options and try stepping through the library. You could also use a third party debugger like dnspy which doesn't distinguish between your own code and library code and allows to debug everything.

Personally I'd suggest creating a repro scenario, thats probably the least amount of work, and more often than not its possible to learn more about the problem while trying to reproduce it, like whether there is additional code that influences triggering the problem.

PS: just giving friendly advice here and am not otherwise involved with the repository or Microsoft. I was subscribed on the Math.Round issue to get notified of updates on the (unrelated) problem they had there, and figured it would make sense to point out that the discussion was losing focus of the actual problem

@roji
Copy link
Member

roji commented Jan 30, 2025

OK, thanks @weltkante. I'll keep this closed until a proper repro is provided, which shows EF doing something wrong.

@DasNaughtie
Copy link
Author

Hi Guys, wanted to provide an update as I think I may have found the issue....

In producing the repro my table definition has a number of Money type fields. In the associated model in the solution we have these mapped to Decimal but these don't have any precision associated to them. Checking the breaking changes in EF you now have to specify the precision of a decimal in configuration.
I recall that the precision in the profiler was incorrect so I introduced a precision to the configuration for that and it worked. Alternatively setting the .HasColumnType("money") against the property in configuration also works.

Thanks for the help/pointers all round...not sure I'd have got to the bottom of this without it.

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

No branches or pull requests

4 participants