16

This error looks like it was caused by installing framework 4.5 on the server even though the project is still targeted to 4.0.

4.5 replaces the CLR and it looks like it has changes in unboxing an object of type tinyint to a int. This was working in 4.0 but not after installing 4.5.

============================================

Please read the whole question before answering, most current answers do not answer the question I am asking.

We today got an error in a cast from a tinyint in sql to an int using

Daterow datarow = GetOneDatarow(connection,
                         "SELECT tinyintcolumn FROM table1 WHERE id = 4");
int i = (int)datarow["tinyintcolumn"];

This is old code that has been in the product for several years without any changes and it has been working up until yesterday. (And it is not the exact code, just enough to show the context)

=== UPDATE

The exact error message was: "The specified cast is not valid!" and the last line

int i = (int)datarow["tinyintcolumn"];

is the exact row from our code casting the error with only variable names and column name changed.

And the database column was tinyint with default value 0, no indexes or other constraints.

=== End update

=== UPDATE 2

Henk Holterman in his response informed me that FW 4.5 replaces the CLR of 4.0 even for projects compiled specifically for 4.0 and this could remotely change existing 4.0 behaviour just like this.

I will keep this open a while more but his answer is the most promising so far :D === End

We changed from framework 3.5 to 4.0 a few weeks ago but it was only yesterday afternoon after a recompile that this happened, yesterday morning the same code (even after recompile) worked like clockwork.

Do anyone have any idea on why this was working before and is not working now?

Has Microsoft made any under the hood changes that removed an implicit conversion or has it worked by pure magic before?

We solved it by changing the database column to int, but I am still curious on what might have caused it to fail right now.

=== UPDATE 3

Just to complete this.

I found the change between frameworks. In an update Microsoft changed how boxing and unboxing is done. This caused the implicit cast from byte to int that older FW did to fail when the byte was boxed, as it is in a datatable.

An unboxed byte will will in 4.5 be implicitly cast to an int, but a boxed byte is an generic object which cannot be implicitly cast.

Not, this was changed in 3.5 SP1 so our FW 4.0 should also have failed, unless the update to SP1 was not in the 4.0 update. This has yet to be answered :)

Here is the ticket from MS on this ;) https://connect.microsoft.com/VisualStudio/feedback/details/766887/casting-tinyint-from-sql-datarow-to-int-no-longer-possible

David Mårtensson
  • 7,550
  • 4
  • 31
  • 47
  • Well , if even after recompile code worked correctly, but broke somehow after, the problem is not framework update. Something changed in DB, **if** the problem is inside this 2 lines actually. – Tigran Oct 10 '12 at 12:33
  • 6
    It normally helps to post the full error message. – RichardTheKiwi Oct 10 '12 at 12:33
  • Echoing @RichardTheKiwi, you need to tell us the error you got. Equally - if it was an `InvalidCastException` my bet, since you haven't said whether it's possible or not, that the row either contained no data, or a `NULL`, or wasn't a boxed `int` – Andras Zoltan Oct 10 '12 at 12:36
  • I don't understand the problem. The error tells you EXACTLY what hte problem is, the cast was not valid, and the reason for the problem is clear. The .NET Framework was not changed, the behavior you experienced likely was more luck then anything, you need to tell us what the data was if you want a more indepth answer. Furthermore the answers you are getting are 100% correct. – Security Hound Oct 10 '12 at 12:54
  • Yes its a column, no expression or conversion in the sql. – David Mårtensson Oct 10 '12 at 12:56
  • @Ramhound the question is how could the code work up until yesterday and suddenly stop working, is there any patch from MS chat changed something or is there anything that could affect how the sqlconnection returns tinyints that I have not thought about. – David Mårtensson Oct 10 '12 at 12:58
  • Its the same, only I and one college has the permissions to change it and even if they did, I would have noticed due to the downtime, restoring from a dump takes over an hour ;) – David Mårtensson Oct 10 '12 at 13:02
  • No, no change in the connectionstring :(, but good call, I would have liked a simple cause like that. – David Mårtensson Oct 10 '12 at 13:24

5 Answers5

28

It should never have worked. Which makes it likely that something was fixed in the framework.

The problem in C#:

byte b = 3;       // TinyInt is a Byte
object o = b;     // DataRow[i] is an object
int i = (int)o;   // invalid cast

The fix:

int i = (byte)datarow["tinyintcolumn"];

And from the comment trail below:

We have installed it, but this project is not compiled towards 4.5, only to 4.0, ... could that be the case?

Yes, framework 4.5 replaces parts of 4.0.

H H
  • 263,252
  • 30
  • 330
  • 514
  • Yes I was going to answer myself with the same thing: you can't cast a boxed `byte` to an `int`. – Andras Zoltan Oct 10 '12 at 12:39
  • I know, but it is code that was working yesterday, and that is why I am at a loss. It should not have worked ever, but did. – David Mårtensson Oct 10 '12 at 12:41
  • Start by double- and triple checking your code revision history. And then check the (auto) updates to the PC. Are you sure _nothing_ was installed/patched ?. – H H Oct 10 '12 at 12:45
  • I am sure, the last time this code was touched was 18 July when we moved the project to a new team foundation server, and we have made 3 releases since then that has been running live, one major and 2 bug fixes, and neither has made any changes to this class and we have made 2 releases since the change to FW4.0. – David Mårtensson Oct 10 '12 at 12:49
  • ...And if it's actually a stored procedure call can you be sure that wasn't modified from having previously done a `CAST(column as int)`? – Andras Zoltan Oct 10 '12 at 12:50
  • Its not a stored procedure but an executeDataTable then fetching the first row of the result then fetching one column and casting it to int. No other code is touching the datarow. – David Mårtensson Oct 10 '12 at 13:00
  • Did you install 4.5 anywhere? It is not SxS with 4.0 – H H Oct 10 '12 at 13:12
  • We have installed it, but this project is not compiled towards 4.5, only to 4.0, but that was one of the possible causes I was contemplating so, could that be the case? – David Mårtensson Oct 10 '12 at 13:25
  • 1
    Yes, 4.5 _replaces_ parts of 4.0. – H H Oct 10 '12 at 13:27
  • That could be it, I think we installed 4.5 yesterday, but we have not compiled towards it yet. – David Mårtensson Oct 10 '12 at 13:33
  • Didn't you think the timing was suspicious? (-: – H H Oct 10 '12 at 13:36
  • Well I thought about it but every thing I have read before has always indicated that the different frameworks lived side by side and would never affect each other. So I assumed that was not the cause. – David Mårtensson Oct 10 '12 at 13:38
6

a TinyInt type will by default return a byte type which itself is castable to an int, but the db's TinyInt type is not, so try this:

(int)(byte)datarow["tinyintcolumn"];
Rob Hardy
  • 1,821
  • 15
  • 15
2

Because tinyint is a Byte type. Here's a list: LIST

You need to convert byte array to int. Solution here: SOLUTION

Community
  • 1
  • 1
Nickon
  • 9,652
  • 12
  • 64
  • 119
  • If you read the whole question you see that we already solved it, I was asking how this could work and then just stop working? – David Mårtensson Oct 10 '12 at 12:40
  • Because it shouldn't be done in this way? Worked or not, it's a bad solution, no matter why:) – Nickon Oct 10 '12 at 12:50
  • @DavidMårtensson - At no point in the original question do you indicate you already fixed the problem. Furthermore your question was already vague to begin with, the updates you did make, just make it down right confusing. – Security Hound Oct 10 '12 at 12:56
  • @Ramhound Last paragraph of the question "We solved it by changing the database column to int, but I am still curious on what might have caused it to fail right now." and it was there from the start ;) – David Mårtensson Oct 10 '12 at 13:05
2

To make the answer usable with other connection libraries I share this. I'm using MariaDb and MySql connector/Net so selected answer did not work for me directly. So first you have to find out the C# datatype of returned sql tinyint field.

Here is example:

I'm using MySqlHelper.ExecuteDataset() and executing query to fetch tinyint(4) column:

SELECT tinyintcolumn FROM datatable WHERE ...

I was getting the "The specified cast is not valid!" exception even if i used cast function in SQL query:

SELECT CAST(tinyintcolumn AS int) ...

I was first trying all suggested here and elsewhere, but finally what worked is finding out what is the type of field in c#:

DataTable datatable MySqlHelper.ExecuteDataset(connString, sql).Tables[0];
DataRow datarow = datatable .Rows[0];
Type datatype = datarow.ItemArray[0].GetType();

Result: System.SByte! So for me it's fine to use one of this:

SByte sbTinyint = datarow.Field<SByte>(0);
SByte sbTinyint2 = (SByte)datarow.ItemArray[0];
int iTinyint = (int)datarow.Field<SByte>(0);
int iTinyint2 = (int)(SByte)datarow.ItemArray[0];
Repeat Spacer
  • 373
  • 3
  • 17
  • 1
    This probably is due to different DatabaseConnections returning different datatypes. In my case the returned data type was boxed(byte) which cannot be implicitly cast to int after 3.5 framework but which was working before due to a bugg ;). In your case you get another data type that probably does not support implicit casting to int so your problem is not exactly the same as mine though similar. – David Mårtensson Oct 27 '15 at 10:16
  • You are right. This Q/A was very helpful while solving the problem. Maybe I just wanted to point this out cause it took me while to realize why the selected answer does not work and simple answer to get it work was to find out what data type mysql connection library is returning. – Repeat Spacer Nov 05 '15 at 11:43
0

I believe the size of SQL's tinyint is 1 byte (8 bits) so try casting it to a 'byte' instead of casting it to an 'int' (which is 32 bits). But I have not tried this since I don't use tinyint in my database.

byte i = (byte)datarow["tinyintcolumn"];

Also, if your column in the database tinyintcolumn is nullable, you also need to take that into account when you cast the value.

Fredrik
  • 2,247
  • 18
  • 21