FILESTREAM Verileri için İstemci Uygulamaları Oluşturma

Şunlar için geçerlidir: SQL Server

Win32 API'lerini kullanarak bir FILESTREAM BLOB'una veri okuyabilir ve yazabilirsiniz. Aşağıdaki adımlar gereklidir:

  • FILESTREAM dosya yolunu okuyun.

  • Geçerli işlem bağlamını okuyun.

  • Win32 tanıtıcısı edinin ve bu tanıtıcıyı kullanarak FILESTREAM BLOB'a veri okuyup yazın.

Uyarı

Bu konudaki örnekler, FILESTREAM-Enabled Veritabanı Oluşturma ve FILESTREAM Verilerini Depolamak için Tablo Oluşturma bölümünde oluşturulan FILESTREAM özellikli veritabanı ve tabloyu gerektirir.

FILESTREAM Verileriyle Çalışma İşlevleri

İkili büyük nesne (BLOB) verilerini depolamak için FILESTREAM kullandığınızda, dosyalarla çalışmak için Win32 API'lerini kullanabilirsiniz. WIN32 uygulamalarında FILESTREAM BLOB verileriyle çalışmayı desteklemek için SQL Server aşağıdaki işlevleri ve API'yi sağlar:

  • PathName , BLOB'a belirteç olarak bir yol döndürür. Bir uygulama bu belirteci kullanarak bir Win32 tanıtıcısı alır ve BLOB verileri üzerinde çalışır.

    FILESTREAM verilerini içeren veritabanı Always On kullanılabilirlik grubuna ait olduğunda, PathName işlevi bilgisayar adı yerine bir sanal ağ adı (VNN) döndürür.

  • GET_FILESTREAM_TRANSACTION_CONTEXT() bir oturumun geçerli işlemini temsil eden bir belirteç döndürür. Bir uygulama, FILESTREAM dosya sistemi akış işlemlerini işleme bağlamak için bu belirteci kullanır.

  • OpenSqlFilestream API bir Win32 dosya tanıtıcısı alır. Uygulama, FILESTREAM verilerinin akışını yapmak için tanıtıcıyı kullanır ve ardından tutamacı şu Win32 API'lerine geçirebilir: ReadFile, WriteFile, TransmitFile, SetFilePointer, SetEndOfFile veya FlushFileBuffers. Uygulama tanıtıcıyı kullanarak başka bir API çağırırsa, bir ERROR_ACCESS_DENIED hatası döndürülür. Uygulamanın CloseHandle kullanarak tanıtıcıyı kapatması gerekir.

Tüm FILESTREAM veri kapsayıcısı erişimi bir SQL Server işleminde gerçekleştirilir. Transact-SQL deyimleri, SQL verileriyle FILESTREAM verileri arasında tutarlılığı korumak için aynı işlemde yürütülebilir.

FILESTREAM Verilerine Erişme Adımları

FILESTREAM Dosya Yolunu Okuma

FILESTREAM tablosundaki her hücrenin kendisiyle ilişkilendirilmiş bir dosya yolu vardır. Yolu okumak için, Transact-SQL deyimindeki bir varbinary(max) sütununun PathName özelliğini kullanın. Aşağıdaki örnekte , bir varbinary(max) sütununun dosya yolunun nasıl okunduğu gösterilmektedir.

DECLARE @filePath VARCHAR(MAX);

SELECT @filePath = Chart.PathName()
FROM Archive.dbo.Records
WHERE SerialNumber = 3;

PRINT @filepath;

İşlem Bağlamını Okuma

Geçerli işlem bağlamını almak için Transact-SQL GET_FILESTREAM_TRANSACTION_CONTEXT() işlevini kullanın. Aşağıdaki örnekte bir işlemin nasıl başlatılıp geçerli işlem bağlamı okunduğu gösterilmektedir.

DECLARE @txContext VARBINARY(MAX);

BEGIN TRANSACTION;
SELECT @txContext = GET_FILESTREAM_TRANSACTION_CONTEXT();
PRINT @txContext;
COMMIT;

Win32 Dosya Tanıtıcısı Alma

Win32 dosya tanıtıcısını almak için OpenSqlFilestream API'sini çağırın. Bu API sqlncli.dll dosyasından dışarı aktarılır. Döndürülen tanıtıcı, aşağıdaki Win32 API'lerinin herhangi birine geçirilebilir: ReadFile, WriteFile, TransmitFile, SetFilePointer, SetEndOfFile veya FlushFileBuffers. Aşağıdaki örneklerde bir Win32 Dosya tutamacını nasıl edindiğiniz ve bir FILESTREAM BLOB'una veri okumak ve yazmak için nasıl kullanacağınız gösterilmektedir.

using System.IO;
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlTypes;

namespace FILESTREAM
{
    class Program
    {
        static void Main(string[] args)
        {
            SqlConnection sqlConnection = new SqlConnection(
                "Integrated Security=true;server=(local)");

            SqlCommand sqlCommand = new SqlCommand();
            sqlCommand.Connection = sqlConnection;

            try
            {
                sqlConnection.Open();

                //The first task is to retrieve the file path
                //of the SQL FILESTREAM BLOB that we want to
                //access in the application.

                sqlCommand.CommandText =
                      "SELECT Chart.PathName()"
                    + " FROM Archive.dbo.Records"
                    + " WHERE SerialNumber = 3";

                String filePath = null;

                Object pathObj = sqlCommand.ExecuteScalar();
                if (DBNull.Value != pathObj)
                    filePath = (string)pathObj;
                else
                {
                    throw new System.Exception(
                        "Chart.PathName() failed"
                      + " to read the path name "
                      + " for the Chart column.");
                }

                //The next task is to obtain a transaction
                //context. All FILESTREAM BLOB operations
                //occur within a transaction context to
                //maintain data consistency.

                //All SQL FILESTREAM BLOB access must occur in 
                //a transaction. MARS-enabled connections
                //have specific rules for batch scoped transactions,
                //which the Transact-SQL BEGIN TRANSACTION statement
                //violates. To avoid this issue, client applications 
                //should use appropriate API facilities for transaction management, 
                //management, such as the SqlTransaction class.

                SqlTransaction transaction = sqlConnection.BeginTransaction("mainTranaction");
                sqlCommand.Transaction = transaction;

                sqlCommand.CommandText =
                    "SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()";

                Object obj = sqlCommand.ExecuteScalar();
                byte[] txContext = (byte[])obj;

                //The next step is to obtain a handle that
                //can be passed to the Win32 FILE APIs.

                SqlFileStream sqlFileStream = new SqlFileStream(filePath, txContext, FileAccess.ReadWrite);

                byte[] buffer = new byte[512];

                int numBytes = 0;

                //Write the string, "EKG data." to the FILESTREAM BLOB.
                //In your application this string would be replaced with
                //the binary data that you want to write.

                string someData = "EKG data.";
                Encoding unicode = Encoding.GetEncoding(0);

                sqlFileStream.Write(unicode.GetBytes(someData.ToCharArray()),
                    0,
                    someData.Length);

                //Read the data from the FILESTREAM
                //BLOB.

                sqlFileStream.Seek(0L, SeekOrigin.Begin);

                numBytes = sqlFileStream.Read(buffer, 0, buffer.Length);

                string readData = unicode.GetString(buffer);

                if (numBytes != 0)
                    Console.WriteLine(readData);

                //Because reading and writing are finished, FILESTREAM 
                //must be closed. This closes the c# FileStream class, 
                //but does not necessarily close the underlying 
                //FILESTREAM handle. 
                sqlFileStream.Close();

                //The final step is to commit or roll back the read and write
                //operations that were performed on the FILESTREAM BLOB.

                sqlCommand.Transaction.Commit();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                sqlConnection.Close();
            }
            return;
        }
    }
}
Imports System.IO
Imports System 
Imports System.Collections.Generic 
Imports System.Text 
Imports System.Data 
Imports Microsoft.Data.SqlClient 
Imports Microsoft.Data.SqlTypes 

Module Module1
    Public Sub Main(ByVal args As String())
        '        Dim sqlConnection As New SqlConnection("Integrated Security=true;server=(local)")
        Dim sqlConnection As New SqlConnection("Integrated Security=true;server=kellyreyue\MSSQL1")

        Dim sqlCommand As New SqlCommand()
        sqlCommand.Connection = sqlConnection

        Try
            sqlConnection.Open()

            'The first task is to retrieve the file path 
            'of the SQL FILESTREAM BLOB that we want to 
            'access in the application. 

            sqlCommand.CommandText = "SELECT Chart.PathName()" + " FROM Archive.dbo.Records" + " WHERE SerialNumber = 3"

            Dim filePath As String = Nothing

            Dim pathObj As Object = sqlCommand.ExecuteScalar()
            If Not pathObj.Equals(DBNull.Value) Then
                filePath = DirectCast(pathObj, String)
            Else
                Throw New System.Exception("Chart.PathName() failed" + " to read the path name " + " for the Chart column.")
            End If

            'The next task is to obtain a transaction 
            'context. All FILESTREAM BLOB operations 
            'occur within a transaction context to 
            'maintain data consistency. 

            'All SQL FILESTREAM BLOB access must occur in 
            'a transaction. MARS-enabled connections 
            'have specific rules for batch scoped transactions, 
            'which the Transact-SQL BEGIN TRANSACTION statement 
            'violates. To avoid this issue, client applications 
            'should use appropriate API facilities for transaction management, 
            'management, such as the SqlTransaction class. 

            Dim transaction As SqlTransaction = sqlConnection.BeginTransaction("mainTranaction")
            sqlCommand.Transaction = transaction

            sqlCommand.CommandText = "SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()"

            Dim obj As Object = sqlCommand.ExecuteScalar()
            Dim txContext As Byte() = Nothing

            Dim contextLength As UInteger

            If Not obj.Equals(DBNull.Value) Then
                txContext = DirectCast(obj, Byte())
                contextLength = txContext.Length()
            Else
                Dim message As String = "GET_FILESTREAM_TRANSACTION_CONTEXT() failed"
                Throw New System.Exception(message)
            End If

            'The next step is to obtain a handle that 
            'can be passed to the Win32 FILE APIs. 

            Dim sqlFileStream As New SqlFileStream(filePath, txContext, FileAccess.ReadWrite)

            Dim buffer As Byte() = New Byte(511) {}

            Dim numBytes As Integer = 0

            'Write the string, "EKG data." to the FILESTREAM BLOB. 
            'In your application this string would be replaced with 
            'the binary data that you want to write. 

            Dim someData As String = "EKG data."
            Dim unicode As Encoding = Encoding.GetEncoding(0)

            sqlFileStream.Write(unicode.GetBytes(someData.ToCharArray()), 0, someData.Length)

            'Read the data from the FILESTREAM 
            'BLOB. 

            sqlFileStream.Seek(0, SeekOrigin.Begin)

            numBytes = sqlFileStream.Read(buffer, 0, buffer.Length)

            Dim readData As String = unicode.GetString(buffer)

            If numBytes <> 0 Then
                Console.WriteLine(readData)
            End If

            'Because reading and writing are finished, FILESTREAM 
            'must be closed. This closes the c# FileStream class, 
            'but does not necessarily close the underlying 
            'FILESTREAM handle. 
            sqlFileStream.Close()

            'The final step is to commit or roll back the read and write 
            'operations that were performed on the FILESTREAM BLOB. 

            sqlCommand.Transaction.Commit()
        Catch ex As System.Exception
            Console.WriteLine(ex.ToString())
        Finally
            sqlConnection.Close()
        End Try
        Return
    End Sub
End Module
#include <windows.h>
#include <sql.h>
#include<sqltypes.h>
#include<sqlext.h>
#include <stdio.h>
#include <msodbcsql.h>

#define COPYBUFFERSIZE 4096

/// <summary>
///This class iterates though the ODBC error queue and prints all of the
///accumulated error messages to the console.
/// </summary>

class ODBCErrors
{
private:
    int         m_iLine;    //Source code line on which the error occurred
    SQLSMALLINT m_type;     //Type of handle on which the error occurred
    SQLHANDLE   m_handle;   //ODBC handle on which the error occurred

public:
    /// <summary>
    ///Default constructor for the ODBCErrors class
    ///</summary>

    ODBCErrors()
    {
        m_iLine  = -1;
        m_type   = 0;
        m_handle = SQL_NULL_HANDLE;
    }

    /// <summary>
    ///Constructor for the ODBCErrors class
    /// </summary>
    /// <param name="iLine">
    /// This parameter is the source code line
    /// at which the error occurred.
    ///</param>
    /// <param name="type">
    /// This parameter is the type of ODBC handle passed in
    /// the next parameter.
    ///</param>
    /// <param name="handle">
    /// This parameter is the handle on which the error occurred.
    ///</param>

    ODBCErrors(int iLine, SQLSMALLINT type, SQLHANDLE handle)
    {
        m_iLine  = iLine;
        m_type   = type;
        m_handle = handle;
    }

    ///<summary>
    /// This method iterates though the error stack for the handle passed
    /// into the constructor and displays those errors on the console.
    ///</summary>

    void Print()
    {
        SQLSMALLINT i = 0, len = 0;
        SQLINTEGER  native;
        SQLTCHAR    state[9], text[256];
        SQLRETURN   sqlReturn = SQL_SUCCESS;

        if ( m_handle == SQL_NULL_HANDLE )
        {
            wprintf_s(TEXT("The error handle is not a valid handle.\n"), m_iLine);
            return;
        }

        wprintf_s(TEXT("Error Line(%d)\n"), m_iLine);

        while( sqlReturn == SQL_SUCCESS )
        {
            len = 0;

            sqlReturn = SQLGetDiagRec(
                m_type,
                m_handle,
                ++i,
                state,
                &native,
                text,
                sizeof(text)/sizeof(SQLTCHAR),
                &len);

            if ( SQL_SUCCEEDED(sqlReturn) )
                wprintf_s(TEXT("Error(%d, %ld, %s) : %s\n"), i, native, state, text);
        }
    }
};


BOOL CopyFileToSQL(LPTSTR srcFilePath, LPTSTR dstFilePath, LPBYTE transactionToken, SQLINTEGER cbTransactionToken)
{
    BOOL bRetCode = FALSE;

    HANDLE srcHandle = INVALID_HANDLE_VALUE;
    HANDLE dstHandle = INVALID_HANDLE_VALUE;
    BYTE   buffer[COPYBUFFERSIZE] = { 0 };

    TCHAR *szErrMsgSrc   = TEXT("Error opening source file.");
    TCHAR *szErrMsgDst   = TEXT("Error opening destFile file.");
    TCHAR *szErrMsgRead  = TEXT("Error reading source file.");
    TCHAR *szErrMsgWrite = TEXT("Error writing SQL file.");

    try
    {
        if ( (srcHandle = CreateFile(
            srcFilePath,
            GENERIC_READ,
            FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_SEQUENTIAL_SCAN,
            NULL)) == INVALID_HANDLE_VALUE )
            throw szErrMsgSrc;

        if ( (dstHandle =  OpenSqlFilestream(
            dstFilePath,
            Write,
            0,
            transactionToken,
            cbTransactionToken,
            0)) == INVALID_HANDLE_VALUE)
            throw szErrMsgDst;

        DWORD bytesRead = 0;
        DWORD bytesWritten = 0;

        do
        {
            if ( ReadFile(srcHandle, buffer, COPYBUFFERSIZE, &bytesRead, NULL) == 0 )
                throw szErrMsgRead;

            if (bytesRead > 0)
            {
                if ( WriteFile(dstHandle, buffer, bytesRead, &bytesWritten, NULL) == 0 )
                    throw szErrMsgWrite;
            }
        } while (bytesRead > 0);

        bRetCode = TRUE;
    }
    catch( TCHAR *szErrMsg )
    {
        wprintf_s(szErrMsg);
        bRetCode = FALSE;
    }

    if ( srcHandle != INVALID_HANDLE_VALUE )
        CloseHandle(srcHandle);

    if ( dstHandle != INVALID_HANDLE_VALUE )
        CloseHandle(dstHandle);

    return bRetCode;
}

void main()
{
    TCHAR *sqlDBQuery =
       TEXT("INSERT INTO Archive.dbo.Records(Id, SerialNumber, Chart)")
       TEXT(" OUTPUT GET_FILESTREAM_TRANSACTION_CONTEXT(), inserted.Chart.PathName()")
       TEXT("VALUES (newid (), 5, CONVERT(VARBINARY, '**Temp**'))");

    SQLCHAR transactionToken[32];
    
    SQLHANDLE henv = SQL_NULL_HANDLE;
    SQLHANDLE hdbc              = SQL_NULL_HANDLE;
    SQLHANDLE hstmt             = SQL_NULL_HANDLE;

    try
    {
        //These statements Initialize ODBC for the client application and
        //connect to the database.

        if ( SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_ENV, henv);

        if ( SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,(void*)SQL_OV_ODBC3, NULL) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_ENV, henv);

        if ( SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_ENV, henv);

        //This code assumes that the dataset name "Sql Server FILESTREAM"
        //has been previously created on the client computer system. An
        //ODBC DSN is created with the ODBC Data Source item in
        //the Windows Control Panel.

        if ( SQLConnect(hdbc, TEXT("Sql Server FILESTREAM"),
                SQL_NTS, NULL, 0, NULL, 0) <= 0 )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_DBC, hdbc);

        //FILESTREAM requires that all read and write operations occur
        //within a transaction.
        if ( SQLSetConnectAttr(hdbc,
            SQL_ATTR_AUTOCOMMIT,
            (SQLPOINTER)SQL_AUTOCOMMIT_OFF,
            SQL_IS_UINTEGER) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_DBC, hdbc);

        if ( SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_DBC, hdbc);

        if ( SQLExecDirect(hstmt, sqlDBQuery, SQL_NTS) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_STMT, hstmt);

        //Retrieve the transaction token.
        if ( SQLFetch(hstmt) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_STMT, hstmt);

        SQLINTEGER cbTransactionToken = sizeof(transactionToken);

        if ( SQLGetData(hstmt, 1,
            SQL_C_BINARY,
            transactionToken,
            sizeof(transactionToken),
            &cbTransactionToken) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_STMT, hstmt);

        //Retrieve the file path for the inserted record.

        TCHAR dstFilePath[1024];
        SQLINTEGER cbDstFilePath;

        if ( SQLGetData(hstmt, 2, SQL_C_TCHAR, dstFilePath, sizeof(dstFilePath), &cbDstFilePath) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_STMT, hstmt);

        if ( SQLCloseCursor(hstmt) != SQL_SUCCESS )
            throw new ODBCErrors(__LINE__, SQL_HANDLE_STMT, hstmt);

        SQLUSMALLINT mode = SQL_ROLLBACK;

        if ( CopyFileToSQL(
            TEXT("C:\\Users\\Data\\chart1.jpg"),
            dstFilePath,
            transactionToken,
            cbTransactionToken) == TRUE )
            mode = SQL_COMMIT;

        SQLTransact(henv, hdbc, mode);
    }
    catch(ODBCErrors *pErrors)
    {
        pErrors->Print();
        delete pErrors;
    }

    if ( hstmt != SQL_NULL_HANDLE )
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    if ( hdbc != SQL_NULL_HANDLE )
        SQLDisconnect(hdbc);

    if ( hdbc != SQL_NULL_HANDLE )
        SQLFreeHandle(SQL_HANDLE_DBC, hdbc); 

    if ( henv != SQL_NULL_HANDLE )
        SQLFreeHandle(SQL_HANDLE_ENV, henv);
}

Uygulama Tasarımı ve Uygulaması için En İyi Yöntemler

  • FILESTREAM kullanan uygulamalar tasarlarken ve uygularken aşağıdaki yönergeleri göz önünde bulundurun:

  • Başlatılmamış bir FILESTREAM sütununu temsil etmek için 0x yerine NULL kullanın. 0x değeri bir dosyanın oluşturulmasına neden olur ve NULL oluşturulmaz.

  • Boş olmayan FILESTREAM sütunları içeren tablolarda ekleme ve silme işlemlerinden kaçının. Ekleme ve silme işlemleri, çöp toplama için kullanılan FILESTREAM tablolarını değiştirebilir. Bu, bir uygulamanın performansının zaman içinde düşmesine neden olabilir.

  • Çoğaltma kullanan uygulamalarda, NEWID() yerine NEWSEQUENTIALID() kullanın. NEWSEQUENTIALID(), bu uygulamalarda GUID oluşturma için NEWID() değerinden daha iyi performans gösterir.

  • FILESTREAM API'si, verilere Win32 akış erişimi için tasarlanmıştır. 2 MB'tan büyük FILESTREAM ikili büyük nesnelerini (BLOB' lar) okumak veya yazmak için Transact-SQL kullanmaktan kaçının. Transact-SQL'den BLOB verilerini okumanız veya yazmanız gerekiyorsa, Win32'den FILESTREAM BLOB'unu açmaya çalışmadan önce tüm BLOB verilerinin tüketildiğinden emin olun. Tüm Transact-SQL verilerinin tüketilememesi, ardışık FILESTREAM açma veya kapatma işlemlerinin başarısız olmasına neden olabilir.

  • FILESTREAM BLOB'unu güncelleştiren, ekleyen veya önceden ekleyen Transact-SQL deyimlerinden kaçının. Bu, BLOB verilerinin tempdb veritabanına biriktirilmesine ve ardından yeniden yeni bir fiziksel dosyaya alınmasına neden olur.

  • FILESTREAM BLOB'a küçük BLOB güncelleştirmeleri eklemekten kaçının. Her ekleme, temel alınan FILESTREAM dosyalarının kopyalanmasına neden olur. Bir uygulamanın küçük BLOB'ları eklemesi gerekiyorsa, BLOB'ları bir varbinary(max) sütununa yazın ve ardından BLOB sayısı önceden belirlenmiş bir sınıra ulaştığında FILESTREAM BLOB'una tek bir yazma işlemi gerçekleştirin.

  • Bir uygulamadaki çok sayıda BLOB dosyasının veri uzunluğunu almaktan kaçının. Boyut SQL Server Veritabanı Altyapısı'nda depolanmadığından bu işlem zaman alan bir işlemdir. BLOB dosyasının uzunluğunu belirlemeniz gerekiyorsa, blobun kapalı olması durumunda boyutunu belirlemek için DATALENGTH() işlevini Transact-SQL kullanın. DATALENGTH(), boyutunu belirlemek için BLOB dosyasını açmaz.

  • Bir uygulama İleti Bloğu1 (SMB1) protokolü kullanıyorsa, performansı iyileştirmek için FILESTREAM BLOB verileri 60 KB'lık katlarda okunmalıdır.