Archiwum | C# RSS for this section

MongoDb i C#

W dzisiejszym wpisie postaram się przybliżyć bazę MongoDB i sposób korzystania z niej za pomocą C#. Nie widzę jakiegoś wielkiego zainteresowania tą bazą w polskiej blogosferze, a szkoda.

MongoDb jest obecnie chyba najbardziej popularną bazą danych NoSQL.
Co to jest NoSQL? Jak sama nazwa wskazuje jest to silnik który nie używa SQLa do zapytań.
Po co komu baza, która posiada jakiś autorski dialekt zapytań, i nie korzysta z dobrze znanej składni SQLa? Otóż są zastosowania w których bazy NoSQL sprawują doskonale. Wszędzie, gdzie mamy dużo (lub bardzo dużo) danych, mało (najlepiej w ogóle) relacji, warto zastanowić się nad Mongo.

Trzeba zaznaczyć, że MongoDB nie jest „silver bullet”, próba zastąpienia relacyjnej bazy danych w systemie mającym relacje między tablicami zakończy się wielkim rozczarowaniem uzyskaną wydajnością.

Tworzeniem i wsparciem silnika MongoDb zajmuje się firma 10gen. I robi to bardzo dobrze. Dostępne są drivery dla wszystkich liczących się języków programowania, driver dla .NET jest przyzwoicie napisany. Kilka miesięcy temu zostało dodane wsparcie dla Linq. Kod pozwalający na połączenie z bazą, dodanie kilku dokumentów do kolekcji, to zaledwie kilka linii.

Wspomniałem już, że Mongo nie jest bazą relacyjną. Co prawda, na upartego można próbować na piechotę łączyć dane z różnych kolekcji, ale wydajność takich operacji będzie niska.

Dane przechowywane są w formacie JSON, a dokładnie w klonie tego formatu pozwalającym na zapis danych binarnych, czyli BSON.
Dokumenty BSON są przechowywane we wspomnianych już kolekcjach. Na upartego można porównać dokument Mongo z rekordem relacyjnej bazy danych, a kolekcję z tablicą. I tu trzeba wspomnieć o rzeczy bardzo ważnej – kolekcje nie posiadają zadanego sztywnego schematu, można w nich przechowywać dowolne dokumenty BSON.

Niestety Mongo posiada także i ograniczenia. Uruchomione na 32-bitowym systemie, pozwala na przechowanie nieco ponad 2GB danych.
Jest to związane ze sposobem, w jaki dane są zapisywane w plikach bazy. Mongo mapuje plik w pamięci, a 32-bitowy system operacyjny nie jest w stanie zaadresować większych obszarów. Tak więc, jeśli baza przekroczy 2GB (cała baza, czyli dane + indeksy), mongo zakończy pracę mało przyjemnym błędem.

Jeśli komuś chodzi po głowie deployment bazy wraz z desktopowym klientem, niech się lepiej dobrze zastanowi.

Uruchomienie mongo jest bardzo proste:

mongod --dbpath "sciezka do folderu z danymi" --logpath "sciezka do folderu na logi"

aby zainstalować mongo jako usługę systemową należy dodać parametr –install do tych powyżej
Zainstalowaną usługę warto uruchomić:

net start MongoDB

Polecenia trzeba wykonywać za pomocą linii komend z uprawieniami administracyjnymi.

Jak już wspomniałem, dokument mongo nie ma ustalonego schematu. Do kolekcji można dodać dowolne dane, byle były opakowane w JSON.

Wszystko pięknie, ale czasami wygodnie jest zdefiniować sobie klasę danych, która będzie automatycznie serializowana i deserializowana, tak jak robią to nHibernate, czy Entity Framework.

Sterownik dla .NET zapewnia taką funkcjonalność.

pubic class KlasaA
{
  public ObjectId _id {get; set;}
  public List<KlasaB> ObiektyKlasyB {get; set;}
}

Jak widać, dane mogą być zagnieżdżone. KlasaA może zawierać listę obiektów klasy B

KlasaA, jako „root” powinna mieć zdefiniowane pole które będzie identyfikatorem. Najprościej jest dodać właściwość _id typu ObjectId. Ale za pomocą atrybutów można wyznaczyć praktycznie dowolne pole aby było identyfikatorem.

Atrybutów którymi można udekorować klasy i ich właściwości jest znacznie więcej, znajdują one się w przestrzeni nazw MongoDB.Bson.Serialization.Attributes. Bardzo przydatnym atrybutem jest BsonIgnoreExtraElements, dzięki któremu nie jest wyrzucany wyjątek, kiedy klasa nie zawiera pola które znajduje się dokumencie JSON.

Połączenie z bazą:

var connectionString = "mongodb://localhost";
var client = new MongoClient(connectionString);
var server = client.GetServer();
var database = server.GetDatabase("NAZWA_BAZY_DANYCH");

Baz danych możne być oczywiście kilka na serwerze.

Zdefiniuję teraz prostą encję:

public class User
{
  public ObjectId _id {get; set;}
  public string Name {get; set;}
}

Pobieramy kolekcję:

var users = database.GetCollection<User>("Users");

Dodajemy dokument do kolekcji

var user = new User() {Name="Arek"};
users.Insert(user);

Zapytanie:

var arki = users.AsQueryable().Where(u=> u == "Arek");

Niestety, na dzień dzisiejszy nie wszystkie funkcje LINQ są wspierane. Aby na
przykład pogrupować dokumenty trzeba się trochę nagimnastykować. Trzeba skorzystać z JavaScriptu. Takie zapytania to dosyć obszerny temat, postaram się je opisać w osobnym wpisie.

Jeśli pola używamy jako kryterium wyszukiwania, warto założyć na nim indeks. Poprawi to znacznie wydajność zapytań.

Robi się to bardzo łatwo:

users.EnsureIndex("Name");

To wszystko jeśli chodzi o podstawowe informacje. Mam nadzieję, że wystarczą do uruchomienia i rozpoczęcia eksperymentów z Mongo. A kolejnej notce omówię
grupowanie danych, i prace z klasę BsonDocument.

.NET i FTP.

Pobieranie pliku z serwera FTP:

var request = FtpWebRequest.Create(ftpServer) as FtpWebRequest;
setCredentials(request);
FtpWebResponse response = (FtpWebResponse)request.GetResponse();

using (var destStream = new System.IO.BufferedStream(System.IO.File.Create(destinationFile)))
using (var srcStream = new System.IO.BufferedStream(response.GetResponseStream()))
{
  // Odczyt z srcStream i zapis do destStream
}

BufferedStream zapewnia elegancki sposób pobierania i zapisywania danych. Wewnątrz using, można operować na pojedynczych bajtach. Jednak odczyt z serwera FTP i zapis do pliku odbywa się w blokach danych – właśnie dzięki BufferedStream.

A co jeśli chcielibyśmy pokazać zaawansowanie operacji pobierania? Do tego potrzebujemy wielkości pliku. Niestety każda próba pobrania wielkości danych ze strumienia response.GetReponseStream() kończy się mało przyjemnym wyjątkiem. Szczęśliwie jest inna droga:

FtpWebRequest request = (FtpWebRequest)WebRequest.Create(ftpServer);
request.Method = WebRequestMethods.Ftp.GetFileSize;
setCredentials(request);
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
long fileSize = response.ContentLength;