1

I have an application that creates WPF Image controls on-the-fly to display images from a SQL Server database table. The panels that display the controls get replaced frequently. I use MemoryStreams to allow the controls to access the byte arrays loaded from the database. I cannot close these streams as long as the Image controls are displayed and still have the images displayed. My concern is that these streams will be left open until the Garbage Collector gets around to disposing of the Image controls and hopefully closing the streams too. I will probably end up just making sure that I close the streams or dispose of the Image controls before the panels are replaced but would like to know if there is an issue with leaving these streams open and if there is a way around this.

Here's the code that I am using.

public class ImageToSql
{
    private static OpenFileDialog _openFileDialog = new OpenFileDialog();

    static ImageToSql()
    {
        _openFileDialog = new OpenFileDialog();
        _openFileDialog.AddExtension = true;
        _openFileDialog.CheckFileExists = true;
        _openFileDialog.DefaultExt = ".jpg";
        _openFileDialog.Filter = "JPEG files (*.jpg)|*.jpg|All files (*.*)|*.*";
        _openFileDialog.FilterIndex = 1;
        _openFileDialog.Multiselect = false;
    }

    public static BitmapImage LoadImageFromFile(string caption)
    {
        BitmapImage bi = new BitmapImage();

        _openFileDialog.Title = caption;
        if (_openFileDialog.ShowDialog() == true)
        {
            bi.BeginInit();
            bi.StreamSource = File.OpenRead(_openFileDialog.FileName);
            bi.EndInit();
        }

        return bi;
    }

    public static int StoreImage(string connectionString, string sql, string imageParameterName, BitmapImage bitmapImage)
    {
        SqlConnection conn = new SqlConnection(connectionString);
        try
        {
            conn.Open();
            sql += ";SELECT @@IDENTITY";
            using (SqlCommand cmd = new SqlCommand(sql, conn))
            {
                byte[] data = new byte[bitmapImage.StreamSource.Length];
                bitmapImage.StreamSource.Seek(0, SeekOrigin.Begin);
                bitmapImage.StreamSource.Read(data, 0, data.Length);

                cmd.Parameters.Add(imageParameterName, System.Data.SqlDbType.VarBinary, -1).Value = data;
                object obj = cmd.ExecuteScalar();
                return Int32.Parse(obj.ToString());
            }
        }
        finally
        {
            conn.Close();                
        }
    }

    public static BitmapImage RetrieveImage(string connectionString, string sql, string imageIDParameterName, int imageID)
    {
        BitmapImage bi = new BitmapImage();
        SqlConnection conn = new SqlConnection(connectionString);
        try
        {
            conn.Open();
            using (SqlCommand cmd = new SqlCommand(sql, conn))
            {
                cmd.Parameters.Add(imageIDParameterName, System.Data.SqlDbType.Int).Value = imageID;
                byte[] data = (byte[])cmd.ExecuteScalar();

                MemoryStream ms = new MemoryStream(data);
                ms.Seek(0, SeekOrigin.Begin);
                bi.BeginInit();
                bi.StreamSource = ms;
                bi.EndInit();
            }
        }
        finally
        {
            conn.Close();
        }

        return bi;
    }
}

Below is how this code is called. "_fileImage" and "_sqlImage" are Image controls on my test Window. At one point, I was using a stream to read the file from disk in "LoadImageFromFile" and think I was able to close that stream while still displaying the image in the Image control. So, MemoryStream seems to be special somehow.

        BitmapImage bi = ImageToSql.LoadImageFromFile("Select Image to Save");
        _fileImage.Source = bi;

        int imageID = ImageToSql.StoreImage(connString,
            "INSERT INTO PV_Image (Description, Image) VALUES ('Some Image', @pImage)",
            "@pImage", bi);

        _sqlImage.Source = ImageToSql.RetrieveImage(connString, 
            "SELECT Image FROM PV_Image WHERE ImageID = @pImageID", 
            "@pImageID", imageID);
1
  • Okay, I probably could have used a "using" on the SqlConnection to close it automatically for me. Old habits die hard. Commented Dec 25, 2015 at 9:21

1 Answer 1

1

You can dispose the memory stream once it is used using the using pattern:

using (Stream stream = new MemoryStream(data))
{
    BitmapImage image = new BitmapImage():
    stream.Position = 0;
    image.BeginInit();
    image.CacheOption = BitmapCacheOption.OnLoad;
    image.StreamSource = stream;
    image.EndInit();
    image.Freeze();
    return image;
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks. So, the trick was that I needed to set the cache option and then freeze the image. I will have to do some research to understand why. For now, I am much more comfortable closing the streams then leaving them open to hopefully be closed by my code or the GC later on.
from msdn:Set the CacheOption to BitmapCacheOption.OnLoad if you wish to close a stream used to create the BitmapImage. The default OnDemand cache option retains access to the stream until the image is needed, and cleanup is handled by the garbage collector.
also: The bitmapImage is a freezable when you freeze it, it becomes immutable and it has performance benefits (no change notification required) and it becomes cross thread accessible. It also seems to prevent certain memory leaks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.