Miroslav Holec

Miroslav Holec { Software Architect & Dev Evangelist }

Workaround pro chybné mapování datových typů s HasComputedSqlColumn v EF Core

Miroslav Holec

Miroslav Holec

od EF Core 1.0 Publikován 23. května 2018 , aktualizace: 26. května 2018 | ASP.NET Core & EF Core

Pokud používáte Entity Framework Core a už jste zkoušeli pro některý scénář použít HasComputedSqlColumn, dost možná jste narazili na zvláštní chybu související s nastavením správného datového typu.

Jak funguje HasComputedSqlColumn

EF Core podporuje SQL Computed Columns, což jsou virtuální sloupce DB tabulky, které jsou "vypočtené" na základě ostatních sloupců. Tato funkce je podporována od SQL Serveru verze 2017 a od EF Core 1.0. Mezi logická omezení patří zejména nemožnost použití tohoto virtuálního sloupce jako FK, není možné explicitně sloupec označit jako NOT NULL a dále tento sloupec nemůže mít výchozí hodnotu DEFAULT. Za určitých okolností lze ale sloupec použít jako součást PK nebo UNIQUE constraintu. Jelikož je sloupec virtuální a hodnoty jsou dopočtené, nedává ani smysl zahrnutí do INSERTů a UPDATů (resp. to vůbec nejde).

Popis problému

Entity Framework Core umožňuje vytvoření Computed SQL Column s pomocí Fluent API a pro tento sloupec napsat vlastní SQL dotaz, který dopočte hodnotu sloupce. Společně s dodatečnými pravidly ve Fluent API se můžeme dostat například na něco podobného tomuto:

modelBuilder.Entity<Person>()
            .Property(p => p.DisplayName)
            .HasComputedColumnSql("[Id] + ', ' + [LastName]")
            .HasColumnType("nvarchar(200)");

Jakého datového typu bude daný virtuální SQL sloupec? Pokud hádáte nvarchar(200), tak vás zklamu. Datový typ je INT, jelikož sloupec [Id] je také INT a EF Core neumí automaticky owrapovat výraz do takové podoby, aby bylo splněno pravidlo .HasColumnType("nvarchar(200)").

Výsledkem je sice funkční model, vytvoření migrací i DB Update, nicméně do tabulky Person se nebudete moci podívat, jelikož sloupec se bude snažit dopočítat INT s použitím Id + LastName.

Řešení

Pro tento issue jsem založil vlákno na GitHubu a dle všeho se tím EF tým bude zabývat. Než (a snad) se objeví automatický CAST hodnot dle HasColumnType, musíme si pomoci sami:

 modelBuilder.Entity<Person>()
            .Property(p => p.DisplayName)
            .HasComputedColumnSql("CAST([Id] as varchar(200)) + ', ' + [LastName]")
            .HasColumnType("nvarchar(200)");

I tak výsledné chování závisí zřejmě na DBS. Například řešení uvedené výše ignoruje délku sloupce a nastaví si vypočtený DisplayName jako nvarchar(302). Důvodem je součet délky LastName, konstanty ', ' a 200 znaků pro Id. Protože HasColumnType v tomto případě postrádá smysl, napadlo mě nastavit konečný typ sloupce jako nvarchar(500). Jednoduše jsem očekával, že vyšší hodnota wins. Bohužel výsledek je stejný.

Další myšlenky

Dále mě zajímal update. Co se stane, když nově bude délka pro LastName 300 znaků?

The column 'DisplayName' is dependent on column 'LastName'.
ALTER TABLE ALTER COLUMN LastName failed because one or more objects access this column.

Takže smůla. Je možné změnit délku pro Id v rámci CASTu, ale už není možné měnit délku sloupce, který je použitý pro výpočet virtuálního sloupce. Pravděpodobně dalším workaroundem by byla nejprve likvidace Computed Column, poté ALTER závislých sloupců a nakonec nový CREATE virtuálního sloupce.

Status Update, 26.5.

Vývojářský tým EF Core rozhodl podporu přetypování zatím nepřidávat. EF Core nechtějí stavit do role, kdy by musel ORM hádat, jaký cílový typ má být aplikován. Osobně chápu, že například sčítání dvou INTů nelze hádat (chce uživatel skutečně sčítat číselné hodnoty, nebo dělat spojení řetězců?). Na druhou stranu mi přijde zvláštní, že explicitní použití HasColumnType bude ignorováno. Za normálních okolností totiž pomocí HasColumnType mohu udělat z INT typu nvarchar.

Zeptejte se