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
a Kubrick helyett: http://alexgorbatchev.com/wiki/SyntaxHighlighter
nem wordpress-hez valo, de szerintem be lehet hekkelni.
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
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. :)
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.