Minule jsem si postěžoval na určitou nekonzistenci, s jakou LINQ to SQL zachází s DEFAULT hodnotami při vytváření nového záznamu v databázi.
Pokud by to bylo jediné omezení této jinak skvělé technologie, pravděpodobně by to bylo ještě poměrně veselé. Věci jsou však horší, než se čekalo.
Rozšiřme nyní trochu příklad z předchozího dílu této "stěžovací si minisérie" a k naší tabulce vytvořme nějakou ukázkovou, primitivní relaci. Máme faktury, tak třeba položky:
CREATE TABLE POLOZKY(
ID int NOT NULL IDENTITY (1,1),
FAKTURA int NOT NULL,
NAZEV varchar (50) NOT NULL DEFAULT 0,
CENA money NOT NULL DEFAULT 0,
CONSTRAINT PK_POLOZKY PRIMARY KEY CLUSTERED (ID)
)
ALTER TABLE POLOZKY ADD CONSTRAINT FK_POLOZKA_FAKTURA
FOREIGN KEY (FAKTURA) REFERENCES FAKTURA (ID)
Spusťme si projekt, a do dbml souboru si přetáhněme naši novou tabulku POLOZKY. Fajn, program pozná, že jsou tabulky v realci. (V tabulce POLOZKY, mimochodem, je nutné míti primární klíč, jinak LINQ to SQL relaci pozná, ale nepoužije.)
Zatím to vypadá moc dobře, v dbml souboru tedy takto:
<Table Name="dbo.FAKTURA" Member="FAKTURAs">
<Type Name="FAKTURA">
<Column Name="ID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
<Column Name="DATUM" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
<Column Name="CENA" Type="System.Decimal" DbType="Money NOT NULL" CanBeNull="false" />
<Column Name="ODBERATEL" Type="System.String" DbType="VarChar(50) NOT NULL" CanBeNull="false" />
<Association Name="FAKTURA_POLOZKY" Member="POLOZKies" ThisKey="ID" OtherKey="FAKTURA" Type="POLOZKY" />
</Type>
</Table>
<Table Name="dbo.POLOZKY" Member="POLOZKies">
<Type Name="POLOZKY">
<Column Name="ID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
<Column Name="FAKTURA" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
<Column Name="NAZEV" Type="System.String" DbType="VarChar(50) NOT NULL" CanBeNull="false" />
<Column Name="CENA" Type="System.Decimal" DbType="Money NOT NULL" CanBeNull="false" />
<Association Name="FAKTURA_POLOZKY" Member="FAKTURA1" ThisKey="FAKTURA" OtherKey="ID" Type="FAKTURA" IsForeignKey="true" />
</Type>
</Table>
Dále, mějme nějaký triviální kód pro vložení faktury a položky, řekněme:
Dim myFaktura As FAKTURA = New FAKTURA
myFaktura.DATUM = #1/1/2008#
myFaktura.CENA = 500
myFaktura.ODBERATEL = "Pepa"
Dim myPolozka As POLOZKY = New POLOZKY
myPolozka.CENA = 500
myPolozka.NAZEV = "Název zboží"
myFaktura.POLOZKies.Add(myPolozka)
d.FAKTURAs.InsertOnSubmit(myFaktura)
d.SubmitChanges()
Očekávali byste v tomto kódu nějakou chybu? Ne, a dobře děláte. Toto projde naprosto bez problémů. Otázka je, co se stane,
pokud dojdeme dřív nebo později k závěru, že ta položka nebo položky by tam být neměly. Takže, pojďme následně zavolat:
myFaktura.POLOZKies.Clear()
d.SubmitChanges()
Nevím jak Vám, ale mě tato operace přijde poměrně standardní. Jaké ovšem zažijeme překvapení, když namísto smazání položky (položek) skončí
kód chybou "Invalid operation exception" ve smyslu:
An attempt was made to remove a relationship between a FAKTURA and a POLOZKY. However, one of the relationship's foreign keys (POLOZKY.FAKTURA) cannot be set to null.
Řešení je hned několik, ale všechna mají své pro a proti:
- Především můžeme pole FAKTURA v tabulce POLOZKY definovat jako NULLABLE. Na tomto řešení se mě (a asi žádnému rozumnému databázistovi) nelíbí to, že v tabulce
POLOZKY zůstanou osiřelé záznamy. A občas jsme postaveni před hotovou databázi, kterou nelze měnit ;-(
-
lze ručně editovat soubor a na příslušné místo dopsat instrukci DeleteOnNull=True
<Table Name="dbo.POLOZKY" Member="POLOZKies">
<Type Name="POLOZKY">
<Column Name="ID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
<Column Name="FAKTURA" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
<Column Name="NAZEV" Type="System.String" DbType="VarChar(50) NOT NULL" CanBeNull="false" />
<Column Name="CENA" Type="System.Decimal" DbType="Money NOT NULL" CanBeNull="false" />
<Association Name="FAKTURA_POLOZKY" Member="FAKTURA1" ThisKey="FAKTURA" OtherKey="ID" Type="FAKTURA" IsForeignKey="true" DeleteOnNull="true"/>
</Type>
</Table>
Na tom se mi nelíbí především nutnost zasahovat ručně do souboru vytvořeného Visual Studiem, nebo
- Přidat do definice cizího klíče ON DELETE CASCADE,
ALTER TABLE POLOZKY ADD CONSTRAINT FK_POLOZKA_FAKTURA
FOREIGN KEY (FAKTURA) REFERENCES FAKTURA (ID) ON DELETE CASCADE
což povede k tomu, že se DeleteOnNull nastaví do DBML souboru samo. Na tom se mi nelíbí především ta do očí bijící nekoncepčnost. Proč musím měnit pravidlo pro mazání PARENT záznamu, jestliže chci pouze smazat CHILD záznam?
Rozumné řešení nemám. Používám bod 2) s tím, že pokud je třeba tabulku přegenerova, musím si to tam znovu dopsat. Jak to řešíte Vy ostatní?