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
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
In questa form inseriamo i seguenti controlli modificandone le property come indicato:
- Panel1 – Panel
- Dock = left
- Size = 237; 405 (ovviamente solo la larghezza ha importanza per un pannello docked).
- Contiene tutti i controlli salvo la datagridview
- pcxFile – PictureBox
- Location = 51; 12
- Size = 138; 123
- txtImageName – Textbox
- Location = 12; 141
- Size = 219; 20
- btnFile – Button
- Location = 12; 167
- Size = 75; 23
- Text = Scegli File
- UseVisualStyleBackColor = True
- btnToDb – Button
- Location = 156; 167
- Size = 75; 23
- Text = Metti in Db
- UseVisualStyleBackColor = True
- pcxDatabase – PictureBox
- Location = 51; 215
- Size = 138; 123
- SizeMode = Zoom
- dgvDb – DataGridView
- AllowUserToAddRows = false
- AllowUserToDeleteRows = false
- BackgroundColor = Orange
- ColumnHeadersHeightSizeMode = AutoSize
- Dock = fill
- GridColor = Teal
- Location = 237; 0
- 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:
Companion code, Memorizzare immagini in SQLServer