Dziedziczenie a nHibernate/LinqToSql

Dziedziczenie może byc realizowane na wiele sposobów. Dziś:
Table per class hierarchy - czyli wspólna tabela z dyskryminatorem
Załóżmy że mamy dwie klasy które docelowo mają różnić się tak naprawdę paroma polami (np każda z nich ma 5 swoich pól reszta jest wspólna). Najlogiczniejszym wyjściem jest dziedziczenie z klasy bazowej zawierającej wspólne pola.
Przy tak niewielkiej ilości pól nie-wspólnych dobrze jest mieć (naprawdę z wielu praktycznych powodów) jedną tabelę w bazie danych dla obu klas.
Aby przenieść całą sytuację do nHibernata lub linqa potrzebne jest dodatkowe pole tak zwany dyskryminator dzięki któremu będziemy wiedzieli po stronie bazy danych z którym typem klasy mamy do czynienia.

Moją klasą bazową będzie Invoice, natomiast dziedziczące InvoiceIssued oraz InvoceReceived. W nHibernate cała sprawa sprowadza się jak zawsze do dobrego pliku mapowania

 
 
 
 
 
 
 

Słówko o dyskryminatorze tutaj. W pliku mapowań nHibernata nie możemy mieć dwa razy tego samego pola, dlatego też nie możemy mieć np oneToOneID oraz kolekcji oneToOne. Nie możemy mieć również propercji odwołującej się do tego samego pola w bazie na którym bazuje dyskryminator. Oczywiście po typie klasy wiemy z czym mamy do czynienia jednak czasem warto coś takiego wiedzieć również na poziomie klasy bazowej ja zastosowałam redundantne pole, choć zapewne rozwiązań jest wiele.
//kontunuując poprzedni fragment kodu
 
//... all common properties

 
 

 
 
 
 
 
 

I to by było na tyle. Nie ma żadnej filozofii. Ponieważ klasy mamy trzy Invoice, InvoiceIssued, InvoiceReceived - możemy się nimi dowolnie posługiwać.
Nie ma też żadnej filozofii w pobieraniu obiektów/list. nHibernatowi jest wszytko jedno czy chcemy pobrać sobie klase Invoice czy InvoiceIssued. O nic więcej nie trzeba się martwić (no chyba że o utrzymanie redundantnych pól w atomowej jedności - ale to przecież tylko w wypadku gdy sami tak zadecydujemy).
var Invoice i =Session.Get(ID);
var InvoiceReceived ir =Session.Get(ID);

To teraz to samo zadanie w LinqToSql.
Ponieważ mamy tutaj możliwość przeciągnij upuść pozmieniaj - przeciągamy sobie tabelkę a powstała klasa będzie naszą bazową. (Z przyczyn realnego użycia projektowego klasy tym razem nazywają się InvoiceList, InvoiceIssuedList, InvoiceReceivedList). Należy dodać klasy które będą dziedziczyć z bazowej, oznaczyć relacje a następnie wyciąć specyficzne propercje i przenieść do odpowiednich klas.
W klasach dziedziczących konieczne jest wskazanie które pole będzie dyskryminatorem -Discriminator Property oraz jaka wartości dyskryminatora jest odpowiednia dla danej klasy -Derived Class Discriminator Value
Ważna sprawa - jedna z klas dziedziczących musi być defaultowa - należy określić wartość Inheritance Default.
Związane jest to z generowanymi zapytaniami sql. Choć pewnie chcielibyśmy często inaczej to LinqToSql dla klas dziedziczących zadaje zapytanie sql
PoleDyskryminatora='kodDyskryminatora' jeśli klasa nie jest default oraz
PoleDyskryminatora<>'kodDyskryminatora' jeśli klasa jest default.
(Przyznaje że przypadek testowany był dla dwóch klas dziecziczących być może w przypadku większej ilości klas jest inaczej.)

Było mi ciężko do tego wszystkiego dojść z wizualnego 'ułatwiacza' pracy, dlatego chciałam przedstawić części wygenerowanego pliku mapowań gdzie wszytko jest czarno na białym (no może kolorowo na szarym ale to kwestia ustawień).
[Table(Name="dbo.Invoice")] 
[InheritanceMapping(Code="Ou", Type=typeof(InvoiceIssuedList), IsDefault=true)] 
[InheritanceMapping(Code="In", Type=typeof(InvoiceReceivedList))] 
public partial class InvoiceList : INotifyPropertyChanging, INotifyPropertyChanged 
{
//...
  [Column(Storage="_InvoiceTypeCode", DbType="NVarChar(2) NOT NULL", CanBeNull=false, UpdateCheck=UpdateCheck.Never, IsDiscriminator=true)] 
  public string InvoiceTypeCode 
  {
    get 
     {
       //...
     }
    }
  }
//...
}

public partial class InvoiceIssuedList : InvoiceList 
{
//...
}

public partial class InvoiceReceivedList : InvoiceList 
{
//...
}

Ostatnia sprawa to wyciągnięcie z Linq'a tego co akurat byśmy chcieli.
public
IQueryable GetInvoiceIssuedList() 
{
InvoicingListLinqDataContext context = new InvoicingListLinqDataContext(ConnectionStrings[GlobalVariables._DATABASE]); 
context.ObjectTrackingEnabled = false; 
var ret = from s in context.InvoiceLists. //...
I oto zdziwienie - w contexcie nie ma InvoiceReceivedList ani InvoiceIssuedList. Jest jedynie InvoiceLists Okazuje się że należy odpytać kontekst o InvoiceLists dodatkowo podając typ który tak naprawdę chcemy wyciągnąć.
public
IQueryable GetInvoiceIssuedList() 
{
  InvoicingListLinqDataContext context = new InvoicingListLinqDataContext(ConnectionStrings[GlobalVariables._DATABASE]); 
  context.ObjectTrackingEnabled = false; 
  var ret = from s in context.InvoiceLists.OfType() 
  where !s.IsDeleted 
  select s; 
  return ret; 
} 

Z mojego punktu widzenia przyjemniejsze rozwiązanie stanowi nHibernate - jednak nad gustami się nie dyskutuje ;)

Komentarze

Popularne posty