NHibernate, Fluent NHibernate i FILESTREAM, czyli przechowywanie plików w bazie danych trochę inaczej

30.07.2013

Pytanie “przechowywać, czy nie przechowywać” pojawia się praktycznie przy okazji każdego projektu, w którym trzeba przesłać coś więcej niż proste formy do i od użytkownika. Szkoły są dwie (plus hybryda, o czym za chwilę), jednak wybór, która w danym wypadku jest najlepsza zależy w dużej mierze od specyfiki samej aplikacji oraz rozwiązań technicznych użytych w projekcie.

Pierwsza możliwość, bardzo popularna, dane opisujące plik (metadane) takie jak wielkość, nazwa, położenie itp. składowane są w bazie danych zaś sam obraz pliku składowany jest w oddzielnej lokalizacji (lokalnej lub zdalnej). Rozwiązanie to jest sprawdzone i występuje w wielu odmianach różniących się właściwie tylko rodzajem magazynu danych i sposobem dostępu (system plików, chmura, WebService itp). Minusem tego rozwiązania jest konieczność zapewnienia oddzielnych mechanizmów kontroli dostępu i kopii zapasowej dla bazy danych oraz magazynu plików.

Druga możliwość, to zapisywanie w bazie danych wszystkich informacji. Zarówno metadanych jak i samego pliku w postaci tablicy bajtów. Dzięki czemu, wszystkie dane znajdują się w jednym miejscu (kontrola dostępu i kopia zapasowa). Odbywa się to jednak kosztem wydajności oraz wielkości obsługiwanych plików.

Jeżeli korzystamy z SQL Server w wersji co najmniej 2008, to istnieje jeszcze trzecie rozwiązanie – hybrydowe, w postaci typu danych FILESTREAM, łączące zalety dwóch poprzednich metod. Metadane zapisane są w bazie, obrazy plików w oddzielnym folderze, zaś baza danych koordynuje dostęp do zasobu i obsługuje mechanizm kopii zapasowej (kopia zapasowa dotyczy zarówno samej bazy danych jak i folderu z plikami). 

Poniżej podaję przepis na obsługę FILESTREAM w Microsoft SQL Server oraz jak pogodzić ten typ z NHibernate (+Fluent NHibernate) oraz automatycznym generowaniem schematu bazy danych z plików mapowań.

  1. Uruchamiamy SQL Server Configuration Manager. Znajdujemy serwis z instancją SQL SERVER’a, dla której chcemy włączyć obsługę FILESTREAM. We Właściwościach, w zakładce FILESTREAM zaznaczamy odpowiednie pozycje:
    image 
    Stan ostatniego checkboxa wedle uznania.
  2. Restartujemy SQL SERVER.
  3. Tworzymy (lub modyfikujemy istniejącą) bazę danych. Na początku dodajemy Filestream w zakładce Filegroups:
    image 
    W zakładce General wpisujemy nazwę bazy danych i dodajemy nowy plik bazy danych typu Filestream Data i ustawiamy mu wcześniej stworzony Filegroup:
    image 
  4. W SQL Management Studio łączymy się z serwerem. Wywołujemy okno Properties serwera i w zakładce Advanced ustawiamy FIle Stream Access Level:
    image 
    Zależnie od preferencji wybieramy Transact SQL-access enabled lub Full access enabled.

Teraz czas na NHIbernate i Fluent NHibernate. Przykładowy obiekt:

public class Foo
{
    public virtual Guid Id { get; set; }

    public virtual string Name { get; set; }

    public virtual byte[] Content { get; set; }
}

I jego plik mapowania (albo analogiczny plik hbm.xml):

public class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        Id(x => x.Id).GeneratedBy.GuidComb().CustomSqlType("uniqueidentifier ROWGUIDCOL");
        Map(x => x.Name).Length(1024).Not.Nullable();
        Map(x => x.Content).CustomSqlType("VARBINARY (MAX) FILESTREAM").Not.Nullable();
    }
}

Jeżeli sami tworzymy schemat bazy danych i generujemy skrypty SQL bez pomocy Fluent NHibernate to możemy pominąć CustomSqlType przy mapowaniu Id. W innym wypadku, bez ROWGUIDCOL, podczas generowania schematu bazy danych otrzymamy wyjątek:

A table with FILESTREAM column(s) must have a non-NULL unique ROWGUID column

Teraz mamy szybki i elastyczny mechanizm do składowania plików. Jednocześnie nie musimy martwić się o tworzenie kopi zapasowej i proces jej przywracania. SQL Server wykonana za nas całą brudną robotę. Wystarczą standardowe polecenia Backup/Restore.