Entity Framework Code First a enumy

Už je to nějaká chvíle, co jsme se na blogu Scotta Guthrie mohli dočíst o „Code-First“ featuře Entity Frameworku. Pro mě to byl moment, kdy jsem začal o EF uvažovat jako reálné alternativě k NHibernate. EF nicméně stále neumí spoustu věcí, které jsou u NH samozřejmostí. Třeba takové mapování enumů, s čímž jsem se nedávno musel vypořádat. Řešení, která jsem našel na googlu mi úplně nevyhovovala, tak jsem použil vlastní způsob.

Spočívá v použití mírně upravené varianty tzv. „Type safe enum„. Tento vzor spočívá v tom, že třídě, která má být naším „enumem“, skryjeme konstruktor, tak abychom instanci mohli vytvořit jenom „zevnitř“, a „navenek“ nabídneme jenom omezené množství instancí prostřednictvím readonly statických členů.

    public class Planet
    {
        public static readonly Planet Mercury = new Planet();
        public static readonly Planet Venus = new Planet();
        public static readonly Planet Earth = new Planet();
        public static readonly Planet Mars = new Planet();

        private Planet() {}
    }

    // ....
    Planet p = Planet.Mercury;
    if (p == Planet.Earth) ...

Entity Framework ale potřebuje nějak rozlišit jednotlivé instance podle dat. Data zatím nemáme žádná. Můžeme vytvořit jedno-účelnou „diskriminační“ property například typu int. Navíc privátní parametrický konstruktor by se EF taky nelíbil, takže přidáme další protected bezparametrický konstruktor. To je ústupek, který musíme často podstoupit i v případě NHibernate, privátní konstruktory nemá ráda většina ORM, protože potřebují vytvářet dynamické proxy. Musíme také přetížit operátor porovnání, aby porovnával na základě hodnoty „diskriminační“ property. Místo jedno-účelové property můžeme případně použít i něco smysluplnějšího, například u dnů v týdnu by to mohlo být jejich pořadové číslo. Výsledná třída bude vypadat následovně:

    public class Planet
    {
        public static readonly Planet Mercury = new Planet(0);
        public static readonly Planet Venus = new Planet(1);
        public static readonly Planet Earth = new Planet(2);
        public static readonly Planet Mars = new Planet(3);

        private Planet(int dicriminator)
        {
            this.Dicriminator = dicriminator;
        }

        protected Planet() {}

        public int Dicriminator { get; set; }

        public static bool operator ==(Planet planet1, Planet planet2)
        {
            return planet1.Dicriminator == planet2.Dicriminator;
        }

        public static bool operator !=(Planet planet1, Planet planet2)
        {
            return !(planet1 == planet2);
        }

        // spravne bychom meli prekryt i metody Equals a GetHashCode
    }

Entity Framework by měl třídu Planet rozpoznat jako „komplexní typ“ (v řeči NHibernate komponentu) nebo-li třídu, pro kterou nebude vytvářet samostatnou tabulku. Mně ale tohle nefungovalo, takže jsem EF instruoval v tomto směru ručně.

    public class Context : DbContext
    {
        // ...
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ComplexType<Planet>();
        }
    }

Comments 3

  1. Aleš Roubíček wrote:

    Můžu se zeptat na důvody k přechodu z NHibernate na EF?

    BTW enumeraci lze snadno získat přetypováním z integeru, který EF bez problémů zvládá. Nevýhodou je, že potřebuješ dvě vlastnosti, ale ta integerová může být s nižší viditelností.

    Posted 29 Srp 2010 at 5.48
  2. admin wrote:

    Důvody byly dva. Za prvé: člověk, pro kterého tenhle konkrétní projekt dělám, preferuje MS technologie. To je čistě „politická“ záležitost. Zajímavější je druhý důvod. Zmíněný projekt není nic extra složitého – webová aplikace – takže jsem si řekl, že to je vhodná příležitost pro to zkusit novou věc, abych věděl, co se děje ve světě EF. Dal jsem si podmínku, že se mi v řádu 1 až 2 hodin musí podařit to rozchodit a otestovat, že to bude umět všechno, co budu potřebovat.

    Musel jsem udělat několik ústupků (například protože EF neumí mapovat jiné než non-public properties, nebo jsem zatím nepřišel, jak na to). Na druhou stranu mám z toho pocit, že to tak nějak „funguje“ snadněji. Jasně teoreticky rozchodit NHibernate+NHibernate.Linq+Fluent NHibernate auto mapping, by měla být taky hračka a když má člověk už zkušenosti, tak to opravdu hračka je, ale je potřeba stáhnout asi 6-7 assembly, když člověk chce updatovat na novou verzi NH, tak je potřeba updatovat všechno ostatní, jednu dobu bylo opravdu složité sehnat NHibernate.Linq zbuildovaný proti aktuálnímu NHibernate a tak bych mohl pokračovat… oproti tomu EF, to je jedna assembly navíc a v budoucnu bude standardní součástí ADO .NET. Tak nějak poslední dobou dospívám k tomu, že snadnější maintanance stojí i za nějaký ten ústupek. Možná, že při závěrečném ladění výkonu budu litovat, že nemám takové možnosti jako s NHibernate, ale zatím jsem celkem spokojen.

    Nicméně pro nějaký složitý doménový model, pro projekt opravdu v duchu Domain-driven-designu, není EF možnost, tady jasně vyhrává NHibernate. Ovšem přiznejme si, webovky a jednoduché informační systémy nejsou úplně cílovou skupinou pro „hard-core“ Domain-driven-design.

    Posted 30 Srp 2010 at 7.43
  3. Aleš Roubíček wrote:

    „Politický důvod“ jsem tak nějak i čekal. :) Škoda, že je zcela zcestný.

    Já nad NHibernate používám místo Fluentu Castle.ActiveRecord a tam je problém se synchronizací knihoven tak nějak vyřešen tím, že NHLinq i NHSearch jsou závislosti ActiveRecord a tudíž je tam vždy správná (a funkční) verze.

    Nevýhodu to má v tom, že mám k tomu některé vlastní patche, takže musím rekompilovat. Na druhou stranu tuhle možnost u EF nemám. A EF třeba stále neumí spatial data což je celkem problém.

    Posted 30 Srp 2010 at 11.53

Post a Comment

Your email is never published nor shared. Required fields are marked *