Obtener acceso a los datos FILESTREAM con OpenSqlFilestream
Se aplica a: SQL Server
La API OpenSqlFilestream obtiene un identificador de archivos de Win32 compatible para un objeto binario grande (BLOB) de FILESTREAM almacenado en el sistema de archivos. El identificador se puede pasar a cualquiera de las API de Win32 siguientes: ReadFile, WriteFile, TransmitFile, SetFilePointer, SetEndOfFileo FlushFileBuffers. Si pasa este identificador a cualquier otra API de Win32, se devuelve un error de ERROR_ACCESS_DENIED. El identificador se debe cerrar al pasarlo a la API Win32 CloseHandle antes de que la transacción se confirme o se revierta. Si no se puede cerrar el identificador, se producirán pérdidas de los recursos del lado servidor.
Todo el acceso al contenedor de datos FILESTREAM debe realizarse en una transacción de SQL Server. También puede ejecutar instrucciones Transact-SQL en la misma transacción. Esto permite mantener la coherencia entre los datos SQL y los datos de BLOB FILESTREAM.
Para acceder al FILESTREAM BLOB con Win32, la autorización de Windows debe estar habilitada.
Importante
Cuando el archivo se abre para acceso de escritura, el agente FILESTREAM posee la transacción. Hasta que la transacción se libera, solo se permite la E/S de archivos Win32. Para liberar la transacción, se debe cerrar el identificador de escritura.
Sintaxis
HANDLE OpenSqlFilestream (
LPCWSTR FilestreamPath,
SQL_FILESTREAM_DESIRED_ACCESS DesiredAccess,
ULONG OpenOptions,
LPBYTE FilestreamTransactionContext,
SIZE_T FilestreamTransactionContextLength,
PLARGE_INTEGER AllocationSize);
Parámetros
FilestreamPath
[in] Es la ruta nvarchar(max) devuelta por la función PathName . Se debe llamar a PathName desde el contexto de una cuenta que tenga los permisos SELECT o UPDATE de SQL Server en la tabla y la columna FILESTREAM.
DesiredAccess
[in] Establece el modo utilizado para tener acceso a los datos de BLOB FILESTREAM. Este valor se pasa a la función DeviceIoControl.
NOMBRE | Valor | Significado |
---|---|---|
SQL_FILESTREAM_READ | 0 | Se pueden leer datos de este archivo. |
SQL_FILESTREAM_WRITE | 1 | En este archivo se pueden escribir datos. |
SQL_FILESTREAM_READWRITE | 2 | En este archivo se pueden escribir y leer datos. |
Nota:
Estos valores se definen en la enumeración SQL_FILESTREAM_DESIRED_ACCESS en msoledbsql.h.
OpenOptions
[in] Los atributos y marcas de archivo. Este parámetro también puede incluir cualquier combinación de las marcas siguientes.
Marca | Valor | Significado |
---|---|---|
SQL_FILESTREAM_OPEN_NONE | 0x00000000: | El archivo se abre o se crea sin opciones especiales. |
SQL_FILESTREAM_OPEN_FLAG_ASYNC | 0x00000001L | El archivo se abre o se crea para E/S asincrónica. |
SQL_FILESTREAM_OPEN_FLAG_NO_BUFFERING | 0x00000002L | El sistema abre el archivo sin utilizar el almacenamiento en caché del sistema. |
SQL_FILESTREAM_OPEN_FLAG_NO_WRITE_THROUGH | 0x00000004L | El sistema no escribe mediante una caché intermedia. La escritura va directamente al disco. |
SQL_FILESTREAM_OPEN_FLAG_SEQUENTIAL_SCAN | 0x00000008L | Se tiene acceso a un archivo de forma secuencial, desde el principio hasta el final. El sistema puede considerar que esto es una sugerencia para optimizar el almacenamiento en caché del archivo. Si una aplicación mueve el puntero de archivo para obtener acceso aleatorio, puede que no se produzca un almacenamiento en caché óptimo. |
SQL_FILESTREAM_OPEN_FLAG_RANDOM_ACCESS | 0x00000010L | Se tiene acceso a un archivo de forma aleatoria. El sistema puede considerar que esto es una sugerencia para optimizar el almacenamiento en caché del archivo. |
FilestreamTransactionContext
[in] El valor devuelto por la función GET_FILESTREAM_TRANSACTION_CONTEXT .
FilestreamTransactionContextLength
[in] Número de bytes en los datos varbinary(max) devueltos por la función GET_FILESTREAM_TRANSACTION_CONTEXT. La función devuelve una matriz de N bytes. N viene determinada por la función y es una propiedad de la matriz de bytes devuelta.
AllocationSize
[in] Especifica el tamaño de asignación inicial del archivo de datos en bytes. Se pasa por alto en modo de lectura. Este parámetro puede ser NULL, en cuyo caso se utiliza el comportamiento predeterminado del sistema de archivos.
Valor devuelto
Si la función se ejecuta correctamente, el valor devuelto es un identificador abierto para un archivo especificado. Si se produce un error en la función, el valor devuelto es INVALID_HANDLE_VALUE. Para obtener información de error extendida, llame a GetLastError().
Ejemplos
Los ejemplos siguientes muestran cómo utilizar la API OpenSqlFilestream
para obtener un identificador Win32.
using System.IO;
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.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,
Encoding.GetBytes());
//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 System.Data.SqlClient
Imports System.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, Encoding.GetBytes())
'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);
}
Comentarios
Este ejemplo se escribió originalmente para SQL Server Native Client (sqlncli.h), pero se actualizó para usar el controlador Microsoft OLE DB (msoledbsql.h) para SQL Server. SQL Server Native Client (a menudo abreviado SNAC) se ha quitado de SQL Server 2022 (16.x) y SQL Server Management Studio 19 (SSMS). Para nuevos trabajos de desarrollo, no se recomiendan ni el proveedor OLE DB de SQL Server Native Client (SQLNCLI o SQLNCLI11) ni el proveedor OLE DB de Microsoft para SQL Server (SQLOLEDB) heredado. Cambie al nuevo Controlador Microsoft OLE DB para SQL Server de ahora en adelante.
Consulte también
Datos de objeto binario grande (Blob) (SQL Server)
Realizar actualizaciones parciales de los datos FILESTREAM
Evitar conflictos con operaciones de base de datos en aplicaciones FILESTREAM