Could you hire me? Contact me if you like what I’ve done in this article and think I can create value for your company with my skills.

December 13, 2007 / by Zsolt Soczó

SQL Server 2008 újdonságok 5. – streaming adatok kezelése kliensoldalról

Az előző részben elkészült a táblánk, ami streaming adatokat tud tárolni. Itt az ideje pár fotót beleszórni az adatbázisba (illetve a fájlrendszerbe).

Az adatokat beküldhetjük SQL parancsként is, a megszokott TDS csatornát felhasználva. Azonban pont azért rakták ki filerendszerbe ezeket a nagy adatokat, hogy NE TDS-en keresztül, hanem SMB-vel, a windows file megosztásán keresztül érjük el őket. Ez kicsit szokatlan lesz, hisz lesz ugyan SQL INSERT, de lesz sima fájlkezelés is a megoldásban. Mondom, menne sima SQL-lel is, de az nem lenne túl hatékony.

No, a megoldás elég bizarr lesz. Nincs ugyanis managed felület az adatok kezelésére! Legalábbis most, 2007 végén nincs. Natív API van, azt lehet rugdosni .NET-ből, interopon keresztül, ha kell.

A megoldás menete vázlatosan a következő.
1. Hozzákapcsolódunk a szerverhez SqlConnectionnel, tranzakciót indítunk, hozzárendeljük a használandó SqlCommandunkhoz. Eddig semmi új.

2. Beszúrjuk a stream metaadatait, a sima adatokat, egy insert-tel, hagyományos módon, ADO.NET-tel.

insert into Kepek (Id, Name, Photo) values (@Id, @Name, cast ('' as varbinary(max)))

A furcsa üres stringet kasztoló izé azért van a parancsban, hogy megágyazzunk az adatoknak a filerendszerben. E nélkül, ha null maradna az oszlop értéke nem jönne létre fájl a diszken, így a következő lépés se menne.

3. Visszaolvassuk a sorunkhoz tartozó stream mint fájl elérési útját és egy tranzakció azonosítót, ez kell majd a következő lépésben. Mindkettőre van egy új függvény illetve metódus (kiemelve).

select Photo.<strong>PathName()</strong> PathName, <strong>get_filestream_transaction_context()</strong> TranCtx from Kepek where Id = @Id

4. Most jön a bizarrabb rész. Kapunk egy natív függvényt, azzal lehet megnyitni trazakcionálisan a streamünket mint fájlt: OpenSqlFilestream. Mivel ő natív cucc, kell hozzá interop deklaráció, kb. így:

[DllImport(“sqlncli10.dll”, SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle OpenSqlFilestream(
string FilestreamPath,
UInt32 DesiredAccess,
UInt32 OpenOptions,
byte[] FilestreamTransactionContext,
UInt32 FilestreamTransactionContextLength,
LARGE_INTEGER_SQL AllocationSize
);

[StructLayout(LayoutKind.Sequential)]
public struct LARGE_INTEGER_SQL
{
public Int64 QuadPart;
public LARGE_INTEGER_SQL(Int64 quadPart) { QuadPart = quadPart; }
}

Ronda, de az interop már csak ilyen. Örüljünk, hogy működik. Vagy tessék használni C++-t.

A paraméterek értékét az előző pont select-je szolgáltatja, amit egy reader-rel érek el:

byte[] tranCtx = (byte[])reader[“TranCtx”];

SafeFileHandle h = OpenSqlFilestream(
(string)reader[“PathName”],
DESIRED_ACCESS_WRITE,
0,
tranCtx,
(uint)tranCtx.Length,
new LARGE_INTEGER_SQL(0))

Kapunk egy szép Handle-t, amit az interop réteg egyből be is csomagol egy SafeFileHandle-be, mert azt úgy illik (aki nem hallott a safehandle-ökről, most álljon meg, és sürgősen olvasson utána, nem hosszú a cikk).

5. A Win32 handle jó dolog, de nem kezdünk neki WriteFile API-val baromkodni, hanem kihasználjuk, hogy a FileStream könnyen összebarátkozik valahonnan szerzett File Handle-ökkel:

FileStream fsWrite = new FileStream(h, FileAccess.Write)

6. Most már csak írni kell bőszen az fsWrite-ba. Figyeljük meg, hogy ez a lényege pont a streaming elérésnek, hogy nem összerakunk egy 34 GByte-os ojjektumot, mondjuk byte[]-öt, és odavágjuk a szervenek (eleve, a kliens belehalna ebbe), hanem apránként lapátoljuk be az adatokat. Ráérősen, öregesen. Valahogy így pl.

int readed;
byte[] buff = new byte[4096];
do
{
readed = fsRead.Read(buff, 0, buff.Length);
fsWrite.Write(buff, 0, readed);
}
while (readed > 0);

Az fsRead a helyi képre van megnyitva, amit betolunk a szervernek. Teccik látni, kicsi kis darabkákban megy be a hatalmas kép.

Számos kérdés, mint izolációs szint, mi látszik ebből a fájlrendszerben, kihasználja-e a tranzakcionális NTFS-t, stb. merül még fel, ezekkel a következő részben foglalkozok.

Zárásul berakom a teljes kódot, élvezzétek. :)

(Tud valaki valami normális, kész, szép wordpress theme-et, ami úgy van belőve, hogy olyan szélesen formázza meg a szöveget, mint amekkora az ablak mérete? Elkezdtem átszabni ezt a Kubrick theme-et, de ez halál, nekem bonyolult megérteni és átírni. A kódok miatt kellene.)

using System;
using System.Data.SqlClient;
using System.IO;
using System.Data;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace FSTest
{
class Program
{
static void Main(string[] args)
{
SqlTransaction tran = null;
try
{
using (SqlConnection conn = new SqlConnection(
@”Data Source=.\sql2008;Initial Catalog=FSTeszt;Integrated Security=true;”))
{
using (SqlCommand cmd = new SqlCommand())
{
conn.Open();
tran = conn.BeginTransaction();
cmd.Connection = conn;
cmd.Transaction = tran;

foreach (string f in Directory.GetFiles(@”E:\ment\kepek\Hivatni”, “*.jpg”))
{
//Kép alapadatok beszúrása
cmd.CommandText = @”insert into Kepek (Id, Name, Photo)
values (@Id, @Name, CAST (” as varbinary(max)))”;
cmd.Parameters.Clear();
cmd.Parameters.Add(“@Name”, SqlDbType.NVarChar).Value = Path.GetFileName(f);
Guid id = Guid.NewGuid();
cmd.Parameters.Add(“@Id”, SqlDbType.UniqueIdentifier).Value = id;
cmd.ExecuteNonQuery();

//Maga a kép mentése mint streaming adat
cmd.CommandText = @”select Photo.PathName() PathName,
get_filestream_transaction_context() TranCtx from Kepek where Id = @Id”;
using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SingleRow))
{
if (reader.Read())
{
byte[] tranCtx = (byte[])reader[“TranCtx”];

using (SafeFileHandle h = OpenSqlFilestream(
(string)reader[“PathName”],
DESIRED_ACCESS_WRITE,
0,
tranCtx,
(uint)tranCtx.Length,
new LARGE_INTEGER_SQL(0)))
{

if (!h.IsInvalid)
{
using (FileStream fsWrite = new FileStream(h, FileAccess.Write))
using (FileStream fsRead = File.OpenRead(f))
{
int readed;
byte[] buff = new byte[4096];
do
{
readed = fsRead.Read(buff, 0, buff.Length);
fsWrite.Write(buff, 0, readed);
}
while (readed > 0);
}
}
else
{
int errno = Marshal.GetLastWin32Error();
Console.WriteLine(errno);
Environment.Exit(-1);
}
}
}
}

}
}
tran.Commit();
}
}
catch (Exception e)
{
tran.Rollback();
Console.WriteLine(e);
}
}

public const UInt32 DESIRED_ACCESS_READ = 0x00000000;
public const UInt32 DESIRED_ACCESS_WRITE = 0x00000001;
public const UInt32 DESIRED_ACCESS_READWRITE = 0x00000002;

[DllImport(“sqlncli10.dll”, SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle OpenSqlFilestream(
string FilestreamPath,
UInt32 DesiredAccess,
UInt32 OpenOptions,
byte[] FilestreamTransactionContext,
UInt32 FilestreamTransactionContextLength,
LARGE_INTEGER_SQL AllocationSize
);

[StructLayout(LayoutKind.Sequential)]
public struct LARGE_INTEGER_SQL
{
public Int64 QuadPart;
public LARGE_INTEGER_SQL(Int64 quadPart) { QuadPart = quadPart; }
}

[DllImport(“kernel32.dll”)]
private extern static void CloseHandle(IntPtr handle);
}
}

Could you hire me? Contact me if you like what I’ve done in this article and think I can create value for your company with my skills.

LEAVE A COMMENT

4 COMMENTS

  • tvecsey September 16, 2009

    a Kubrick helyett: http://alexgorbatchev.com/wiki/SyntaxHighlighter
    nem wordpress-hez valo, de szerintem be lehet hekkelni.

  • Philip December 28, 2009

    Szia!

    SqlFileStream-et akarok használni egy SQL CLR tárolt eljárásban, de folyton kivételre futok.
    Az assembly-t – strong name-et használva – external_access permission set-tel katalogizáltam az SQL szerverben. Tetszőleges sql parancsokkal és I/O műveletekkel tesztelve hibátlanul dolgozik. Azonban, amikor egy SqlFileStream-et próbálok használni, a következő kivételt dobja:
    Msg 6522, Level 16, State 1, Procedure SqlFileStreamTest, Line 0
    A .NET Framework error occurred during execution of user-defined routine or aggregate “SqlFileStreamTest”:
    System.ComponentModel.Win32Exception: A kérés nem támogatott
    System.ComponentModel.Win32Exception:
    a következő helyen: System.Data.SqlTypes.SqlFileStream.OpenSqlFileStream(String path, Byte[] transactionContext, FileAccess access, FileOptions options, Int64 allocationSize)
    a következő helyen: System.Data.SqlTypes.SqlFileStream..ctor(String path, Byte[] transactionContext, FileAccess access, FileOptions options, Int64 allocationSize)
    a következő helyen: System.Data.SqlTypes.SqlFileStream..ctor(String path, Byte[] transactionContext, FileAccess access)
    a következő helyen: StoredProcedures.SqlFileStreamTest()

    Van valami tipped, mi lehet a probléma?

    Üdv.
    Philip

  • Soczó Zsolt January 6, 2010

    Szia,
    úgy látom Bob már megválaszolta neked. :)

    http://social.msdn.microsoft.com/Forums/en-US/sqlnetfx/thread/62861b62-1820-47b9-8598-b4ba77ca190b

    Ha ő azt mondja nem, akkor nem. :)

  • Philip January 6, 2010

    Szia,

    köszi a választ.
    Igen, Bob megerősített, hogy nem érdemes több energiát fektetni a dologba (pedig tetszett volna ez a megoldás).

    Üdv.