Introduction
I find that it is quite common for people to have a problem loading an image from a database and recreating it as an Image for display - when they try to create the new Image,
it throws an exception reporting "Parameter is not valid."
So, this explains why it happens, and (hopefully) will save me - and you - some typing in the future.
Background
Storing images in databases is such a handy idea, that everyone picks up on it at some stage. Indeed, it can be a good idea, provided the images are small,
or there aren't too many of them, and provided that you only load them when you actually need them. Loading images from a DB when you don't need them can waste
a lot of bandwidth, and slow your application down a serious amount.
But the problem is that it is also far too easy to get wrong - particularly if you use string
concatenation to assemble your SQL statements - and the error only shows up when you try to use the stored information.
It then looks like a fault in your reader code: "but it can't be - it works! I got it form here!"
Loading images from the DB
The code to read an image from a DB and convert it to a Image for display is pretty easy:
using (SqlConnection con = DBAccess.DBCon)
{
using (SqlCommand cmd = new SqlCommand("SELECT picture FROM Pictures WHERE Id=@ID", con))
{
cmd.Parameters.AddWithValue("@ID", Id);
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
byte[] data = (byte[])reader["Picture"];
using (MemoryStream stream = new MemoryStream(bytes))
{
myImage = new Bitmap(stream);
}
}
}
}
But - if the data is not a valid image for any reason, the line:
myImage = new Bitmap(stream);
will throw an exception: "Parameter is not valid."
It is only if you actually look at the data returned from the database that you realise why - and it's not so obvious when you glance at it in the debugger:
{byte[21]}
[0] 83
[1] 121
[2] 115
[3] 116
[4] 101
[5] 109
[6] 46
[7] 68
[8] 114
[9] 97
[10] 119
[11] 105
[12] 110
[13] 103
[14] 46
...
It doesn't look like anything you recognise, so it could be your data - though the length of 21 bytes is a big clue: is your image likely to be only 21 bytes long?
That's a pretty small image...
But it is readable, with a little practice. Each byte is an ASCII value, in decimal.
"83" is an uppercase 'S'
"121" is an lowercase 'y'
"115" is an lowercase 's'
"116" is an lowercase 't'
"101" is an lowercase 'e'
"109" is an lowercase 'm'
"46" is a '.'
"68" is an uppercase 'D'
"114" is an lowercase 'r'
"97" is an lowercase 'a'
"119" is an lowercase 'w'
"105" is an lowercase 'i'
"110" is an lowercase 'n'
"103" is an lowercase 'g'
"46" is an lowercase '.'
...
In short, the data you read from the database is a human readable string, that says
"System.Drawing.Bitmap"
When the Image class tries to convert it to an actual image, it can't understand it at all, and throws the exception. The code works - it's the data that is the problem.
Saving an image to the database
This is where you caused the problem!
Normally, this is as a result of concatenating strings to form an SQL command:
string sql = "INSERT INTO myTable (imageData) VALUES ('" + myImage + "')";
Does not include the image content in the SQL command - it calls the default Image.ToString
method, which returns the human readable Image type:
"System.Drawing.Bitmap"
So you end up with an SQL command:
INSERT INTO myTable (imageData) VALUES ('System.Drawing.Bitmap')
Which will not cause an error - but it will save the name of the image type as what you think is the Image data itself.
This is yet another reason why you should never concatenate string to form SQL commands. If you want the others, use Google: "Bobby Tables" should do it.
And please - don't think they are joking!
Doing it properly isn't difficult, or even that time consuming, and it can help to improve readability
as well as reliability. All you have to do is use a Parametrized Query.
First, convert the Image to an array of bytes:
Image myImage = Image.FromFile(@"D:\Temp\MyPic.jpg");
byte[] data;
using (MemoryStream ms = new MemoryStream())
{
myImage.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
data = ms.ToArray();
}
Then, use the bytes in your SqlCommand
object:
using (SqlConnection con = new SqlConnection(strConnect))
{
con.Open();
using (SqlCommand com = new SqlCommand("INSERT INTO Pictures (Picture) VALUES (@IM)", con))
{
com.Parameters.AddWithValue("@IM", data);
com.ExecuteNonQuery();
}
}
Before I close...
No, you can't retrieve the image data for the images you already stored wrongly - you will have to delete them all, and save them to the DB correctly!
Yes, yes - I know you knew that. But you would be surprised how many people it comes as a shock to!
History
Original version.