Press "Enter" to skip to content

Memorizzare immagini in SQL Server

Memorizzare immagini in SQL Server CAT: C#, SQL, WIN Tags:Blob, Csharp, Image, SqlServer DATA: 04/09/2007

In questo articolo spiegheremo come caricare un’immagine che si trova su un file all’interno di un disco del nostro PC all’interno di un database SQL Server e come recuperarla per inserirla su una DataGridView oppure su un controllo PictureBox. In questo articolo troverete riferimenti alle SqlConnection, ai SqlCommand, ai MemoryStream, alle DataTable e alle Images

Introduzione

Lo scopo di questo articolo è illustrare come inserire in una tabella di un database SQL Server una immagine letta da un file su disco e come rileggerla ed utilizzarla all’interno di un nostro programma visualizzandola su una DataGridView oppure su una PictureBox. L’articolo non si occupa di spiegare come creare un database, come creare una tabella, come assegnare all’utente i diritti per utilizzare SQL Server, assume che chi legge sia in grado di farlo e sia in grado di utilizzare SQLServer Management Studio per lanciare gli script qui riportati.

Gli argomenti trattati sono quindi:

  • Leggere un’immagine da File
  • Scrivere un immagine su Database
  • Leggere un immagine da Database
  • Visualizzarla su PictureBox

Codice per la creazione del database SQLServer

USE [master]
GO
/****** Object:  Database [DnwImageInDb]    Script Date: 09/03/2007 19:27:52 ******/
CREATE DATABASE [DnwImageInDb] ON  PRIMARY 
( NAME = N'DnwImageInDb', FILENAME = N'D:\sql.dir\data\DnwImageInDb.mdf' 
, SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'DnwImageInDb_log', FILENAME = N'D:\sql.dir\data\DnwImageInDb_log.ldf' 
, SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO

 

Codice per la creazione della tabella sul database SQLServer

USE [DnwImageInDb]
GO
/****** Object:  Table [dbo].[TbImagesInDb]    Script Date: 09/03/2007 19:23:07 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TbImagesInDb](
    [IDImage] [nvarchar](256) NOT NULL,
    [Image] [image] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

La struttura del programma di esempio

scsql_images_solutionexplorer01

La struttura del progetto di esempio relativo a questo articolo è quella in figura, una soluzione con un progetto Windows Forms in cui sono 2 files:

  1. Program.cs (vb) – contiene il Main dell’applicazione.
  2. FrmMain.cs (vb) – Form per la gestione dell’applicazione.

Nel progetto sono state effettuate le seguenti modifiche:

C#

Properties > Application

Default Namespace = Dnw

Icon = dnw.ico

VB

My Project > Application

Root Namespace = Dnw

Icon = dnw.ico

Enable Application Framework = unchecked

Startup Object = Sub Main

Nelle Resources di entrambi i progetti è stata aggiunta l’icona dnw.ico.

 

 

La classe Program.cs

Il suo compito è solo quello di ospitare il Main del progetto e istanziare la form principale del progetto stesso.

// C#
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Dnw
{
    static class Program
    {
       [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new FrmMain());
        }
    }
}

 

'VB
Public Class Program

    <STAThread> _
    Shared sub Main
            Application.EnableVisualStyles()
            Application.SetCompatibleTextRenderingDefault(false)
            Application.Run(new FrmMain())
    End Sub

End Class

Come possiamo vedere, in questa classe, attiviamo lo stile di Windows XP e istanziamo la form principale del progetto.

La classe FrmMain.cs

Questa classe, svolge tutti i compiti richiesti dal nostro programma, pertanto al suo interno troveremo il necessario a tutte le funzionalità che intendiamo implementare e spiegare.

Le costanti

private const string FMP_ConnectionString = 
	"Data Source={0}; Initial Catalog={1}; Integrated Security=True";
        private const string MyServer = "MyPcName";
        private const string MyDataBase = "DnwImageInDb";

 

Private Const FMP_ConnectionString As String = _
        "Data Source={0}; Initial Catalog={1}; Integrated Security=True" 
    Private Const MyServer As String = "MyPcName" 
    Private Const MyDataBase As String = "DnwImageInDb"

Per prima cosa dichiariamo tre costanti, che contengono la stringa per la creazione della connectionstring a SQL Server che assumiamo sia effettuata utilizzando l’autenticazione via trusted connection (Windows Authentication).

E due stringhe che personalizzerete con il nome del vostro server SQL che nel mio caso avendo io installato SQL Server Developer Edition nell’istanza di default coincide con il nome del mio PC. Mentre per chi avesse installato SQLExpress sarebbe “MyPcName\SqlExpress” o il nome da voi assegnato all’istanza di SqlExpress.

Il costruttore

public FrmMain()
        {
            InitializeComponent();
            this.Icon = Properties.Resources.dnw;
        }

 

Public Sub New() 
        InitializeComponent() 
        Me.Icon = My.Resources.dnw 
    End Sub

Salvo la funzione automatica di inizializzazione dei controlli inseriti a design aggiorniamo solo l’icona della form usando l’icona personalizzata DotNetwork che abbiamo inserito nel programma.

La Form a Design

sc_sql_images_FrmMain01

In questa form inseriamo i seguenti controlli modificandone le property come indicato:

  • Panel1 – Panel
    1. Dock = left
    2. Size = 237; 405 (ovviamente solo la larghezza ha importanza per un pannello docked).
    3. Contiene tutti i controlli salvo la datagridview
  • pcxFile – PictureBox
    1. Location = 51; 12
    2. Size = 138; 123
  • txtImageName – Textbox
    1. Location = 12; 141
    2. Size = 219; 20
  • btnFile – Button
    1. Location = 12; 167
    2. Size = 75; 23
    3. Text = Scegli File
    4. UseVisualStyleBackColor = True
  • btnToDb – Button
    1. Location = 156; 167
    2. Size = 75; 23
    3. Text = Metti in Db
    4. UseVisualStyleBackColor = True
  • pcxDatabase – PictureBox
    1. Location = 51; 215
    2. Size = 138; 123
    3. SizeMode = Zoom
  • dgvDb – DataGridView
    1. AllowUserToAddRows = false
    2. AllowUserToDeleteRows = false
    3. BackgroundColor = Orange
    4. ColumnHeadersHeightSizeMode = AutoSize
    5. Dock = fill
    6. GridColor = Teal
    7. Location = 237; 0
    8. Size = 447; 405

I Campi della form

/// <summary>
        /// Connessione
        /// </summary>
        SqlConnection mCn;

        /// <summary>
        /// Commnad
        /// </summary>
        SqlCommand mCmd;

        /// <summary>
        /// Openfiledialog x la ricerca immagini
        /// </summary>
        OpenFileDialog ofd;

        /// <summary>
        /// Datatable x lista datagrid
        /// </summary>
        DataTable mDt;

        /// <summary>
        /// Formato dell'immagine
        /// </summary>
        ImageFormat mCurrentFormat;

 

''' <summary> 
    ''' Connessione 
    ''' </summary> 
    Dim mCn As SqlConnection 

    ''' <summary> 
    ''' Commnad 
    ''' </summary> 
    Dim mCmd As SqlCommand 

    ''' <summary> 
    ''' Openfiledialog x la ricerca immagini 
    ''' </summary> 
    Dim ofd As OpenFileDialog 

    ''' <summary> 
    ''' Datatable x lista datagrid 
    ''' </summary> 
    Dim mDt As DataTable 

    ''' <summary> 
    ''' Formato dell'immagine 
    ''' </summary> 
    Dim mCurrentFormat As ImageFormat

Le variabili a livello di form, conterranno la SqlConnection per la connessione a database, il SqlCommand usato per accedere al database, la OpenFileDialog usata per accedere alle immagini su disco, la DataTable che conterrà la lista delle immagini lette da database, l’ImageFormat per il formato dell’immagine corrente per la corretta visualizzazione.

L’event handler FormLoad

private void FrmMain_Load(object sender, EventArgs e)
        {
            try
            {

                mCn = new SqlConnection();
                mCn.ConnectionString = string.Format(FMP_ConnectionString, MyServer, MyDataBase);
                mCmd = new SqlCommand();
                ofd = new OpenFileDialog();
                ofd.Multiselect = false;
                LoadDatagrid();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

 

Private Sub FrmMain_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load 
        Try 
            
            mCn = New SqlConnection() 
            mCn.ConnectionString = String.Format(FMP_ConnectionString, MyServer, MyDataBase) 
            mCmd = New SqlCommand() 
            ofd = New OpenFileDialog() 
            ofd.Multiselect = False 
            LoadDatagrid() 
        Catch ex As Exception 
            MessageBox.Show(ex.Message) 
        End Try 
    End Sub

Questo event handler, compone la connection string, genera la connessione e il command per interrogare il database, genera e predispone la OpenFileDialog per selezionare le immagini e carica la datagrid con le immagini eventualmente già caricate sul database.

L’event handler Click di btnFile

private void btnFile_Click(object sender, EventArgs e)
        {
            try
            {

                ofd.Title = "Seleziona un file immagine";
                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    this.pcxFile.Image = Image.FromFile(ofd.FileName);
                    FileInfo info = new FileInfo(ofd.FileName);
                    this.txtImageName.Text = info.Name;
                    if (info.Extension.ToLower() == ".gif")
                    {
                        mCurrentFormat = ImageFormat.Gif;
                    }
                    else
                    {
                        mCurrentFormat = ImageFormat.Jpeg;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

 

Private Sub btnFile_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnFile.Click 
        Try 
            
            ofd.Title = "Seleziona un file immagine" 
            If ofd.ShowDialog() = DialogResult.OK Then 
                Me.pcxFile.Image = Image.FromFile(ofd.FileName) 
                Dim info As New FileInfo(ofd.FileName) 
                Me.txtImageName.Text = info.Name 
                If info.Extension.ToLower() = ".gif" Then 
                    mCurrentFormat = ImageFormat.Gif 
                Else 
                    mCurrentFormat = ImageFormat.Jpeg 
                End If 
            End If 
        Catch ex As Exception 
            MessageBox.Show(ex.Message) 
        End Try 
    End Sub

Questo event handler, al click del bottone di ricerca file immagini, apre la dialog per la ricerca dei files e permette di leggere immagini di tutti i tipi, ma se non sono GIF vengono tutte convertite in JPG, attenzione questo è un limite voluto, ma potete caricare qualsiasi tipo di immagine nel vostro database allo stesso modo.

Una volta che è stata selezionata un’immagine, viene memorizzato il tipo di immagine e viene aggiornata la picturebox pcxFile per mostrare com’è fatta l’immagine. In Questo caso, la picture box mostra l’immagine esattamente come è fatta, non la modifica per adattarla alle dimensioni della picturebox. Potete variare questo comportamento usando la proprietà SizeMode come abbiamo fatto nella seconda PictureBox.

L’event handler click di btnToDb

private void btnToDb_Click(object sender, EventArgs e)
        {
            try
            {

                byte[] imageForDb = null;
                using (MemoryStream ms = new MemoryStream())
                {
                    ((Bitmap)pcxFile.Image).Save(ms, mCurrentFormat);
                    imageForDb = ms.ToArray();
                }
                SqlParameter[] param = new SqlParameter[] {
                new SqlParameter( "@IDImage", this.txtImageName.Text),
                new SqlParameter( "@Image", imageForDb )     };
                mCmd.CommandText = "INSERT INTO TbImagesInDb( IDImage, Image) Values( @IDImage, @Image )";
                mCmd.Parameters.AddRange(param);
                mCn.Open();
                mCmd.Connection = mCn;
                mCmd.ExecuteNonQuery();
                mCn.Close();

                LoadDatagrid();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

 

Private Sub btnToDb_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnToDb.Click 
        Try 
            
            Dim imageForDb As Byte() = Nothing 
            Using ms As New MemoryStream() 
                DirectCast(pcxFile.Image, Bitmap).Save(ms, mCurrentFormat) 
                imageForDb = ms.ToArray() 
            End Using 
            Dim param As SqlParameter() = _
            New SqlParameter() {New SqlParameter("@IDImage", _
            Me.txtImageName.Text), New SqlParameter("@Image", imageForDb)} 
            mCmd.CommandText = "INSERT INTO TbImagesInDb( IDImage, Image) Values( @IDImage, @Image )" 
            mCmd.Parameters.AddRange(param) 
            mCn.Open() 
            mCmd.Connection = mCn 
            mCmd.ExecuteNonQuery() 
            mCn.Close() 
            
                
            LoadDatagrid() 
        Catch ex As Exception 
            MessageBox.Show(ex.Message) 
        End Try 
    End Sub

Questo Event Handler, trasforma l’immagine inserita nella Picturebox in un Memorystream e la converte in un array di Byte, fatto questo, aggiorna il database inserendo l’array di Byte ottenuto nel campo Image della tabella di SQL Server. Fatto questo, ricarica ed aggiorna la DataGridView contenente la tabella.

Il Metodo LoadDatagrid

private void LoadDatagrid()
        {
            try
            {
                mCmd.CommandText = "SELECT * FROM TbImagesInDb";
                mCmd.Connection = mCn;
                mCmd.Parameters.Clear();
                mCn.Open();
                SqlDataReader dr = mCmd.ExecuteReader();
                if (dr.HasRows)
                {
                    mDt = new DataTable();
                    mDt.Load(dr);
                }
                dr.Close();
                mCn.Close();
                this.dgvDb.DataSource = mDt;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

 

Private Sub LoadDatagrid() 
        Try 
            mCmd.CommandText = "SELECT * FROM TbImagesInDb" 
            mCmd.Connection = mCn 
            mCmd.Parameters.Clear() 
            mCn.Open() 
            Dim dr As SqlDataReader = mCmd.ExecuteReader() 
            If dr.HasRows Then 
                mDt = New DataTable() 
                mDt.Load(dr) 
            End If 
            dr.Close() 
            mCn.Close() 
            Me.dgvDb.DataSource = mDt 
        Catch ex As Exception 
            MessageBox.Show(ex.Message) 
        End Try 
    End Sub

Questo metodo, si connette a database, recupera una lista del contenuto della tabella immagini, e l’aggancia alla datagridview. Che è così intelligente da visualizzare automaticamente le immagini contenute nel database, anche se come possiamo notare, le gif animate restano ferme, mentre se le carichiamo su una picturebox si animano correttamente.

L’evento ColumnAdded della DataGridView

private void dgvDb_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
        {
            switch (e.Column.Index)
            {

                case 0:

                    e.Column.Visible = true;
                    e.Column.HeaderText = "Codice";
                    e.Column.MinimumWidth = 200;
                    break;
                case 1:
                    e.Column.Visible = true;
                    e.Column.HeaderText = "Immagine";
                    e.Column.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
                    e.Column.MinimumWidth = 100;
                    break;
            }
        }

 

Private Sub dgvDb_ColumnAdded(ByVal sender As Object, _
        ByVal e As DataGridViewColumnEventArgs) _
        Handles dgvDb.ColumnAdded 
       Select Case e.Column.Index 
            Case 0 
                e.Column.Visible = True 
                e.Column.HeaderText = "Codice" 
                e.Column.MinimumWidth = 200 
                
            Case 1 
                e.Column.Visible = True 
                e.Column.HeaderText = "Immagine" 
                e.Column.AutoSizeMode = _
                    DataGridViewAutoSizeColumnMode.Fill 
                e.Column.MinimumWidth = 100 
        End Select 
    End Sub

Questo evento, viene scatenato quando le colonne della datatable vengono aggiunte alla DataGridView, lo utilizziamo per formattare correttamente le colonne e dare loro un aspetto pulito, vi invito comunque a studiare sia gli eventi per la formattazione della DataGridView sia i Column e Row Template per la formattazione della stessa.

L’Evento RowsAdded della DataGridView

private void dgvDb_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
        {
            foreach (DataGridViewRow row in this.dgvDb.Rows)
            {
                MemoryStream ms = new MemoryStream((byte[])row.Cells[1].Value);
                Image img = Image.FromStream(ms);
                row.Height = img.Height + 5;
                ms.Dispose();
            }
        }

 

Private Sub dgvDb_RowsAdded(ByVal sender As Object, _
             ByVal e As DataGridViewRowsAddedEventArgs) _
             Handles dgvDb.RowsAdded 
        For Each row As DataGridViewRow In _
                    Me.dgvDb.Rows 
            Dim ms As New MemoryStream( _
                        DirectCast(row.Cells(1).Value, Byte() )) 
            Dim img As Image = Image.FromStream(ms) 
            row.Height = img.Height + 5 
            ms.Dispose() 
        Next 
    End Sub

Questo evento, viene scatenato quando aggiungiamo righe alla DataGridView e lo utilizziamo per leggere la dimensione dell’immagine e impostare di conseguenza l’altezza della riga corrente. Anche in questo caso, sono molte le cose che si possono fare naturalmente.

L’Evento RowEnter della DataGridView

private void dgvDb_RowEnter(object sender, DataGridViewCellEventArgs e)
        {
            try
            {

                DataRow[] rowList = this.mDt.Select(string.Format("IDImage = '{0}'", 
                this.dgvDb.Rows[e.RowIndex].Cells[0].Value));
                if (rowList.Length > 0)
                {
                    byte[] arrayImage = (byte[])rowList[0]["Image"];
                    MemoryStream ms = new MemoryStream(arrayImage);
                    if (this.pcxDatabase.Image != null)
                    {
                        this.pcxDatabase.Image.Dispose();
                    }
                    this.pcxDatabase.Image = Image.FromStream(ms);
                    //IMPORTANTE! Se il memory stream viene chiuso 
                    //La picturebox da un errore di GDI+ pertanto dobbiamo
                    //lasciarlo aperto, quindi abbiamo inserito la Dispose al cambio di
                    //immagine e alla chiusura della form.
                }

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

 

Private Sub dgvDb_RowEnter(ByVal sender As Object, _
        ByVal e As DataGridViewCellEventArgs) _
        Handles dgvDb.RowEnter 
        Try 
            
            Dim rowList As DataRow() = Me.mDt.[Select](String.Format("IDImage = '{0}'", _
            Me.dgvDb.Rows(e.RowIndex).Cells(0).Value)) 
            If rowList.Length > 0 Then 
                Dim arrayImage As Byte() =  DirectCast(rowList(0)("Image"), Byte() )
                Dim ms As New MemoryStream(arrayImage) 
                If Me.pcxDatabase.Image IsNot Nothing Then 
                    Me.pcxDatabase.Image.Dispose() 
                End If 
                    'IMPORTANTE! Se il memory stream viene chiuso 
                    'La picturebox da un errore di GDI+ pertanto dobbiamo 
                    'lasciarlo aperto, quindi abbiamo inserito la Dispose al cambio di 
                    'immagine e alla chiusura della form. 
                Me.pcxDatabase.Image = Image.FromStream(ms) 
                
            End If 
        Catch ex As Exception 
            MessageBox.Show(ex.Message) 
        End Try 
    End Sub

Questo evento, si scatena quando clicchiamo con il mouse su una riga della DatagridView rendendola attiva, lo utilizziamo per modificare il contenuto della Picturebox pcxDatabase impostandola con l’immagine letta dal campo della datarow, come potete notare, convertiamo il campo in un array di byte, poi da questo generiamo un MemoryStream che utilizziamo per generare l’oggetto Image che inseriamo nella picturebox. Come ho segnalato anche nel codice, per quanto dovrebbe essere possibile chiudere il memory stream all’uscita della funzione, visto che l’oggetto Image dovrebbe vivere di vita propria, evidentemente però è così, pertanto non possiamo effettuarne la Dispose. Invece, effettuiamo la dispose dell’immagine della picturebox prima di sostituirla e come vedremo, l’ultima immagine impostata, la elimineremo alla chiusura della form. Certo, in un programma come questo non ce ne sarebbe la necessità, ma se il vostro programma lavora in modo più complesso, ricordatevi sempre che gli oggetti che consumano risorse, come i memorystream e in questo caso le immagini ad essi collegati, devono essere eliminati e resi disponibili al garbage collector prima possibile.

L’Evento FormClosed

private void FrmMain_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (this.pcxDatabase.Image != null)
            {
                this.pcxDatabase.Image.Dispose();
            }

        }

 

Private Sub FrmMain_FormClosed(ByVal sender As Object, _
	ByVal e As FormClosedEventArgs) Handles MyBase.FormClosed 
        If Me.pcxDatabase.Image IsNot Nothing Then 
            Me.pcxDatabase.Image.Dispose() 
        End If 
        
    End Sub

Come accennato nel paragrafo precedente, alla chiusura della form, consegnamo al garbage collector l’immagine caricata per ultima, in quanto altrimenti il MemoryStream rimarrebbe appeso, e se in questa applicazione non ha importanza in quanto la chiusura della form chiude l’applicazione stessa quindi libera tutta la memoria usata, se una vostra applicazione facesse uso di varie form con questi memory stream rischieremmo di usare molta memoria senza averne la necessità.

Conclusioni

Come quasi tutte le cose in .NET anche il trattamento delle immagini come contenuto di un Database SQL Server non è una cosa difficile, pure avendo alcune complessità. Ovviamente non è difficile quando qualcuno ti mostra come fare, perché altrimenti, pensare ai MemoryStream, agli Array di Byte e a ciò che abbiamo visto in questo articolo non è propriamente immediato.

Per qualsiasi Feedback, Ulteriore domanda, Chiarimento, oppure se trovate qualche errore usate direttamente il form dei commenti in calce a questo articolo .

Il codice esempio relativo a questo articolo può essere scaricato dal link seguente: