Title: Serialize and deserialize multiple images in C#
This example shows how you can serialize and deserialize multiple images together in a single serialization. It also talks a bit about database design. (For more information about database design including this issue, see my book, Beginning Database Design Solutions.)
Recently someone asked if you could store multiple images in a single record in a database. The answer to that kind of question is usually, "Yes, if you want to badly enough." He eventually found some posts that did it by creating multiple fields in the data table and then storing an image in each.
The basic idea for storing an image in a database is to convert the image into bytes and then store the bytes in some sort of non-specific byte field. In some databases, that kind of field has the Binary Large Object (BLOB) data type. In an Access database, you can use a field with the OLE Object data type. For an example that does this, see the post Save images in an Access database in C#.
From there, it's not too hard to create multiple BLOB or OLE Object fields and use them to store multiple images in each record. One of my rules of thumb for database design, however, is, "There's no such thing as two." The idea is that, if you are asked to add two of the same kind of field to a database table, then what's to prevent your customer from later asking for three? Or four? Or more?
Adding one instance of something (like a headshot) in a table is fine. But if you then decide to add a second picture (perhaps a spouse picture) sends you down a slippery slope. If you include a spouse picture, then why not pictures of your children? Or pets? Or cars? You can certainly add a second picture field, but you'd better be absolutely certain that you won't need to change the number of those fields later.
For example, suppose you use six fields, but then later hire an employee who has a spouse and seven children. Now you need to create a new database design, build the new database, copy your old data into it, and modify any programs that use it. (Or convince your new employee to put three children up for adoption.)
In the following sections, I'll describe two ways that you can handle this issue, the "right" way and a somewhat more interesting way.
The "Right" Way
The officially sanctioned way to handle this issue using classic database design techniques is to create a second detail table and link it to the original table by using a key field. For example, a People table might include a PersonId field. The Pictures table would also include a PersonId field. To find the pictures associated with a particular person, you would search the Pictures table for records with the matching PersonId value. The picture on the right shows the relationship between those two tables.
Now you can add as many pictures as you like to any person's record. This solution also has the distinct advantage that you can add other fields to the Pictures table. For example, the design shown in the diagram includes a Title field where you can indicate the type of picture such as Headshot, Spouse, Child, Pet, or Car. Later you could search the database to find a specific type of picture. For example, you could use the following SQL query to find employees and their headshots.
SELECT FirstName, LastName, Picture
FROM People, Pictures
WHERE People.PersonId = Pictures.PersonId
AND Pictures.PictureType = 'Headshot'
An Interesting Alternative
An interesting alternative is to serialize a record's images and place them in a single BLOB field. The technique of serializing objects is useful in other circumstances, too. For example, it lets you save and restore images (or other data) in files or transmit images across a network.
The example program uses a BinaryFormatter to serialize images into a MemoryStream. The program uses the following using directives to make using those classes easier.
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
When it starts, the program uses the following code to serialize and deserialize three images.
private void Form1_Load(object sender, EventArgs e)
{
// Add the files to a list.
List<Image> input_images = new List<Image>();
input_images.Add((Bitmap)picSource1.Image);
input_images.Add((Bitmap)picSource2.Image);
input_images.Add((Bitmap)picSource3.Image);
// Serialize.
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, input_images);
bytes = ms.ToArray();
}
// Display the serialization bytes.
txtHex.Text = BitConverter.ToString(
bytes, 0).Replace('-', ' ');
txtHex.Select(0, 0);
// Deserialize.
using (MemoryStream ms = new MemoryStream(bytes))
{
BinaryFormatter formattter = new BinaryFormatter();
List<Image> output_images =
(List<Image>)formattter.Deserialize(ms);
picDest1.Image = output_images[0];
picDest2.Image = output_images[1];
picDest3.Image = output_images[2];
}
}
This code first creates a List<Image> and adds the three images (stored in PictureBox controls) to it. It then creates a byte[] to hold the binary serialization.
Next, the code creates a MemoryStream. It makes a BinaryFormatter object and uses its Serialize method to serialize the list of images into the stream. It then calls the stream's ToArray method to convert the bytes that it contains into a byte array. This is the array of bytes that you would store in the database.
The program then uses the BitConverter class to display a textual representation of the serialization, just so you can get an idea that the serialization actually exists. (You can also get an idea of its size.)
Next, the code deserializes the serialized data. To do that, it creates a new MemoryStream associated with the byte array. It creates a new BinaryFormatter and uses its Deserialize method to deserialize the bytes and recreate the list of images. Finally, it displays the images in a new set of PictureBox controls so you can see that they have been recreated correctly.
To Be Continued...
This example shows how to serialize and deserialize a list of images. You could then store the serialization in a database so each record in a table could include any number of images.
That technique has the advantage that it works. You can also use a similar technique to store just about any other kind of data in a database table or file. For example, you could store audio, video, a network, or a hierarchical data structure in database records.
You could even store pieces of data that you didn't not anticipate in a BLOB field without rebuilding the database. For example, if you're working with an existing database that already stores images in a BLOB and you now need to add audio data, you could serialize the pictures together with the audio data. You would need to write a program to re-serialize the data, but you would not need to change the database structure.
HOWEVER, this technique has the huge disadvantage that it doesn't let you search the data stored in the BLOB fields. For example, you could not search for headshot images. For that reason alone I would suggest that you use the "right" way unless the database is already built and you're not allowed to change its design.
In my next post, I'll explain how you can use similar techniques to serialize and deserialize a list of images in a file. To learn a lot more about database design, see my book Beginning Database Design Solutions.
Download the example to experiment with it and to see additional details.
|