Az SQL mint szabvány? Hahaha!

NHibernate Designer -e szerettem volna az eddig MSSQL -en és MySQL -en jól működő táblákat Oracle adatbázisba átvarázsolni. Én butus! Azt hittem, hogy ez egy kattintással megoldható! De nem! Az Oracle nem tudta értelmezni az Int64 típust (ami MSSQL és MySQL alatt is

bigint

). Nosza, utána olvastam, és az Oracle csak az UInt64 -et tudja igazából megemészteni. Sebaj, nekiálltam, és a teljes modellt átalakítottam UInt64 -re. Emiatt a kódokat is át kellett írnom, no problem!

Elkészültem vele, befuttattam Oracle -be. De bizony egy csomó mező elnevezés nem tetszett neki (amit MSSQL és MySQL is simán benyelt!). Újabb órás munka, hogy a mezőket átnevezzem, átneveztem a Custom osztályokban is, és még az XML leírókban is.

Befuttattam MSSQL alá a módosítást, de bizony nem sikerült! Az MSSQL nem csipázza az UInt64 -et! No problem, az NHibernate konfigurációját kicsit megpiszkálva, azt mondtam, hogy az tulajdonképpen Int64.

Ezzel meg is lettünk volna, de mégsem! Merthogy így se volt hajlandó kezelni, mert nem tudta kigenerálni az NHibernate Designer a táblamódosításokat!

Platty! Újabb 3 óra utána olvasás, míg végül megállapodtam abban, hogy az Int32 típust mindegyik DB ismeri, és mindegyik meg tudja emészteni.

Most ott tartok, hogy a kódokat újra át kell írni, de cirkulált tábla referencia miatt az NHibernate Designer nem tudja kigenerálni új adatbázisba a táblákat!

Áááááááá!!! Milyen jó is az, hogy az SQL szabványos, és nem kell gondolkozni, ha egy táblát vagy egy lekérdezést más adatbázis kezelőben szeretnék megvalósítani! 😀

NHibernate 3.2 + MSSQL Blob

Furcsa dologgal kellett szembesülnöm a minap. Az MSSQL adatbázisban létrehoztam egy Blob mezőt (hogy pontosítsam magam, egy varbinary<max> mezőt), majd ebbe belepakoltam adatokat.

A visszaolvasáskor jött a szemkikerekedés, amikor is az adatok egy része jelent csak meg. Mintha a nagyobb része (46kb volt az adat) valahol elveszett volna. Jó sok nyomkövetés után döbbentem rá arra, hogy az MSSQL -be már  a beszúráskor hibásan kerülnek be az adatok. Konkrétan 8000 byte! Jó sok nyomozás után végül sikerült rátapintanom a lényegre. Az NHibernate 3.2 valamilyen okból kifolyólag rosszul kezeli a varbinary típust. Megoldást nem nagyon találtam hozzá, ezért a forrásokat letöltöttem és szépen végig követtem, mi lehet a probléma. Végül sikerült rálelnem, és javítottam is.

A megoldás a mapping -ban rejlik, pontosan definiálni kell a mezőt ahhoz, hogy rendesen kezelje.

Nem elég ez:

<property name='Image' column='`Iame`' />

Helyette a következő definíciót kell alkalmazni:

<property name='Image' type='BinaryBlob'><column name='`Image`' sql-type='`varbinary(max)`'/></property>

NHibernate: boolean kezelés MSSQL alatt

Az MSSQL nem szereti a boolean mezőt. Minden esetben BIT típusú mezőt hoz létre. De ha én egy Criteria -t hozok létre, ahol boolean típusú értéket szeretnék beadni, felháborodottan közli,  hogy ezt Ő nem tudja értelmezni!

Mi ilyenkor a teendő? Az NHibernate -et meg kell tanítani arra, hogy jól kezelje:

A megoldás az NHibernate Configuration osztályának létrehozásakor (vagy a konfigurációs file -ba) be kell csempészni a következőt:

<add key=”hibernate.query.substitutions” value=”true 1, false 0″ />

        public ISessionFactory MSSQLBuildSessionFactory()
        {
            NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
            Dictionary<string, string> props = new Dictionary<string, string>();
            String connectionString = String.Format("Data Source={0};Initial Catalog={1};User Id={2};Password={3}", _serverName, _dataBase, _userName, _password);
            props.Add(Environment.ConnectionDriver, typeof(SqlClientDriver).FullName);
            props.Add(Environment.Dialect, "NHibernate.Dialect.MsSql2008Dialect");
            props.Add(Environment.ConnectionProvider, typeof(DriverConnectionProvider).FullName);
            props.Add(Environment.ConnectionString, connectionString);
            props.Add(Environment.ShowSql, _showSQL.ToString());
            props.Add(Environment.ProxyFactoryFactoryClass, typeof(NHibernate.Bytecode.DefaultProxyFactoryFactory).AssemblyQualifiedName);
            //A lényeg!!
            props.Add("hibernate.query.substitutions", "true 1, false 0");

            cfg.AddProperties(props);
            OnFlowConfigurationHelper.ApplyConfiguration(cfg);
            OnFlowCustomConfigurationHelper.ApplyCustomConfiguration(cfg);
            return cfg.BuildSessionFactory();
        } 

ManyToOne probléma NHibernate -ben

Belefutottunk egy érdekes hibába. Van egy táblánk (legyen ez a hallgató), amihez tartoznak kiegészítő értékek egy másik táblában (pl. tantárgyak).

A mapping -nél beállítottam a many-to-one kapcsolatot a hallgatónál, de ha a tantárgyak táblában nem volt adat, akkor elhasalt proxy hibával.

<many-to-one name='Tantargy' class='Tantargyak' 
   column='`TantargyId`' fetch='join'/>

Komoly fejtörést okozott, mert ilyen eset lehetséges akkor, amikor egy hallgatót felvesznek az egyetemre a GÓLYA rendszeren keresztül, és még nem vett fel tárgyakat.

Végül sikerült rájönnöm, hogy hogyan lehet ezt a hibát kikerülni, a megoldás:  not-found=’ignore’

Az előbbi példából kiindulva:

<many-to-one name='Tantargy' class='Tantargyak' 
  column='`TantargyId`' fetch='join' not-found='ignore'/>

Ha a not-found tag -et, aminek az alapértelmezett beállítása exception átállítjuk ignore -ra, akkor ha a left outer join -t követően nem talál értéket, a Tantargy property -be null -al tér vissza.

NHibernate mapping by code

Az NHibernate egyik gyerekbetegsége volt az, hogy a mapping -et, azaz az osztályok és az adatbázis tábláinak és mezőinek az összerendelését XML fájlokon kereszztül lehetett beállítani.
Mit is jelent ez?
Íme egy példa rá:

<?xml version='1.0' encoding='utf-8' ?>
<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2' assembly="Example" namespace='Example'>
<class name='User' table='user' dynamic-update='true' dynamic-insert='true' >
 <id name='Id' column="Id" >
  <generator class='hilo'>
   <param name='table'>globalid</param>
   <param name='column'>Id</param>
   <param name='max_lo'>0</param>
  </generator>
 </id>
 <property name='Password' column="Password" />
 <property name='Name' column="Name" />
 <property name='Mothername' column="Mothername" />
 <property name='Bornname' column="Bornname" />
 <property name='Borndate' column="Borndate" />
 <property name='Taj' column="Taj" />
 <property name='Vatnumber' column="Vatnumber" />
 <property name='Addresscardno' column="Addresscardno" />
 <property name='Archived' column="Archived" />
 <property name='Username' column="Username" />
 <property name='Serial' column="Serial" />
 <property name='External' column="External" />
</class>
</hibernate-mapping>


Van egy táblánk, amelyben a felhasználó adatait tároljuk el. Ne foglalkozzunk most azzal, hogy a felhasználói táblának millió kapcsolódása van, mi csak arra vagyunk kiváncsiak, hogy milyen úton – módon lehet rábeszélni az NHibernate -et arra, hogy az osztály és a tábla között megtalálja a kapcsolatot.

Ez eléggé nehéz és áttekinthetetlen hogyha 15 mezőnél többet tartalmaz a tábla.

Az új NHibernate 3.2 egyik újítása (rögtön jegyezzük meg itt, hogy a Fluent NHibernate már ezt jóideje tudja), hogy a feltérképezést kódbol is el lehet végezni.
Mi kell hozzá?

Az NHibernate.Mapping.ByCode osztály, és egy kis ráérző képesség, semmi több!

Íme az előző példa, C# kódként!

public virtual ModelMapper Mapping()
{
var mapper = new ModelMapper();
mapper.Class<User>(
ca =>
 {
  ca.EntityName(this.GetType().ToString());
  ca.Table("user");
  ca.DynamicUpdate(true);
  ca.DynamicInsert(true);
  ca.Id(x => x.Id, map =>
  {
   map.Column("Id");
   map.Generator(Generators.HighLow, gmap => gmap.Params(new { max_low = 0 }));
   });
   ca.Property(x => x.Password, map => map.Column("Password"));
   ca.Property(x => x.Name, map => map.Column("Name"));
   ca.Property(x => x.Mothername, map => map.Column("Mothername"));
   ca.Property(x => x.Bornname, map => map.Column("Bornname"));
   ca.Property(x => x.Borndate, map => map.Column("Borndate"));
   ca.Property(x => x.Taj, map => map.Column("Taj"));
   ca.Property(x => x.Vatnumber, map => map.Column("Vatnumber"));
   ca.Property(x => x.Addresscardno, map => map.Column("Addresscardno"));
   ca.Property(x => x.Username, map => map.Column("Username"));
   mapper.AddMappings(typeof(User).Assembly.GetTypes());
   return mapper;
}

Mennyivel átláthatóbb, nyomon követhetőbb, és nem utolsó sorban kisebb az esély az elírásra, mint az XML esetén, amit futásidőben dolgoz fel az NHibernate -t!

1..* és *..* asszociáció lekérdezése egy függvény segítségével

Nem volt egyszerű szülés, őszintén mondom. Nagyjából a harmincadik megoldás lett a nyerő.

Mit is akartam?

C# és NHibernate segítségével egy olyan eljárást írni, ami 1..* kapcsolat esetén is, és *..* kapcsolat esetén is le tudja kérdezni a mester azonosítója alapján a hozzá tartozó értékeket.

Egy egyszerű példán keresztül szemléltetem.

Létezik egy iskola, ahol vannak tanárok, vannak osztályok, és vannak tantárgyak. Minket a tanárok érdekelnek, illetve az osztályok.

Szükségünk lenne arra, hogy egy – egy tanár melyik osztályban tanít, illetve visszafele is szükség lenne arra, hogy adott osztályba melyik tanárok tanítanak. És arra is szükségünk van, hogy egy – egy tanár milyen tantárgyakat tanít.

A tanár és az osztály között *..* azaz ManyToMany kapcsolat van, hiszen egy tanár több osztályban is taníthat, és egy osztályban több tanár is taníthat. A tanár és a tantárgy között 1..* azaz OneToMany, azaz egy tanárnak több tantárgya is lehet.

A megoldás viszonylag egyszerű, csak kellett hozzá jó pár napnyi dokumentáció böngészés:

return session.CreateCriteria("Classes", "a")
  .CreateCriteria("Teacher", "b", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
  .Add(Restrictions.Eq("b.Id", 5)).List();

A fenti kódrészlet lehozza az 5 -ös azonosítójú osztályhoz tartozó tanárok listáját.

return session.CreateCriteria("Teachers", "a")
  .CreateCriteria("Class", "b", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
  .Add(Restrictions.Eq("b.Id", 12)).List();

Ez a kódrészlet lehozza azokat a termeket, ahol a 12 -es azonosítójú tanár tanít.

return session.CreateCriteria("Teacher", "a")
  .CreateCriteria("Subject", "b", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
  .Add(Restrictions.Eq("b.Id", 12)).List();

És végezetül a 12 -es azonosítójú tanár összes tantárgyát tölti le az eljárás.

Mint látszik, a *..* és az 1..* kapcsolatban csak annyi a különbség, hogy a *..* kapcsolatnál a master adatokat többesszámmal láttam el.

1..* és *..* asszociáció kezelése

A programozó ember azt gondolná, hogy az egy – sok és sok – sok kapcsolatok lekezelése megegyezik, hiszen csak az egyik oldalon van egy aprócska eltérés. De bizony amikor odaér, hogy le kell programoznia egy asszociáció betöltését, akkor rögtön gondba lesz. 

Mert a több – több (ManyToMany) kapcsolat esetén egy asszociációnál fontos az irány, azaz hogy melyik oldalró szeretnénk a többes kapcsolatot megközelíteni. És ha ezt úgy kell megírni, hogy bármilyen entitás esetén működjön, akkor ott már komoly barkácsolásra van szükség.

Ez jutott nekem is! Barkácsolom a entitás kezelőt, és bízom benne, hogy összehozom még ma! 🙂

Zseniális vagyok!

Tegnap este és ma délelőtt több olyan problémát is megoldottam az NHibernate -ben, amivel már jó ideje küszködünk! És végre sikerült rávenni az NHibernate Designer -t arra, hogy a modellből kigenerálja a táblákat és a megfelelő osztályokat is!

NHibernate generátorok

Aki már készített adatbázisokkal szorosan együttműködő, esetleg még kliens szerver (3-tier) alkalmazást, az tudja, hogy az adatok megkülönböztetéséhez az egyediség a kulcsszó.

No de mi is az egyediség?

Egy olyan egyedi jellemzője a relációs adatbázisban elhelyezkedő adatoknak, amely egyszer, és megismételhetetlenül képződik egy tételhez, és a tétel azonosíthatóságát kizárólagosan biztosítja. Azaz, ha van 1.000.000 adatunk, akkor egy millió egyedi azonosítónak kell lenni, hogy pontosan meg tudjuk határozni azt az egy tételt, amire nekünk szükségünk van. Ezt egyedi kulcsnak, szép angol nevén primary key -nek hívják. Mi lehet egyedi kulcs? Ha egy személyről beszélünk, egyedi kulcs lehet az adóazonosítója, a személyigazolvány száma, vagy a TAJ száma. De mi van, ha számokról beszélünk? Hogyan lehet egy számhalmaznak egyedi azonosítója?

A nagy relációs adatbázis kezelő rendszerek segítségünkre vannak, úgynevezett egyedi kulcs generátorokkal. Ezek lehetnek HASH kódok, egyedi számok, vagy egyszerűen egy GUID.

De ha mi nem Oracle -t, MSSQL -t, vagy IBM DB2 -t használunk, akkor már nincs is lehetőségünk ilyen egyedi kulcsok használatára?

De igen, ha az NHibernate Generator -át hívjuk segítségül.

Mire is valók az NHibernate generátorok? Segítségükkel egy – egy tételhez egyedi kódot, számot tudunk hozzárendelni, így a visszakereshetősége biztosan működni fog.

És most lássuk, milyen lehetőségeket biztosít számunkra az NHibernate! Az NHibernate előre elkészített generátor osztályokkal segíti a munkánkat, illetve biztosít egy egyedi, saját magunk által megírt generátort is beleilleszteni a rendszerbe.

increment: A legegyszerűbb egyedi kulcs készítő. Egy számot növel, így lesz minden egyes tételnek új egyedi kulcsa. Azaz, ha beszúrjuk az első adatot, akkor ez a szám 1 lesz. Ha beszúrjuk a második adatot, akkor ez a szám 2 lesz, ha a harmadik adatot, akkor 3. Ha töröljük az első adatot, és beszúrunk még egyet, akkor ez a szám 4 lesz. És így tovább. A mező típusától függően ez több milliárd egyedi kulcsot biztosít. No de akkor miért nem ezt használja minden rendszer? Azon rendszerek, ahol a gépek és az adatbázisok fürtözve (cluster) vannak, ez a szám ismétlődhet a fürt egy – egy elemében! Azaz elképzelhető, hogy két, teljesen különböző adat ugyanazt az egyedi azonosítót kapja meg. Arra, hogy ez miért így történik, túl rövid ez a bejegyzés! De higgyed el, kedves olvasó! Hogyan definiáljuk?

<id name=”Id” type=”Int64″ column=”uid”>
<generator class=”increment“>
</generator>
</id>

identity: Ez a generátor csak a DB2, MySQL, MS SQL és a Sybase szerverek alól érhető el, ahol van egy speciális rekord típus, az indentity. Az adatbázis által visszaadott típus a Convert.ChangType eljárással alakítja megfelelő típusúvá. Érdekessége ennek a generátornak, hogy az NH2.1 -től ellenőrzi, hogy a RDB tudja -e kezelni az identity értéket, ha nem, akkor megpróbálja szekvenciaként értelmezni. Ha így se megy, akkor trigger típusúként próbálja kezelni. De ha a felhasználónak saját egyedi generátora van, akkor azt a IdentityStyleIdentifierGeneratorClass  függvény felülbírálásával (override) tudja felüldefiniálni!

<id name=”Id” type=”string” column=”uid”>
<generator class=”identity“>
</generator>
</id>

sequence: A szekvencia a IBM DB2, PostgresSQL és az Oracle (illetve a Firebird triggerrel megfejelt) egyedi kódsor készítőjének hívó generátora. Azaz ezek a rendszerek egy teljesen egyedi szekvenciát tudnak létrehozni és átadni. Az adatbázis által visszaadott típus a Convert.ChangType eljárással alakítja megfelelő típusúvá.

<id name=”Id” type=”string” column=”uid”>
<generator class=”sequence“>
</generator>
</id>

hilo: Az egyik újonnan felfedezett kedvencem. A segítségével egy szerver-, és egy kliens oldali egyedi azonosítót lehet készíteni, amelyet aztán összemos, és így keletkezik egy teljesen egyedi kód. Egy példával szemléltetem: A kliens alkalmazás beszúr három új sort. A hilo -nál beállítottunk a max_lo értékét 10 -re. A sorok relációban vannak egymással, például egy dolgozó adatai, és telefonszámai. Ilyen esetben, a dolgozó adatainak az NHibernate 1 -es számot generálja. A kliens 01 -et. Így a dolgozó egyeid azonosítója: 101. Az első telefonszám 102, hiszen a kliens 02 -t generált hozzá, míg a második telefonszám 103 lesz. Ezzel a módszerrel az egy tranzakción belüli tételeket gyönyörűen nyomon lehet követni! Fontos, hogy ezt a generátort nem szabad akkor használni, hogyha a csatlakozási adatokat a felhasználó adhatja meg!

<id name=”Id” type=”Int64″ column=”uid”>
<generator class=”hilo“>
<param name=’table’>idgenerator</param>
<param name=’column’>Id</param>
<param name=’max_lo’>10</param>
</generator>
</id>

seqhilo: Ez a generátor a hilo algoritmust vegyíti a nevesített adatbázis szekvenciával. Azaz az adatbázis egy szekvenciát ad vissza, amihez a kliens oldal hozzádobja a saját egyedi kódját.

<id name=”Id” type=”Int64″ column=”uid”>
<generator class=”seqhilo“>
<param name=’sequence’>IdSequence</param>
<param name=’max_lo’>100</param>
</generator>
</id>

uuid.hex: A System.Guid függvényt hívja meg,  majd a ToString() függvény segítségével egy egyedi Guid -ot (egyedi globális azonosítót: Global Unique Identifier) képez. A szöveg hossza a formátumtól függően változhat.

<id name=”Id” type=”string” column=”uid”>
<generator class=”uuid.hex“/>
</id>

uuid.string: Szintén Guid készítő, de ez az új System.Guid függvényt használja, amely egy byte tömböt ad vissza, és ezt konvertálja a ToString() segítségével szöveggé.

<id name=”Id” type=”string” column=”uid”>
<generator class=’uuid.string‘/>
</id>

guid: Újabb Guid generátor, ami az új System.Guid -ot használja, és az általa kiköpött szöveget menti le!

<id name=”Id” type=”string” column=”uid”>
<generator class=’guid‘/>
</id>

guid.comb: Az új System.Guid függvényt használja, de a generált kód Jimmy Nillson által leírt módon jön létre.

<id name=”Id” type=”string” column=”uid”>
<generator class=’guid.comb‘/>
</id>

native: A relációs adatbázis kezelő saját egyedi kulcs generátorát használja. Ilyenkor rábízza magát az RDBS -re, és az onnan kapott értéket tekinti biztosan érvényesnek!

<id name=”Id” type=”string” column=”uid”>
<generator class=’native‘/>
</id>

assigned: Ezt akkor alkalmazza az NHibernate -et használó programozó, hogyha saját, egyedi azonosítót akar adni az adatainak. Ilyen példa az okmányirodában a dokumentumokra felvezetett egyedi kód. Ezt egy megadott algoritmus számolja, ami biztosan egyedi kódot ad vissza!

<id name=”Id” type=”string” column=”uid”>
<generator class=’assigned‘/>
</id>

foreign: Az egyedi kulcsot egy idegen táblából veszi. Ritkán használatos, csak az egy – egy kapcsolatoknál lehet igazán használni!

<id name=”Id” type=”string” column=”uid”>
<generator class=’foreign‘/>
<param name= ‘property’>Employee</property>
</id>

Set, Bag, List az NHibernate -ben

Alapvető kérdés, hogy mikor milyen típust érdemes definiálni a kollekciók letöltésénél az  NHibernate -ben.

A három lehetséges versenyző: Set, Bag, List

No de miben különböznek egymástól, mikor mit érdemes használni?

Set: rendezés mentes kollekció, az indexeket nem lehet használni, minden tétele egyedi. Duplikáció nem lehetséges a tételek között! A GetHashCode és az Equals függvényeket felül kell definiálni (override) és le kell kezelni az esetleges duplikációkat! A rendezése csak az OrderBy -al, vagy egy előre definiált SortedSet segítségével lehetséges. A Set használata akkor ajánlott, hogyha a visszatérő kollekcióban biztosan egyedi tételek vannak, és kizárt a duplikálódás! Az NHibernate a kollekció visszatérésére az Iesi.Collection.ISet interface -t használja. A mapping XML -ben <set> -el kell definiálni.

Bag: rendezetlen lista, ahol megengedett a duplikálódás. A .NET IList -jét használja, azaz IList -ben adja vissza az adatokat az NHibernate -t. A Bag -et néha nevezik Multiset -nek is, a duplikálódó tételei miatt. Az index oszlopát az NHibernate nem használja és nem illeszti, így a keresés indexek alapján nem lehetséges! A mapping XML -ben <set> -el kell definiálni.

List: rendezett lista, a duplikálódás engedélyezve van. Az index oszlopot az NHibernate -ben illeszteni kell a megfelelő index mezőhöz. A kollekciót a .NET szabványos IList -en keresztül adja vissza. A mapping XML -ben <list> -el kell definiálni.

Lehetőség van még a< map>, <array> és <primitive-array> definiálására is, amire most nem térek ki… 🙂

1 2