Fluent NHibernate

NHibernate on javan Hibernatesta portattu ORM-ratkaisu. NHibernatessa mäppäykset tehdään xml-tiedostoilla, mutta Fluent NHibernate tarjoaa vaihtoehtoisen, koodiin perustuvan mäppäyksen. Tämän tekniikan etuna on mm. suoraviivaisuus sekä useiden kirjoitusvirheiden eliminointi.

Fluent NHibernaten käyttöönotto

[list bullet=”circle”]

  • Lataa  kirjasto Fluent NHibernaten sivuilta
  • Lisää projektiin referenssi FluentNHibernate.dll -tiedostoon
  • [/list]

    Mäppäykset

    Jotta luokan mäppäys toimii odotetusti, tarvitsee pari seikkaa ottaa huomioon

    [list bullet=”circle”]

  • Id-ominaisuuden set-aksessorin tulisi olla yksityinen
  • Ominaisuuksien tulisi olla virtuaalisia
  • [/list]

    Fluent NHibernatessa mäppäykset tehdään erityisillä luokilla ClassMap<T> ja SubclassMap<T>.

    Yksinkertainen mäppäys

    Luokka, jolla on muutama ominaisuus

    public class Tuote
    {
    	// Id-ominaisuus
    	public virtual int Id { get; private set; }
    	// muita ominaisuuksia
    	public virtual string Nimi { get; set; }
    	public virtual decimal Hinta { get; set; }
    }
    

    Mäppäämistä varten tehdään uusi luokka, joka periytyy ClassMap<T> -luokasta ja T korvataan mäpättävällä luokalla. Mäppäykset tehdään konstruktorissa

    public class TuoteMap : ClassMap<Tuote>
    {
    	public TuoteMap()
    	{
    		Id(x => x.Id);
    		Map(x => x.Nimi);
    		Map(x => x.Hinta);
    	}
    }
    

    Yllä on kaksi mäppäykseen tarkoitettua metodia
    Id mäppää identityn (yleensä auto increment, eli tietokannassa automaattisesti kasvava numero)
    Map mäppää ominaisuuden tietokannan sarakkeeseen

    Monen suhde yhteen ja yhden suhde moneen

    Monen suhde yhteen ja yhden suhde moneen -relaatiot ovat saman asian eri puolet. Esimerkiksi monta tuotetta voisi kuulua yhteen tuoteryhmään ja toiselta puolelta katsottuna yhdellä tuoteryhmällä olisi monta tuotetta.

    Lisätään tuoteryhmä-luokka

    public class Tuoteryhma
    {
    	// identity
    	public virtual int Id { get; private set: }
    	public virtual string Nimi { get; set; }
    	// kokoelma tuotteita
    	public virtual IList<Tuote> Tuotteet { get; set; }
    }
    

    ja lisätään tuote-luokkaan tuoteryhmä

    public class Tuote
    {
    	// Id-ominaisuus
    	public virtual int Id { get; private set; }
    	// muita ominaisuuksia
    	public virtual string Nimi { get; set; }
    	public virtual decimal Hinta { get; set; }
    	// tuoteryhmä
    	public virtual Tuoteryhma Tuoteryhma { get; set; }
    }
    

    Nyt tuoteryhmän mäppäysluokassa käytetään HasMany-metodia määrittelemään, että tuotteita voi olla monta

    public class TuoteryhmaMap : ClassMap<Tuoteryhma>
    {
    	public TuoteryhmaMap()
    	{
    		Id(x => x.Id);
    		Map(x => x.Nimi);
    		// monta tuotetta
    		HasMany(x => x.Tuotteet).Inverse();
    	}
    }
    

    HasMany:n jälkeen tuleva Inverse tarkoittaa, että tuote on vastuussa tallennuksestaan eli tuotteisiin tehtyjä muutoksia ei tallenneta, kun tuoteryhmä tallennetaan vaan ne pitää tallentaa erikseen.

    Lisätään tuotteen mäppäykseen viittaus (References-metodi) tuoteryhmään ja määritellään erikseen sarakkeen nimi

    public class TuoteMap : ClassMap<Tuote>
    {
    	public TuoteMap()
    	{
    		Id(x => x.Id);
    		Map(x => x.Nimi);
    		Map(x => x.Hinta);
    		// viittaa yhteen tuoteryhmään
    		// tallennetaan tuoteryhmän id sarakkeeseen TuoteryhmaId
    		References(x => x.Tuoteryhma, "TuoteryhmaId");
    	}
    }
    

    Monen suhde moneen

    Jos muutetaan edellä ollutta esimerkkiä siten, että tuote voisikin kuulua useampaan tuoteryhmään, saadaan monen suhde moneen -relaatio. Eli yksi tuote voi kuulua moneen tuoteryhmään ja yhdellä tuoteryhmällä voi olla monta tuotetta.

    Muutetaan tuote-luokkaa

    public class Tuote
    {
    	public virtual int Id { get; private set; }
    	public virtual string Nimi { get; set; }
    	public virtual decimal Hinta { get; set; }
    	// nyt onkin monta tuoteryhmää
    	public virtual IList<Tuoteryhma> Tuoteryhmat { get; set; }
    }
    

    Nyt tuotteen mäppäyksessä References pitää vaihtaa HasManyToMany:yn

    public class TuoteMap : ClassMap<Tuote>
    {
    	public TuoteMap()
    	{
    		Id(x => x.Id);
    		Map(x => x.Nimi);
    		Map(x => x.Hinta);
    		// monta tuoryhmää
    		HasManyToMany(x => x.Tuoteryhmat).Table("TuoteryhmanTuotteet").ParentKeyColumn("TuoteId").ChildKeyColumn("TuoteryhmaId");
    	}
    }
    

    Table määrittelee ManyToMany:yn tarvittavan taulun nimen
    ParentKeyColumn määrittelee sarakkeen nimen omalle id:lle
    ChildKeyColumn määrittelee sarakkeen nimen toisen osapuolen id:lle

    Tuoteryhmä-luokastakin HasMany pitää muuttaa HasManyToMany:yn

    public class TuoteryhmaMap : ClassMap<Tuoteryhma>
    {
    	public TuoteryhmaMap()
    	{
    		Id(x => x.Id);
    		Map(x => x.Nimi);
    		// monta tuotetta
    		HasManyToMany(x => x.Tuotteet).Table("TuoteryhmanTuotteet").ParentKeyColumn("TuoteryhmaId").ChildKeyColumn("TuoteId");
    	}
    }
    

    HasManyToMany:n kummankin puolen mäppäys on melkein samanlainen; taulun nimi on sama, mutta ParentKeyColumn ja ChildKeyColumn vaihtuvat keskenään.

    Komponentit

    Komponentit ovat uudelleenkäytettäviä osia, joilla voi niputtaa joukon tietoja ilman, että tarvitsee luoda erillistä taulua. Esim. osoite voisi olla järkevä komponentti ja henkilöllä voisi olla kaksi osoitetta: työ- ja kotiosoite.

    Tehdään osoitteelle luokka

    public class Osoite
    {
    	public string Katuosoite { get; set; }
    	public string Postinumero { get; set; }
    	public string Postitoimipaikka { get; set; }
    }
    

    ja mäppäys-luokka (tällä kertaa periytyy ComponentMap<T> -luokasta)

    public class OsoiteMap : ComponentMap<Osoite>
    {
    	public OsoiteMap()
    	{
    		Map(x => x.Katuosoite);
    		Map(x => x.Postinumero);
    		Map(x => x.Postitoimipaikka);
    	}
    }
    

    Nyt jos henkilöllä on kaksi osoitetta

    public class Henkilo
    {
    	public Osoite Kotiosoite { get; set; }
    	public Osoite TyoOsoite { get; set; }
    }
    

    voidaan ne mäpätä (käyttäen Component-metodia)

    public class HenkiloMap : ClassMap<Henkilo>
    {
    	public HenkiloMap()
    	{
    		Component(x => x.Kotiosoite).ColumnPrefix("Koti_");
    		Component(x => x.TyoOsoite).ColumnPrefix("Tyo_");
    	}
    }
    

    Koska osoitteita on kaksi, täytyy sarakkeille määritellä etuliitteet (Koti_ ja Tyo_) ColumnPrefix-metodilla.

    Konfigurointi

    Konfigurointi suoritetaan Fluently.Configure -metodilla, joka palauttaa ISessionFactory-objektin.

    Esim. käytetään MS SQL Server 2008:aa ja haetaan sille ConnectionString config-tiedostosta (web.config / app.config) nimellä connstr

    Fluently.Configure()
    	.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("connstr")))
    	.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Tuote>())
    	.BuildSessionFactory();
    

    Jos haluaa hienosäätää tuota prosessia, voi asetukset ”paljastaa” ExposeConfiguration-metodilla.

    Paljastetaan asetukset BuildSchema-metodille, jossa määrätään luomaan taulut tietokantaan

    // metodi, joka luo SessionFactory:n
    public static ISessionFactory CreateSessionFactory()
    {
    	return Fluently.Configure()
    		.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("connstr")))
    		.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Tuote>())
    		.ExposeConfiguration(BuildSchema)
    		.BuildSessionFactory();
    }
    private static void BuildSchema(Configuration config)
    {
    	// Tämä luo taulut tietokantaan
    	new SchemaExport(config).Create(false, true);
    }
    
    u7x9cc0

    Navigointi

    Social Media