Artykuł

kwi 07 2012
0

LINQ i DataContext - wycofywanie zmian w bazie

Ostatnimi czasy rozpocząłem naukę programowania pod Windows Phone 7. Szybko okazało się, że przy pisaniu jednej z testowych aplikacji, warunkiem koniecznym jej dalszego rozwoju stał się dostęp do bazy danych. Programowanie na platformę mobilną wiążę się jednak z wieloma ograniczeniami w stosunku do programowania aplikacji desktopowych i oczywiście nie ominęły one również samej bazy danych.

W przypadku Windows Phone, możemy korzystać zatem tylko z SQL Server CE i wykorzystywać w tym celu wyłącznie LINQ to SQL współpracujące z DataContext. Cały ten mechanizm jest jednak dużo bardziej ograniczony, aniżeli możliwości do których przyzwyczaiły nas klasyczne zapytania wyrażone językiem T-SQL.

Dla mnie osobiście, największą bolączką jest brak mechanizmu, który w prosty sposób pozwala na wycofywanie wprowadzonych zmian. Dlaczego o tym wspominam? Ano dlatego, że transakcje wykonywane na kontekście danych, zapisywane są do bazy danych dopiero w momencie wykonania metody SubmitChanges. Jest to podejście stricte transakcyjne i w gruncie rzeczy sprawdzą się nawet dobrze w tym systemie. Niestety mimo, że istnieje metoda która symuluje commit, brakuje tutaj metody typu rollback. Prowadzi to do tego, że nawet jeśli nie zatwierdzimy zmian od razu, to zostaną one zapisane przy dowolnym późniejszym commicie (chyba że wyłączymy aplikację, ale chyba nie o to chodzi). Jest to odrobinę dziwne, ale na szczęście istnieje pewne obejście tego problemu, którym zaraz się Wami podzielę.

Implementacja

Ponieważ cały problem właściwie zdefiniowałem już we wstępie do tekstu, to w gruncie rzeczy możemy od razu przystąpić do implementacji. W przypadku tworzenia aplikacji wykorzystującej kontekst danych, naturalnym procederem jest tworzenie nowej klasy, która dziedziczy po klasie DataContext. Tak również postąpimy w tym przypadku, ponieważ będzie to dla nas świetne miejsca na dodanie metody wycofującej zmiany wprowadzone w kontekście danych.

Standardowa klasa, która rozszerza DataContext, wygląda mniej więcej tak:

class CMyDataContext : DataContext
{
	public CMyDataContext(string sConnectionString)
		: base(sConnectionString)
	{
	}

	// Tabele

}

Naszym celem będzie dodanie w tym miejscu metody UndoChanges, która będzie działać niczym Rollback w klasycznych transakcjach znanych z bazą danych.

Spójrzmy zatem na kod:

public void UndoChanges()
{
	ChangeSet oChangeSet = GetChangeSet();
	foreach (var oInsert in oChangeSet.Inserts)
	{
		GetTable(oInsert.GetType()).DeleteOnSubmit(oInsert);
	}
	foreach (var oDelete in oChangeSet.Deletes)
	{
		GetTable(oDelete.GetType()).InsertOnSubmit(oDelete);
	}
	List<ITable> oUpdatedTables = new List<ITable>();
	foreach (var oUpdate in oChangeSet.Updates)
	{
		ITable oTable = GetTable(oUpdate.GetType());
		if (oUpdatedTables.Contains(oTable))
		{
			continue;
		}
		oUpdatedTables.Add(oTable);
		Refresh(RefreshMode.OverwriteCurrentValues, oTable);
	}
    SubmitChanges();
}

Metoda nie przyjmuje żadnych argumentów, a sam proces wycofywania zmian przebiega nie jako trzy-etapowo.

Na wstępie pobieramy obiekt, zawierający wszystkie niezatwierdzone zmiany w bazie danych. Obiekt ten zawiera zarówno elementy przeznaczone do dodania, jak i te skierowane do usunięcia oraz modyfikacji. Dlatego też proces przebiega trzy-etapowo, ponieważ musimy rozprawić się z każdą grupą z osobna.

Najpierw zajmujemy się insertami w liniach 4-7. Z naszego obiektu, który zabiera wszystkie wykonane zmiany, wyciągamy kolekcję wszystkich insertów. Ponieważ operuje ona na kolekcji obiektów, skorzystamy ze słowa kluczowego var aby w sprytny sposób uzyskać odpowiedni typ, bez zbędnego rzutowania. Dzięki temu, rozwiązanie będzie również uniwersalne, niezależnie od zdefiniowanej struktury bazy danych. Na temat var możecie poczytać więcej tutaj.

Wróćmy jednak do naszej pętli. Dla każdego inserta który przetwarzanego w jej wnętrzu, wywołujemy dość złożoną operację. Na początku, pobieramy tabelę dla typu prezentowanego przez dany insert, a następnie wykonujemy operację DeleteOnSubmit co spowoduje usunięcie inserta przy operacji Submit.

Wiem, że brzmi to odrobinę dziwnie i pokrętnie, ale jest to prawdopodobnie jedna z lepszy opcji wycofania zmian ze stosunkowo niskim narzutem pamięci i liczby wykonywanych operacji. LINQ działa w tej materii na tyle w inteligentny sposób, że jeśli w kolejce operacji do wykonania wykryje insert i delete, to w istocie żadne operacje nie zostaną wykonane.

Analogicznie postępujemy z kolekcją elementów przeznaczonych do usunięcia (8-11). W tym wypadku musimy je z kolei dodać z powrotem, więc wykonujemy operację InsertOnSubmit. Również w tym przypadku LINQ zniweluje obie operacje i nie zrobi nic.

Odrobinę innego podejścia wymagają elementy, które istniały już w bazie danych, ale podczas tej sesji zostały w niej zmodyfikowane. W tym przypadku pobieramy kolejną kolekcję, ale wykonujemy inne przetwarzanie wewnątrz pętli foreach (12-22).

Na początku musimy utworzyć listę tabel, dla których już przywróciliśmy dane. Robimy to dlatego, ponieważ w przypadku kolekcji Updates poszczególne tabele mogą się powtarzać. Dzięki temu unikniemy wielokrotnego wykonania tych samych operacji.

Do pobrania tabeli użyjemy znanej już wcześniej konstrukcji (15). Następnie sprawdzamy czy korzystaliśmy już wcześniej z wybranej tabeli. Jeśli nie, to dodajemy tabelę do naszej lokalnej kolekcji (20), a następnie odświeżamy wszystkie dane w wybranej tabeli, stosując ustawienie nadpisujące wszystkie aktualne wartości.

Na samym końcu, dla pewności zapisujemy wszystkie wykonane zmiany (23).

Oczywiście powyżej zaprezentowany kod, to nie jedyne wyjście z powyższej sytuacji, ale wydaje się być dość rozsądną opcją. Odrobinę skrajnym rozwiązaniem może być ponowne zainicjalizowanie kontekstu danych, które może jednak wiązać się ze sporym narzutem pamięci oraz koniecznością odnowienia wcześniej utworzonych dowiązań, co w kontekście powyższej 24-liniowej metody, wydaje się być raczej dzikim rozwiązaniem.

Data ostatniej modyfikacji: 07.04.2012, 18:58.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus