5 Replies - 1595 Views - Last Post: 02 June 2011 - 05:34 AM Rate Topic: -----

#1 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Will this load all of the byte[] arrays into memory?

Posted 01 June 2011 - 07:01 PM

Basically, I'm trying to save some information and also attach a byte[] array with each database record.

Here's the table declaration:

create table Student
(
StudentId int primary key identity(1,1),
Name nvarchar(128),
ContractImage varbinary(max)
)



And here's the Windows Forms code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace WinformsPlayground
{
    public partial class Form1 : Form
    {
        OpenFileDialog openFileDialog = new OpenFileDialog();

        public Form1()
        {
            InitializeComponent();
            lstStudents.ValueMember = "StudentId";
            lstStudents.DisplayMember = "Name";
            RefreshStudentsDisplay();
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            SaveStudentInformation();
            RefreshStudentsDisplay();            
        }

        private void RefreshStudentsDisplay()
        {
            using (WinformsPlaygroundDBEntities dbContext = new WinformsPlaygroundDBEntities())
            {
                lstStudents.DataSource = dbContext.Students;
            }
        }

        private void btnBrowseFile_Click(object sender, EventArgs e)
        {
            BrowseForImage();
        }

        private void SaveStudentInformation()
        {
            Student student = new Student();
            student.Name = txtName.Text;
            student.ContractImage = File.ReadAllBytes(openFileDialog.FileName);

            using (WinformsPlaygroundDBEntities dbContext = new WinformsPlaygroundDBEntities())
            {
                dbContext.AddToStudents(student);
                dbContext.SaveChanges();
            }
        }        

        private void BrowseForImage()
        {
            openFileDialog.Title = "Search for an image.";
            openFileDialog.InitialDirectory = Environment.SpecialFolder.DesktopDirectory.ToString();
            openFileDialog.Filter = "JPEG Files (*.jpeg)|*.jpeg|PNG Files (*.png)|*.png|JPG Files (*.jpg)|*.jpg|GIF Files (*.gif)|*.gif";
            openFileDialog.FilterIndex = 0;
            openFileDialog.RestoreDirectory = true;

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                if (File.Exists(openFileDialog.FileName))
                {
                    Image image = Image.FromFile(openFileDialog.FileName);
                    ptbImage.Image = image;
                    txtImagePath.Text = openFileDialog.FileName;
                }
            }
        }

        private void lstStudents_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lstStudents.SelectedIndex != -1)
            {
                Student student = (Student)lstStudents.SelectedItem;
                txtName.Text = student.Name;

                MemoryStream memoryStream = new MemoryStream(student.ContractImage);
                ptbImage.Image = Image.FromStream(memoryStream);
            }
        }
    }
}



My main concern is in the RefreshStudentsDisplay() method, I'm pretty sure (around 80%) that it's also loading the byte[] arrays into memory. This is a major malfunction. I'll be storing big images at around 2MB each so having 60 images like that loaded into memory is unacceptable.

Can someone confirm this is the case? Also how would I assign to .DataSource without the image?

I was thinking something along the lines of this:

lstStudents.DataSource = dbContext.Students.Select(s => new { StudentId = s.StudentId, Name = s.Name });



But I get this error:
Unable to cast object of type '<>f__AnonymousType0`2

private void lstStudents_SelectedIndexChanged(object sender, EventArgs e)
{
    if (lstStudents.SelectedIndex != -1)
    {
        Student student = (Student)lstStudents.SelectedItem;
        txtName.Text = student.Name;

        MemoryStream memoryStream = new MemoryStream(student.ContractImage);
        ptbImage.Image = Image.FromStream(memoryStream);
    }
}



Can you give my code a look over and see if I can improve it? This is just a demo I'm writing up for tonight, tomorrow I actually use it in my production software. Thanks!

This post has been edited by Sergio Tapia: 01 June 2011 - 07:03 PM


Is This A Good Question/Topic? 0
  • +

Replies To: Will this load all of the byte[] arrays into memory?

#2 Curtis Rutland   User is offline

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 5106
  • View blog
  • Posts: 9,283
  • Joined: 08-June 10

Re: Will this load all of the byte[] arrays into memory?

Posted 01 June 2011 - 08:54 PM

First off, yes, it does load the byte arrays into memory. Not when you do the assignment, but when the control is bound, the collection is Enumerated.

Now, in your other solution, the one where you create an anonymous type. You can't cast from that to a Student. They're not compatible types.

So, I'm guessing you just want to load the byte array when the selection changes, and you only want to load the one that's clicked on.

Well, the way I would do it is, instead of using an anonymous type, define a type that matches and use that to select into. Then, in your SelectedIndexChanged event, use the Id from that to load the byte[] from the DB matching that Id. It'll force a DB read each time.

You could also cache each array you load into a Dictionary, keyed on the id, that way you don't have to load twice. The downside of this is that each will remain in memory after it's loaded.

So it's up to you whether or not to cache, but if you don't want to load them all, you're almost on the right track.

The problem with anonymous types is that they're useless out of their original scope, unless you want to use dynamic, which adds its own complications to things.

Let us know what you come up with.
Was This Post Helpful? 1
  • +
  • -

#3 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Re: Will this load all of the byte[] arrays into memory?

Posted 02 June 2011 - 04:34 AM

Ok, thanks for the suggestions. Here's what I've come up with:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace WinformsPlayground
{
    class StudentContractViewModel
    {
        public int StudentId { get; set; }
        public string Name { get; set; }
    }

    public partial class Form1 : Form
    {
        OpenFileDialog openFileDialog = new OpenFileDialog();

        public Form1()
        {
            InitializeComponent();
            lstStudents.ValueMember = "StudentId";
            lstStudents.DisplayMember = "Name";
            RefreshStudentsDisplay();
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            SaveStudentInformation();
            RefreshStudentsDisplay();            
        }

        private void RefreshStudentsDisplay()
        {
            using (WinformsPlaygroundDBEntities dbContext = new WinformsPlaygroundDBEntities())
            {
                lstStudents.DataSource = dbContext.Students.Select(s => new StudentContractViewModel { Name = s.Name, StudentId = s.StudentId });
            }
        }

        private void btnBrowseFile_Click(object sender, EventArgs e)
        {
            BrowseForImage();
        }

        private void SaveStudentInformation()
        {
            Student student = new Student();
            student.Name = txtName.Text;
            student.ContractImage = File.ReadAllBytes(openFileDialog.FileName);

            using (WinformsPlaygroundDBEntities dbContext = new WinformsPlaygroundDBEntities())
            {
                dbContext.AddToStudents(student);
                dbContext.SaveChanges();
            }
        }        

        private void BrowseForImage()
        {
            openFileDialog.Title = "Search for an image.";
            openFileDialog.InitialDirectory = Environment.SpecialFolder.DesktopDirectory.ToString();
            openFileDialog.Filter = "JPEG Files (*.jpeg)|*.jpeg|PNG Files (*.png)|*.png|JPG Files (*.jpg)|*.jpg|GIF Files (*.gif)|*.gif";
            openFileDialog.FilterIndex = 0;
            openFileDialog.RestoreDirectory = true;

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                if (File.Exists(openFileDialog.FileName))
                {
                    Image image = Image.FromFile(openFileDialog.FileName);
                    ptbImage.Image = image;
                    txtImagePath.Text = openFileDialog.FileName;
                }
            }
        }

        private void lstStudents_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lstStudents.SelectedIndex != -1)
            {
                StudentContractViewModel studentModel = (StudentContractViewModel)lstStudents.SelectedItem;
                txtName.Text = studentModel.Name;

                using (WinformsPlaygroundDBEntities dbContext = new WinformsPlaygroundDBEntities())
                {
                    var student = dbContext.Students.SingleOrDefault(s => s.StudentId == studentModel.StudentId);

                    if (student != null)
                    {
                        MemoryStream memoryStream = new MemoryStream(student.ContractImage);
                        ptbImage.Image = Image.FromStream(memoryStream);
                    }                    
                }                
            }
        }
    }    
}



I'm having second thoughts about that usage of MemoryStream. Is that the best way to use it? I want the previous image to be garbage collected and no longer take up memory once the student selection changes. But I'm not that familiar with how memorystreams works. Maybe someone can shed some light on the situation. :)
Was This Post Helpful? 0
  • +
  • -

#4 eclipsed4utoo   User is offline

  • Not Your Ordinary Programmer
  • member icon

Reputation: 1536
  • View blog
  • Posts: 5,972
  • Joined: 21-March 08

Re: Will this load all of the byte[] arrays into memory?

Posted 02 June 2011 - 05:03 AM

Truthfully, you don't need another class, just use the Student class. Just because it has 3 properties doesn't mean those 3 properties are required for every instance.

So your code could be this..

lstStudents.DataSource = dbContext.Students.Select(s => new Student { Name = s.Name, StudentId = s.StudentId });



Then your SelectedIndexChanged code could be this...

private void lstStudents_SelectedIndexChanged(object sender, EventArgs e)
{
	if (lstStudents.SelectedIndex < 0)
		return;
	
	Student selectedStudent = lstStudents.SelectedItem as Student;
	txtName.Text = selectedStudent.Name;
	
	if (selectedStudent.ContractImage == null)
	{
		selectedStudent.GetContractImage();
	}
	
	ptbImage.Image = selectedStudent.ContractImage;
}



You would then move the code to get the image from the database to the Student class instead of having it in the UI.
Was This Post Helpful? 1
  • +
  • -

#5 eclipsed4utoo   User is offline

  • Not Your Ordinary Programmer
  • member icon

Reputation: 1536
  • View blog
  • Posts: 5,972
  • Joined: 21-March 08

Re: Will this load all of the byte[] arrays into memory?

Posted 02 June 2011 - 05:08 AM

As for the MemoryStream code, you should probably put it into a using block. That way, you can make sure it's ready for garbage collection.

One thing to remember, garbage collection doesn't happen immediately. It's completely random. You can't rely on the GC to garbage collected objects at a specific time. The only way to do that is to call the GC methods explicitly, which is rarely the correct thing to do.
Was This Post Helpful? 1
  • +
  • -

#6 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Re: Will this load all of the byte[] arrays into memory?

Posted 02 June 2011 - 05:34 AM

This bit seems so obvious once I read it that I wonder why I didn't think of it. :lol:

lstStudents.DataSource = dbContext.Students.Select(s => new Student { Name = s.Name, StudentId = s.StudentId });



You're right, I would no longer need the extra class I made. :)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1