Partager via


Préservation des ressources lors de l'écriture des valeurs BLOB dans SQL Server

Vous pouvez écrire un objet binaire volumineux (BLOB) dans une base de données en insérant ou en remplaçant dans un champ une valeur de chaîne ou un tableau d'octets en fonction du type de ce champ (consultez Écriture de valeurs BLOB dans une base de données). Toutefois, un BLOB peut parfois être très volumineux et donc consommer une grande quantité de mémoire système lorsqu'il est écrit en tant que valeur unique, ce qui a une incidence négative sur les performances de l'application.

Pour réduire la quantité de mémoire utilisée, il est donc courant d'écrire le BLOB dans la base de données en segments. La procédure d'écriture dépend dans ce cas des fonctionnalités de la base de données.

L'exemple suivant montre comment écrire un BLOB en segments dans SQL Server. Dans cet exemple, un nouvel enregistrement est ajouté à la table Employees de la base de données Northwind ; cet enregistrement contient un BLOB, à savoir la photo de l'employé. La fonction UPDATETEXT de SQL Server est utilisée pour écrire la photo de l'employé dans le champ Photo en segments d'une taille spécifiée.

Cette fonction requiert un pointeur vers le champ BLOB mis à jour. Dans cet exemple, lorsque l'enregistrement du nouvel employé est ajouté, la fonction SQL Server TEXTPTR est appelée pour retourner un pointeur vers le champ Photo du nouvel enregistrement. La valeur de pointeur retournée est repassée sous forme de paramètre de sortie. Le code de cet exemple conserve ce pointeur et le passe à la fonction UPDATETEXT lors de l'ajout des segments de données.

L'instruction Transact-SQL utilisée pour insérer le nouvel enregistrement d'employé et conserver le pointeur vers le champ Photo est illustrée dans l'exemple suivant (où @Identity et @Pointer sont identifiés comme paramètres de sortie de SqlCommand).

INSERT INTO Employees (LastName, FirstName, Title, HireDate, ReportsTo, Photo) 
  Values(@LastName, @FirstName, @Title, @HireDate, @ReportsTo, 0x0)
SELECT @Identity = SCOPE_IDENTITY()
SELECT @Pointer = TEXTPTR(Photo) FROM Employees WHERE EmployeeID = @Identity

Notez que la valeur initiale de 0x0 (nulle) est insérée dans le champ Photo. Une valeur de pointeur peut ainsi être extraite pour le champ Photo du nouvel enregistrement inséré. Toutefois, cette valeur nulle n'a pas d'incidence sur les segments de données ajoutés.

Le pointeur vers le champ Photo du nouvel enregistrement inséré étant conservé, les segments de données peuvent être ajoutés au champ BLOB par la fonction UPDATETEXT de SQL Server. Cette fonction accepte en entrée l'identificateur de champ (Employees.Photo), le pointeur vers le champ BLOB, une valeur de décalage représentant l'emplacement dans le BLOB auquel le segment actuel est écrit et le segment de données à ajouter. L'exemple de code suivant illustre la syntaxe de la fonction UPDATETEXT (où @Pointer, @Offset et @Bytes sont identifiés comme paramètres d'entrée de SqlCommand).

UPDATETEXT Employees.Photo @Pointer @Offset 0 @Bytes

La valeur de décalage est déterminée par la taille de la mémoire tampon, laquelle est définie en fonction des besoins de votre application. Si la taille de la mémoire tampon est élevée, l'écriture du BLOB est plus rapide, mais la quantité de mémoire système utilisée plus importante. Dans cet exemple, la mémoire tampon est de taille assez modeste : 128 octets. La valeur de décalage démarre à 0 pour le premier segment de données et est ensuite incrémentée de la taille de la mémoire tampon pour chacun des segments suivants.

Dans cet exemple, la photo de l'employée est extraite sous forme de segments d'un fichier dont le chemin d'accès est spécifié. Chaque segment est lu dans un tableau d'octets compte tenu de la taille de mémoire tampon spécifiée. Le tableau d'octets est alors défini comme valeur du paramètre d'entrée @Bytes de SqlCommand. La valeur du paramètre @Offset est mise à jour et SqlCommand exécutée, ce qui a pour effet d'ajouter le segment actuel d'octets au champ Photo de l'enregistrement de l'employé.

Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.IO

Public Class EmployeeData

    Public Shared Sub Main()
      Dim hireDate As DateTime = DateTime.Parse("4/27/98")
      Dim newID As Integer = _
          AddEmployee("Smith", "John", "Sales Representative", hireDate, 5, "smith.bmp")
      Console.WriteLine("New Employee added. EmployeeID = " & newID)
    End Sub

    Public Shared Function AddEmployee(lastName As String, firstName As String, title As String, hireDate As DateTime, _
                           reportsTo As Integer, photoFilePath As String) As Integer

    Dim nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;")

    Dim addEmp As SqlCommand = New SqlCommand("INSERT INTO Employees (LastName, FirstName, Title, HireDate, ReportsTo, Photo) " & _
                                              "Values(@LastName, @FirstName, @Title, @HireDate, @ReportsTo, 0x0);" & _
                                              "SELECT @Identity = SCOPE_IDENTITY();" & _
                                              "SELECT @Pointer = TEXTPTR(Photo) FROM Employees WHERE EmployeeID = @Identity", nwindConn) 

    addEmp.Parameters.Add("@LastName",  SqlDbType.NVarChar, 20).Value = lastName
    addEmp.Parameters.Add("@FirstName", SqlDbType.NVarChar, 10).Value = firstName
    addEmp.Parameters.Add("@Title",     SqlDbType.NVarChar, 30).Value = title
    addEmp.Parameters.Add("@HireDate",  SqlDbType.DateTime).Value     = hireDate
    addEmp.Parameters.Add("@ReportsTo", SqlDbType.Int).Value          = reportsTo

    Dim idParm As SqlParameter = addEmp.Parameters.Add("@Identity", SqlDbType.Int)
    idParm.Direction = ParameterDirection.Output
    Dim ptrParm As SqlParameter = addEmp.Parameters.Add("@Pointer", SqlDbType.Binary, 16)
    ptrParm.Direction = ParameterDirection.Output

    nwindConn.Open()

    addEmp.ExecuteNonQuery()

    Dim newEmpID As Integer = CType(idParm.Value, Integer)

    StorePhoto(photoFilePath, ptrParm.Value, nwindConn)

    nwindConn.Close()

    Return newEmpID
  End Function

  Public Shared Sub StorePhoto(fileName As String, pointer As Byte(), nwindConn As SqlConnection)

    Dim bufferLen As Integer = 128   ' The size of the "chunks" of the image.

    Dim appendToPhoto As SqlCommand = New SqlCommand("UPDATETEXT Employees.Photo @Pointer @Offset 0 @Bytes", nwindConn)

    Dim ptrParm As SqlParameter = appendToPhoto.Parameters.Add("@Pointer", SqlDbType.Binary, 16)
    ptrParm.Value = pointer
    Dim photoParm As SqlParameter = appendToPhoto.Parameters.Add("@Bytes", SqlDbType.Image, bufferLen)
    Dim offsetParm As SqlParameter = appendToPhoto.Parameters.Add("@Offset", SqlDbType.Int)
    offsetParm.Value = 0

    ''''''''''''''''''''''''''''''''''''
    '' Read the image in and write it to the database 128 (bufferLen) bytes at a time.
    '' Tune bufferLen for best performance. Larger values write faster, but
    '' use more system resources.


    Dim fs As FileStream = New FileStream(fileName, FileMode.Open, FileAccess.Read)
    Dim br As BinaryReader = New BinaryReader(fs)

    Dim buffer() As Byte = br.ReadBytes(bufferLen)
    Dim offset_ctr As Integer = 0

    Do While buffer.Length > 0
      photoParm.Value = buffer
      appendToPhoto.ExecuteNonQuery()
      offset_ctr += bufferLen
      offsetParm.Value = offset_ctr
      buffer = br.ReadBytes(bufferLen)
    Loop

    br.Close()
    fs.Close()
  End Sub

End Class
[C#]
using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;

public class EmployeeData
{
  public static void Main()
  {
    DateTime hireDate = DateTime.Parse("4/27/98");
    int newID  = AddEmployee("Smith", "John", "Sales Representative", hireDate, 5, "smith.bmp");
    Console.WriteLine("New Employee added. EmployeeID = " + newID);
  }

  public static int AddEmployee(string lastName, string firstName, string title, DateTime hireDate , int reportsTo, string photoFilePath)
  {
    SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;");

    SqlCommand addEmp  = new SqlCommand("INSERT INTO Employees (LastName, FirstName, Title, HireDate, ReportsTo, Photo) " +
      "Values(@LastName, @FirstName, @Title, @HireDate, @ReportsTo, 0x0);" +
      "SELECT @Identity = SCOPE_IDENTITY();" +
      "SELECT @Pointer = TEXTPTR(Photo) FROM Employees WHERE EmployeeID = @Identity", nwindConn);

    addEmp.Parameters.Add("@LastName",  SqlDbType.NVarChar, 20).Value = lastName;
    addEmp.Parameters.Add("@FirstName", SqlDbType.NVarChar, 10).Value = firstName;
    addEmp.Parameters.Add("@Title",     SqlDbType.NVarChar, 30).Value = title;
    addEmp.Parameters.Add("@HireDate",  SqlDbType.DateTime).Value     = hireDate;
    addEmp.Parameters.Add("@ReportsTo", SqlDbType.Int).Value          = reportsTo;

    SqlParameter idParm = addEmp.Parameters.Add("@Identity", SqlDbType.Int);
    idParm.Direction = ParameterDirection.Output;
    SqlParameter ptrParm = addEmp.Parameters.Add("@Pointer", SqlDbType.Binary, 16);
    ptrParm.Direction = ParameterDirection.Output;

    nwindConn.Open();

    addEmp.ExecuteNonQuery();

    int newEmpID = (int)idParm.Value;

    StorePhoto(photoFilePath, (byte[])ptrParm.Value, nwindConn);

    nwindConn.Close();

    return newEmpID;
  }

  public static void StorePhoto(string fileName, byte[] pointer,  SqlConnection nwindConn)
  {
    int bufferLen = 128;  // The size of the "chunks" of the image.

    SqlCommand appendToPhoto = new SqlCommand("UPDATETEXT Employees.Photo @Pointer @Offset 0 @Bytes", nwindConn);

    SqlParameter ptrParm  = appendToPhoto.Parameters.Add("@Pointer", SqlDbType.Binary, 16);
    ptrParm.Value = pointer;
    SqlParameter photoParm = appendToPhoto.Parameters.Add("@Bytes", SqlDbType.Image, bufferLen);
    SqlParameter offsetParm = appendToPhoto.Parameters.Add("@Offset", SqlDbType.Int);
    offsetParm.Value = 0;

    //''''''''''''''''''''''''''''''''''
    // Read the image in and write it to the database 128 (bufferLen) bytes at a time.
    // Tune bufferLen for best performance. Larger values write faster, but
    // use more system resources.

    FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    BinaryReader br = new BinaryReader(fs);

    byte[] buffer = br.ReadBytes(bufferLen);
    int offset_ctr = 0;

    while (buffer.Length > 0)
    {
      photoParm.Value = buffer;
      appendToPhoto.ExecuteNonQuery();
      offset_ctr += bufferLen;
      offsetParm.Value = offset_ctr;
      buffer = br.ReadBytes(bufferLen);
    }

    br.Close();
    fs.Close();
  }
}

Voir aussi

Écriture de valeurs BLOB dans une base de données | Exemples de scénarios ADO.NET | Accès aux données avec ADO.NET | Utilisation des fournisseurs de données .NET Framework pour l'accès aux données