-
Notifications
You must be signed in to change notification settings - Fork 825
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
GetValues() fails if any column contains an infinite timestamp #1324
Comments
GetValues() is a method that requests all columns of the current row. It can either succeed or fail - it can't succeed on some columns but not on others... You have other APIs on NpgsqlDataReader which work on a column-by-column basis - you can call GetValue() on all columns and catch exceptions, etc. If you choose to use GetValues(), then either the whole operation succeeds or the whole operation fails.
What special object? Are you suggesting we invent some "error" object for the sole purpose of putting it in the array populated by GetValues() in case of error? That doesn't make much sense... In modern languages failure is indicated by an exception.
As you say, this would be a very inconsistent API that would be very cumbersome to use. It would be impossible to assume anything about the return value without first checking what's returned.
I really don't see the problem here... For 95% of applications, CLR DateTime is a good default mapping - it is intuitive, covers most use cases (infinity and values outside of DateTime's range are probably rare), and is portable with regards to other databases. If your application needs values not covered by DateTime (e.g. infinity), just use NpgsqlDateTime by calling GetProviderSpecificValue(), or even better, GetFieldValue. This is a much better option than having an inconsistent API that sometimes returns DateTime and sometimes NpgsqlDateTime. True, you can't use GetValues() to get all values at once, but you have all the alternatives you need. On a side note, GetValues(), GetValue() and GetProviderSpecificValue() should be avoided in many cases anyway, since they box your values (as opposed to GetFieldValue). Finally, you can also use the "Convert Infinity DateTime" connection string parameter to make Npgsql return infinity as DateTime.MaxValue. I think this is a pretty bad idea but it's available for backwards compatibility. I'm going to close this as I don't see a problem, but we can continue the conversation if you like. |
Hi Roji, a customer of ours ran into the same problem as this issue: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=24814 We read all values for an entity using GetValues() as we do postprocessing on values and store all values in an array anyway so it's the right way to read the values for us. The exception approach is really unfortunate as it stops the user from dealing with the value properly: the exception is hard to process (which column failed?). We implemented a system to deal with this as ODP.NET for oracle has a similar problem where a large decimal value in their database doesn't fit in a System.Decimal value and it simply throws an overflow exception. What I'd like to propose is that GetValues() returns the value in the Npgsql specific type. This doesn't give an exception and gives the possibility for the user to deal with it. While the entity / object itself likely doesn't have a property with that type (but e.g. typed as DateTime), through Type Converters (our variant of what's now also implemented in EF Core as value converters) this can then be converted from the Npgsql specific type to DateTime (with loss of data but c'est la vie) by the user if they want to. Just a thought :) Cheers! |
I certainly can relate to this argument. IMO it falls in the same category we discussed around "If the user doesn't request a certain type we promise to return an object that provides the best compatibility to the type returned by the backend." Throwing an exception for a perfectly valid backend type just because we decided to map it to an incompatible .Net type (no matter if the user really cares) and forcing the consequences upon the user doesn't seem very friendly to me. In many cases it's a simple cast to the corresponding standard .Net type if that's what you want. |
First to answer @Brar, regarding I'll start by saying that returning .NET
It's significant that we're not mapping it to some arbitrary .NET type out there - we're mapping to the standard, built-in way to represent an arbitrary-precision number (i.e.
At least to me it looks like a pretty simple choice :) There's very little actual downsides to having Finally, any change here would obviously a massive breaking change as all current code which expects
Maybe in many, but not all. More importantly, there's a reason that built-in .NET types such as Now regarding @FransBouma's original question on |
This is the solution I didn't know until today 👍 |
I have to admit, I never really understood GetProviderSpecificValues, or better: what it will give me. What I'm after is the situation where I get native .net types in all cases, except when it would create an exception, (like with Oracle NUMBER(10, 38) or here with the time) and in that case return a provider specific value. The method however seems to be it will create provider specific types in all cases, so return a SqlInt32 or whatever and not an int32. So I never looked into this method as it's not useful in any case except perhaps if you have an exception due to a .net type that can't be created and you have to create a provider specific type. We have a mechanism in place for the user to tap into that (the exception happens, they get a call, so they can recover) and I'll suggest the providerspecificvalues method as a solution :) |
I think the idea is to have a predictable type returned when you get get a column's value. I get where you're coming from, though - when you're writing a generic data access layer such as an O/RM, you're in a dynamic situation in the sense that you have no idea what types are coming back, and you don't necessarily care very much. However, when you code directly against ADO.NET you know which type is coming because you coded the SQL query; if you know your specific database values in your specific application cannot be represented by the built-in .NET type, you call On the other hand, if you're in that scenario where you know what type you're expecting, you should actually just call So to summarize, I can see the logic of having |
thanks for the info, yes it's ok now, the providerspecificvalue method is a good 'fallback' option for the situation when the exception occurs. I understand your situation and agree it's something you don't want to change. Boxing the values is our case OK as in this particular fetch pipeline we store the values as an object[] inside the entity (we have to as this is due to backwards compatibility as my former self thought he was wise to store values in field objects instead of typed properties! current self thinks former self was a bit of an idiot thinking that but alas, we can't change things now and have to deal with the boxed values forever now ;)). But indeed, GetFieldValue might be a good option as well. Thanks! |
OK cool :) FYI at some point I went and made sure the entire read path is generic to completely avoid boxing of any value types. There's also #1639 which does the same for writing, although that involves a new non-standard API (generic parameters) which hopefully will make it into ADO.NET one day (https://github.com/dotnet/corefx/issues/8955). FWIW I'd bet that SQL Client and other drivers already box internally, so you may not gain anything on them when switching to |
:) Interestingly |
That's a bit odd... That would mean that In any case, in Npgsql |
Actually GetInt32 uses their internal SqlBuffer class (and returns the Int32 property), the Your approach is indeed better :) |
The following code
currently throws an exception:
I've read #557 and I understand that not converting infinity to DateTime is intentional, but in this case I didn't even ask for the infinity value - maybe I just want the string in the other column? I don't think it makes sense for GetValues to entirely fail just because one column contains a value that cannot be converted. At least you could populate that column with some special object to indicate that that value could not be read, preferably containing the error message.
Alternatively, GetValue() could return an NpgsqlDateTime for an infinite timestamp. Yes, it's inconsistent to do that when it usually returns a DateTime. If the user code doesn't handle it they'll probably get an InvalidCastException, but at least this way it can be handled, whereas currently the InvalidCastException is guaranteed if they do anything other than call GetProviderSpecificValue(). GetValue, GetValues, ExecuteScalar, this[] all fail.
The text was updated successfully, but these errors were encountered: