WCF Data Services

Autor: Piotr Zieliński

Wymagane oprogramowanie

  • Visual Studio 2010 RC,
  • SQL Server 2008.

Wprowadzenie

W środowisku rozproszonym dostęp do danych może stanowić poważny problem. Korzystając z klasycznego WCF, użytkownicy ograniczeni są wyłącznie do wywoływania wyeksponowanych metod. WCF Data Services to usługa sieciowa oferująca bardzo bogaty interfejs selekcji danych. Całość oparta jest na Open Protocol Data. Istotną cechę WCF Data Services stanowi interoperacyjność, czyli możliwość korzystania z usługi za pomocą klientów stworzonych w różnych technologiach.

WCF Data Service działa na zasadzie architektury REST (Representational State Transfer). Architektura REST została wprowadzona po raz pierwszy przez Roya T. Fieldinga w jego pracy doktorskiej (rok 2000). Rozwiązania zgodne z REST składają się z serwera i klienta. Klient inicjuje zapytanie i wysyła je do serwera. Zadaniem serwera jest oczywiście odpowiednie przetworzenie zapytania i wygenerowanie odpowiedzi. Ma on dostęp do tzw. zasobów (np. relacyjnej bazy danych). Z kolei reprezentacja zasobu (representation of resource) to dokument przesyłany do klienta w ustalonym formacie (przeważnie XML). Ponadto zasoby muszą być jednoznacznie identyfikowane, np. za pomocą URI. Klient, pytając serwer o jakieś zasoby, zmienia swój stan aplikacji. By przejść do innego stanu, wywołuje nowe zapytanie z innym URI. To właśnie z przejścia (tranzycji) stanów wzięła się nazwa REST − Representational State Transfer.

Architektura nie narzuca protokołu komunikacji między klientem a serwerem, jednak zwykle jest nim HTTP. W przypadku HTTP identyfikator zasobu to znany wszystkim adres w postaci http://www.localhost.com/users. W zależności od wywołanej metody HTTP, serwer powinien wykonać inną operację na zasobach:

  • HTTP POST odpowiada za tworzenie zasobu,
  • HTTP GET − za selekcję danych,
  • HTTP PUT − za aktualizację,
  • HTTP DELETE – za usunięcie danych.

Przykładowo, aby uzyskać listę użytkowników, klient może wysłać następujące zapytanie HTTP:

GET /users HTTP/1.1

Host: localhost

Accept: application/xml

Architektura zgodna z REST musi spełniać również kilka warunków:

  • Bezstanowość. To klient odpowiada za przechodzenie z jednego stanu do kolejnego, a nie serwer. Każde zapytanie musi zawierać komplet danych, ponieważ na serwerze nie są przechowywane informacje o klientach (takie jak nazwa oraz hasło aktualnie zalogowanego użytkownika). Bezstanowość znacznie zwiększa skalowalność aplikacji, gdyż ułatwia wprowadzenie wielu serwerów bez przekazywania każdemu aktualnego stanu klienta. Ponadto brak stanu to również mniejsze zapotrzebowanie na pamięć.
  • Buforowanie danych. W REST przewidziano buforowanie danych przez klienta. Każda odpowiedź musi zatem zawierać zasady buforowania, aby klient korzystał z poprawnych danych.
  • SoC (separation of concerns). Klient i serwer muszą być od siebie niezależne. Przykładowo klient nie musi wiedzieć, jakiego typu baza dany jest wykorzystywana przez serwer, wystarczy, że zna ujednolicony protokół komunikacji z nim.
  • Budowa warstwowa. Klient nie musi się łączyć z jednym, konkretnym serwerem. REST przewiduje korzystanie np. z serwerów pośredniczących, które mogą dokonywać buforowania lub równoważenia obciążenia (loadbalancing).
  • Ujednolicony protokół komunikacji. Ma on za zadanie, jak już wspomniano, odseparować klienta od serwera.

Architektura WCF Data Services wygląda więc następująco:

Rysunek 1. Zasada działania WCF Data Service.

Data Services Framework umożliwia wyeksponowanie danych uzyskanych za pomocą Entity Framework. Ponadto API przewiduje możliwość pisania własnych klas (tzw. providerów) w celu uzyskiwania danych z różnych baz, nawet tych nierelacyjnych.

Microsoft dostarcza biblioteki klienckie (.NET, Silverlight, Ajax) znacznie ułatwiające korzystanie z WCF Data Services. Dzięki temu użytkownik nie musi ręcznie definiować żądań HTTP. Ponadto dostępne są również biblioteki dla PHP i JAVA.

Jeśli chodzi o stronę serwera, aktualnie wiele rozwiązań wspiera już WCF Data Services, m.in. SharePoint Server 2010, Excel 2010 (przez SQL Server PowerPivot), Windows Azure Storage oraz SQL Server 2008 R2.

Rysunek 2. Entity Framework a WCF Data Services.

W artykule przedstawiamy zastosowanie WCF Data Services w połączeniu z Entity Framework. Struktura wykorzystanej bazy danych wygląda następująco:

Rysunek 3. Tabele wykorzystane w artykule.

Implementacja WCF Data Services

Utworzenie WCF Data Services odbywa się w pełni automatycznie. Należy wybrać odpowiedni projekt i postępować zgodnie z instrukcjami. Oto opis całego procesu, krok po kroku:

  1. Tworzymy projekt WCF Service Application i usuwamy domyślnie wygenerowaną usługę Service1.

    Rysunek 4. Pierwszy krok − utworzenie usługi WCF.

  2. Dodajemy ADO .NET Entity Data Model (Add -> New Item -> Data ->ADO .NET Entity Data Model):

    Rysunek 5. Dodawanie modelu encji.

  3. Wybieramy pierwszą opcję Generate from database, ponieważ mamy już utworzoną strukturę tabel i na jej podstawie chcemy wygenerować stosowny model. Można oczywiście wykorzystać inną metodę: najpierw utworzyć model encji, a potem wygenerować bazę danych. W tym artykule nie ma znaczenia, jakiego sposobu użyjemy.

    Rysunek 6. Generacja modelu encji na podstawie tabel.

  4. Wybieramy prawidłowe połączenie z bazą danych i klikamy na przycisk NEXT. Zaznaczamy wszystkie tabele oraz niesystemowe procedury. Gdy klikniemy na przycisk Finish, wygenerowany model encji powinien wyglądać następująco:

    Rysunek 7. Model encji.

  5. Następnie dodajemy już właściwą usługę sieciową WCF Data Service (Add -> New Item -> Web -> WCF Data Service):

    Rysunek 8. Utworzenie WCF Data Service.

  6. Wygenerowany plik (SalesSystemService.svc) stanowi usługę sieciową. Teraz należy jeszcze wskazać źródło danych. W tym celu edytujemy plik SalesSystemService.svc.cs:

public class SalesSystemService : DataService<DataServicesEntities>
{
}

Jak widać, wystarczy podmienić parametr klasy generycznej DataService na wygenerowany ObjectContext EF (czyli DataServicesEntities).

  1. Ostatnim krokiem jest zdefiniowanie praw dostępu. Załóżmy, że chcemy mieć prawo czytania wierszy zawartych w zbiorach encji Products, Invoices oraz OrderItems. W tym celu trzeba w statycznej metodzie (wygenerowanej przez IDE) wywołać SetEntitySetAccessRule:
public static void InitializeService(DataServiceConfiguration config)
{                                    

config.SetEntitySetAccessRule("Products", EntitySetRights.AllRead);

config.SetEntitySetAccessRule("Invoices", EntitySetRights.AllRead);

config.SetEntitySetAccessRule("OrderItems", EntitySetRights.AllRead);
}

Domyślnie jakikolwiek dostęp do encji (nawet tylko do odczytu) jest zabroniony. Dzięki temu usługa spełnia zasadę „Principle of leastprivilege”, mówiącą, że należy zaczynać od najmniejszych przywilejów. Dopiero gdy jesteśmy pewni, że dostęp do jakieś encji jest absolutnie wymagany, nadajemy odpowiednie prawa (również zaczynając od minimalnych). Za pomocą SetEntitySetAccessRule można nadać różne przywileje:

  • None – brak dostępu,
  • ReadSingle – przywilej czytania pojedynczych wierszy,
  • ReadMultiple – przywilej czytania zbioru danych (kolekcje),
  • WriteAppend – przywilej wstawiania nowych wierszy,
  • WriteReplace – przywilej aktualizowania wierszy,
  • WriteDelete – przywilej usuwania wierszy,
  • WriteMerge – przywilej scalania danych,
  • AllRead – wszelkie prawa odczytu (ReadSingle, ReadMultiple),
  • AllWrite – wszelkie prawa zapisu (WriteAppend, WriteReplace, WriteDelete, WriteMerge),
  • All – wszystkie prawa (zarówno zapisu, jak i odczytu).

Analogicznie jak w przypadku encji, dostęp do tzw. operacji (o nich szczegółowo później) może być zmieniany metodą SetServiceOperationAccessRule. ServiceOperationRights zawiera głównie typy praw opisane wcześniej (None, ReadSingle, ReadMultiple, AllReadAll). Jedynym dodatkowym typem jest OverrideEntitySetRights − nadpisanie praw, w przypadku gdy zostały już jawnie nadane encjom za pomocą SetEntitySetAccessRule.

Po skompilowaniu i uruchomieniu serwera usługa sieciowa jest gotowa do użycia. Warto zauważyć, że nie było potrzeby pisania metod ani nawet konfiguracji WCF.

Korzystanie z WCF Data Service przez przeglądarkę internetową

Jak już wspomniano na początku, komunikacja odbywa się za pośrednictwem protokołu HTTP. Oznacza to, że przetestować taką usługę sieciową można, wykorzystując nawet zwykłą przeglądarkę internetową.

Wszelkie zapytania należy wykonywać za pomocą odpowiedniego URI, składającego się z podstawowego adresu usługi oraz konkretnego zapytania. Przy założeniu, że serwer używa portu 5744, adres bazowy wygląda następująco:

http://localhost:5744/Services/SalesSystemService.svc/

Spróbujmy wykonać jakieś operacje na bazie danych:

  • Zwrócenie wszystkich produktów:

    http://localhost:5744/Services/SalesSystemService.svc/Products

  • Zwracanie encji o podanej wartości klucza głównego:

    http://localhost:5744/Services/SalesSystemService.svc/Products(4)

  • Selekcja konkretnej właściwości encji:

    http://localhost:5744/Services/SalesSystemService.svc/Products(1)/Description

  • Selekcja kolekcji przez klucz obcy. W przykładowej bazie dołączonej do artykułu znajdują się tabele Invoices oraz OrderItems. OrderItems za pomocą klucza obcego odwołują się do Invoices. Pojedyncza encja Invoice zawiera zatem kolekcje OrderItems. Można się do niej dostać za pomocą:

    http://localhost:5744/Services/SalesSystemService.svc/Invoices(3)/OrderItems

  • Czasami w celu zwiększenia wydajności użytkownik może chcieć uzyskać zarówno zbiór faktur, jak i produktów zamówionych w ramach faktury. Innymi słowy chce, aby wynik zawierał i podstawową encję, i połączoną przez klucz kolekcję. Operację taką często nazywa się ładowaniem zachłannym (eagerloading):

    http://localhost:5744/Services/SalesSystemService.svc/Invoices?$expand=OrderItems

    Warto wspomnieć, że operator JOIN nie jest wspierany przez WCF Data Services i można korzystać wyłącznie z relacji zdefiniowanych na serwerze. Mechanizm $expand jest jedną z alternatyw dla JOIN.

  • Wybieranie danych z nałożonym filtrem (produkty z kodem kreskowym równym 4242):

    http://localhost:5744/Services/SalesSystemService.svc/Products?$filter=BarCode eq '4242'

Oprócz operatora równości, mamy również do dyspozycji:

Tabela 1. Operatory logiczne.

Operator Opis Przykład
eq operator równości /Products?filter=City eq 'procesor'
ne negacja równości (nierówność) /Products?filter=Name ne 'procesor'
gt większy niż /Products?$filter=BasePricegt 20
ge większy niż lub równy /Products?$filter=BasePricege 800
lt mniejszy niż / Products?$filter=BasePricelt 1
le mniejszy niż lub równy /Producst?$filter=BasePrice le 20
and koniunkcja /Products?filter=BasePrice le 20 and BasePricegt 10
or alternatywa /Products?filter=BasePrice le 20 or BasePricegt 10
not negacja /Products?$ ?$filter=not endswith(BarCode,'100')

Tabela 2. Operatory arytmetyczne.

Operator Opis Przykład
add dodawanie /Products?filter=BasePrice add 5 gt 10
sub odejmowanie / Products?filter=BasePrice sub 5 gt 10
mul mnożenie / Products?$filter=BasePricemul 800 gt 2000
div dzielenie / Products?$filter=BasePricediv 10 eq 4
mod reszta z dzielenia / Products?$filter=BasePricemod 10 eq 0

Tabela 3. Pozostałe operatory.

Operator Opis Przykład
( ) Grupowanie, priorytetowanie / Products?filter=(BasePrice sub 5) gt 10

Ponadto programista dostaje szereg funkcji:

Tabela 4. Operacje na łańcuchu znaków (string).

boolsubstringof(string p0, string p1)
boolendswith(string p0, string p1)
boolstartswith(string p0, string p1)
intlength(string p0)
intindexof(string arg)
string insert(string p0, intpos, string p1)
string remove(string p0, intpos)
string remove(string p0, intpos, int length)
string replace(string p0, string find, string replace)
string substring(string p0, intpos)
string substring(string p0, intpos, int length)
stringtolower(string p0)
stringtoupper(string p0)
stringtrim(string p0)
string concat(string p0, string p1)

 

Tabela 5. Operacje na dacie.

intday(DateTime p0)
inthour(DateTime p0)
intminute(DateTime p0)
intmonth(DateTime p0)
intsecond(DateTime p0)
intyear(DateTime p0)

 

Tabela 6. Przybliżanie.

doubleround(double p0)
decimalround(decimal p0)
doublefloor(double p0)
decimalfloor(decimal p0)
doubleceiling(double p0)
decimalceiling(decimal p0)

 

Tabela 7. Operacje na typach − rzutowanie oraz sprawdzanie czy encja jest podanego typu.

boolIsOf(type p0)
boolIsOf(expression p0, type p1)
<p0>Cast(type p0)
<p1> Cast(expression p0, type p1

Większość funkcji powinna być zrozumiała po obejrzeniu sygnatury. Kilku słów komentarza wymaga jednak funkcja IsOf.

Po co sprawdzać typ, skoro odwołujemy się w URI do konkretnego zestawu encji (np. Products)? Należy pamiętać, że Entity Framework wspiera mechanizm dziedziczenia. Wyobraźmy sobie strukturę tabel, w której oprócz encji Products mielibyśmy DiscounctedProducts dziedziczącą po Products. W takim wypadku musimy mieć możliwość sprawdzenia typu podczas selekcji. Zwrócenie tylko encji typu DiscountedProducts wyglądałoby więc następująco:

/Products$filter=isof(‘DiscountedProducts)

  • Sortowanie encji na podstawie wskazanej kolumny:

    http://localhost:5744/Services/SalesSystemService.svc/Products?$orderby=BasePrice

  • Pomijanie wierszy (skip) oraz wybieranie tylko określonej liczby wpisów z wyniku (top):

    http://localhost:5744/Services/SalesSystemService.svc/Products?$skip=1&&$top=2

Konfiguracja usługi

Jak już wspomniano, WCF Data Service zawiera metodę statyczną (InitializeService), w której można konfigurować zachowanie usługi. W tej części artykułu zostaną zaprezentowane właściwości, które można ustawiać w tej metodzie:

  • UseVerboseErrors – określa, czy szczegółowa informacja zostanie zwrócona do klienta w przypadku wystąpienia błędu.
  • MaxBatchCount – określa maksymalną liczbę operacji w pojedynczym żądaniu (tzw. batch). Dzięki temu można w jednym zapytaniu wysłać do serwera kilka operacji, co znacząco zwiększa wydajność aplikacji. Właściwość MaxBatchCount określa, ile maksymalnie może być takich operacji w pojedynczym żądaniu. Aby wykorzystać mechanizm, należy po stronie klienta ustawić:
m_Context.SaveChangesDefaultOptions = System.Data.Services.Client.SaveChangesOptions.Batch;

Innym sposobem jest wskazanie odpowiedniego parametru podczas zatwierdzania zmian:

m_Context.SaveChanges(System.Data.Services.Client.SaveChangesOptions.Batch);
  • MaxChangesetCount – określa maksymalną liczbę zmian, które mogą być zwarte w rejestrze.
  • MaxExpandCount – określa maksymalną liczbę encji, które mogą być dołączone za pomocą zapytania $expand.
  • MaxExpandDepth – określa maksymalną głębokość zagnieżdżenia $expand.
  • MaxResultsPerCollection – określa maksymalną liczbę encji, które mogą być zwrócone przez serwer.

Przykład:

public static void InitializeService(DataServiceConfiguration config)
{
config.MaxResultsPerCollection = 5;
config.MaxBatchCount = 6;
config.MaxChangesetCount = 10;
config.MaxExpandCount = 4;
config.MaxExpandDepth = 4;            
}

Własne operacje

Dotychczas wszelkie zapytania były tworzone przez użytkownika usługi. W przypadku bardzo długich i skomplikowanych zapytań warto jednak stworzyć gotową metodę, którą użytkownik by wywoływał. Po co za każdym razem ma kłopotać się z tworzeniem skomplikowanego zapytania? Ponadto we własnych metodach można zawrzeć logikę biznesową. Tworzenie własnej operacji nie różni się praktycznie niczym od napisania standardowej metody. Wystarczy przestrzegać kilku zasad:

  • metoda musi być opatrzona atrybutem WebGetAttribute lub WebInvokeAttribute,
  • metoda może zwracać IEnumerable<T>, IQueryable<T> lub void.

 Spróbujmy więc zaimplementować prostą metodę zwracającą listę produktów o wskazanym kodzie kreskowym:

[WebGet()]
public IEnumerable<Entities.Product> GetProductsByBarCode(string barCode)
        {
return CurrentDataSource.Products.Where((product) =>product.BarCode == barCode);
}

Podobnie jak w przypadku encji, odpowiednie prawo dostępu (do odczytu) musi być nadane:

config.SetServiceOperationAccessRule("GetProductsByBarCode", ServiceOperationRights.AllRead);

Wywołanie operacji metodą GET wygląda następująco:

http://localhost:5744/Services/SalesSystemService.svc/GetProductsByBarCode?barCode='4242'

Korzystanie z usługi z poziomu aplikacji

Użycie WCF Data Service w aplikacji jest jeszcze łatwiejsze. Wszystkie operacje sprowadzają się do zwykłych zapytań LINQ, a całość wygląda tak, jakbyśmy korzystali z lokalnej bazy danych w architekturze dwuwarstwowej. Zacznijmy jednak od początku:

  1. Tworzymy aplikację kliencką, np. typu WPF.

  2. Dodajemy referencję do usługi. Klikamy prawym przyciskiem myszy na węzeł References w Solution Explorer i z menu kontekstowego wybieramy Add Service Reference:

    Rysunek 9. Dodawanie referencji do usługi.

  3. W tej chwili można stworzyć już kontekst usługi i przejść do pobierania danych.

  • Wybieranie wszystkich danych
Uri serviceUri = new Uri(System.Configuration.ConfigurationManager.AppSettings["DataServiceAddress"]);            

Sales.DataServicesEntities context = new Sales.DataServicesEntities(serviceUri);
dataGrid1.ItemsSource = m_Context.Products;

Warto zwrócić uwagę, że tu, inaczej niż w przypadku klasycznej usługi sieciowej, należy podać w parametrze konstruktora jej adres. Dla większej elastyczności umieściłem go w pliku app.config, aby każdy mógł łatwo go podmienić.

  • Selekcja wybranych wierszy

Wszelkie operacje wykonuje się analogiczne do zwykłego Entity Framework. Aby znaleźć produkty o podanym kodzie kreskowym, wystarczy użyć metody WHERE:

this.m_Context.Products.Where((product) =>product.BarCode == productSearch.BarCode);
  • sortowanie danych

W celu zwrócenia uporządkowanego zbioru należy użyć metody OrderBy:

m_Context.Products.OrderBy((product) = >product.BasePrice);
  • Operacje wstawiania, aktualizacji oraz usuwania

Oprócz selekcji danych, można również dokonywać wstawiania, aktualizowania oraz usuwania wierszy. Przykładowo, w celu dodania nowego produktu należy wywołać:

m_DataContext.AddToProducts(Product);

Analogiczne, by usunąć encję, wystarczy:

m_DataContext.DeleteObject(Product);

Aktualizacja obiektu już dodanego do kontekstu:

m_Context.UpdateObject(Product)

Na końcu oczywiście trzeba zatwierdzić zmiany za pomocą metody SubmitChanges:

m_DataContext.SaveChanges();
  • Wywoływanie operacji

Wywoływanie własnych operacji jest trochę bardziej skomplikowane, ponieważ nie są generowanie podczas dodawania referencji do usługi w Visual Studio. Programista sam musi wywołać konkretny URI za pomocą metody Execute:

http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataservices/thread/86576348-f29c-418a-81b3-adb59a84fa3e

string uri= System.Configuration.ConfigurationManager.AppSettings["DataServiceAddress"];
uri+=string.Format("/GetProductsByBarCode?barCode='{0}'",productSearch.BarCode);                
var result = this.m_Context.Execute<Sales.Product>(new Uri(uri));
  • Brak wsparcia dla JOIN

Operator JOIN (LINQ) nie jest wspierany w WCF Data Service. Zamieszczone zapytanie LINQ nie zostanie przetłumaczone na prawidłowy URI:

var invoices = from orderItem in m_Context.OrderItems
join invoice in m_Context.Invoices on
orderItem.ID_INVOICE equals invoice.ID_INVOICE
select invoice;

W celu uzyskania złączenia dwóch encji należy użyć metody Expand oraz zdefiniowanych na serwerze połączeń:

IEnumerable<Sales.Invoice> invoices = m_Context.Invoices.Expand("OrderItems");

Metoda Expand jest bardzo niewydajna, ponieważ złączenie danych odbywa się lokalnie w pamięci, a nie przez silnik bazy danych. Wiąże się to oczywiście z odczytywaniem ogromnej ilości niepotrzebnych danych, gdyż najpierw odczytywane są wiersze, a dopiero potem następuje ich złączenie. Warto podkreślić, że relacje mogą być wykonywane po stronie serwera za pomocą własnych operacji (service operations):

[WebGet]
public IEnumerable<Entities.Invoice> GetInvoicesWithOrderItems()
{
return CurrentDataSource.Invoices.Include("OrderItems");
}

Wydajność

Jedną z ogromnych zalet WCF Data Service jest to, że wydajność selekcji danych może znacznie wzrosnąć dzięki wprowadzeniu HTTP caching. Pierwsze zapytanie odczytuje dane z bazy danych, a kolejne po prostu zwracają wcześniej odczytany zestaw danych. Jeśli się zastosuje protokół HTTP, można wykorzystać gotowe mechanizmy buforowania przez przeglądarkę lub serwer proxy.

Walidacja oraz przechwytywanie żądań

Czasami chcielibyśmy mieć większą kontrolę nad odczytywaniem danych, ponieważ zwykła ich selekcja nie wystarcza. W Data Service można definiować metody, które będą wywoływane przed zadaną operacją (np. przed selekcją, aktualizacją, wstawieniem bądź usunięciem wiersza).

  • Metody przechwytujące operacje selekcji

Dzięki metodom przechwytującym (interceptors), programista może zdefiniować reguły dostępu do danych. Zacznijmy od kodu:

[QueryInterceptor("Products")]
public Expression<Func<Entities.Product, bool>> OnQueryProducts()
{
return product =>product.BasePrice> 5;
}

Metoda OnQueryProducts zostanie wywołana, w momencie gdy użytkownik chce uzyskać dostęp do produktów. W tym przypadku wybrane wiersze muszą spełniać kryteria zdefiniowane zarówno przez użytkownika, jak i przez metodę przechwytującą (cena produktu wyższa od 5). W analogiczny sposób zdefiniujemy metodę, która zwróci wyłącznie produkty dla aktualnie zalogowanego użytkownika (nazwę użytkownika można uzyskać przez HttpContext.Current.User.Identity.Name).

  • Metody przychwytujące dla operacji aktualizacji, wstawienia oraz usunięcia wiersza

    Dzięki przechwyceniu operacji update, insert, delete, bardzo łatwo można zaimplementować walidację danych. Rozważmy następujący przykład:

[ChangeInterceptor("Products")]
        public void OnChangeProducts(Entities.Product product, UpdateOperations ops)
        {
            if (ops == UpdateOperations.Add ||
               ops == UpdateOperations.Change)
            {                
                if (product.BasePrice<5)
                {
                    throw new DataServiceException(400,
                                "Minimalna cena to 5.");
                }                
            }
        }

Metoda zostanie wywołana przed wykonaniem operacji. Można zatem zawrzeć w niej wszelkie reguły walidacji uzależnione od typu operacji (insert, update, delete). Wyrzucenie wyjątku spowoduje, że operacja zostanie anulowana i baza danych nie zostanie zmodyfikowana.