Enginerds

01/01/2009

FluentNHibernate

Filed under: nHibernate — Mart Leet @ 20:38

Nimelt mõni aeg tagasi proovisin ja nüüd kasutan mappimiseks FluentNHibernatet. Alguses tundus see vaid hea mõttena ning kuna hetkel teen projektis suures osas ainult mina domeeni, mappimist jne siis refaktoorimist justkui ei tuleks ette. Siiski esimesed kasud juba leitud.

Nimelt Fluent aitab compiletime vigu leida, toetab refaktoorimist ning nHibernate puhul kaotab ära vajaduse xml (hbm) failide järele. Tegelikult küll mitte, sest fluenti idee on, et luuakse nHibernate jaoks siiski samasugused xml failid, kuid seda inmemory-na ehk mina kui arendaja neid ei näe (kui ma just nimelt ei taha näha).

Aga sisu ise:

On olemas generic ClassMap<T> ning lambda expressionite abil saab koodis entity-d ära mappida.

Näide:

public class HorseMap : ClassMap<Horse>
    {
        public HorseMap()
        {
            DefaultAccess.AsReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);

            Id(x => x.Id).GeneratedBy.Native()
                .WithUnsavedValue(0);

            HasMany<HorseRecord>(x => x.HorseRecords)
                .WithKeyColumn("HorseId")
                .Cascade
                .AllDeleteOrphan();
            HasMany<HorseStatistics>(x => x.HorseStatistics)
                .WithKeyColumn("HorseId")
                .Cascade
                .AllDeleteOrphan();
            HasMany<HorsePerformance>(x => x.PastPerformances)
                .WithKeyColumn("HorseId")
                .Cascade
                .AllDeleteOrphan();

            References(x => x.Sex, "Sex");

            Map(x => x.Age);
            Map(x => x.AtgId);
            Map(x => x.HorseNameAndNationality);
            Map(x => x.IsRegisteredInSweden, "IsSwedenReg");
            Map(x => x.IsThoroughbred);
            Map(x => x.Name);
            Map(x => x.NationalityInBorned, "NationalityBorn");
            Map(x => x.NationalityInOwnered, "NationalityOwner");
            Map(x => x.NationalityInRaised, "NationalityRaised");
        }
    }

Nagu näha saab enamus mappinguid valmis teha stringe kasutamata (mis mulle eriti meeldib). Kuna ma ise ei loonud andmebaasi ning mappimine käib andmebaas –> domeen –> mappingud viisil, siis mõnes kohas siiski baasi columnname ei ole sama, mida ma property-na tahan kasutada.

Eelmises posituses viitasin ITimeStamped interfacele ning ka siinkohas on Fluent lähenemine igati kasulik. Nimelt on mul default property-d mapitud ära baas class-i ning nii on vähem koodikordavust.

public class TimeStampClassMap<T> : ClassMap<T> where T : ITimeStamped
{
    public TimeStampClassMap()
    {
        DefaultAccess.AsReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);

        Id(x => x.Id).GeneratedBy.Native()
            .WithUnsavedValue(0);

        Map(x => x.DateLastModified)
            .TheColumnNameIs("LastUpdated");

    }
}

ning sellest class-ist tulenev entity:

public partial class LicenseOwnerMap : TimeStampClassMap<LicenseOwner>
{
    public LicenseOwnerMap()
    {
    WithTable("Start_LicenseOwner");
        References(x => x.Owner)
            .TheColumnNameIs("LicenseOwnerId")
            .Cascade
            .SaveUpdate();
        References(x => x.Status)
            .TheColumnNameIs("Status");
        References(x => x.Type)
            .TheColumnNameIs("Type");
    }
}

Mappinguid on lisaks veel eriti hea testida:

[Test]
[Category("Integration")]
public void VerifyMapping_IsSuccessful()
{
    new PersistenceSpecification<T>(Dao.Session)
        .VerifyTheMappings();
}

Eelnevas on T Fluentiga mappitud entity. Siinkohal tegelikult tehakse seda, et luuakse uus T ning setitakse kõik default valued property-teks ning salvestatakse baasi. Teises sessioonis tehakse päring ning võrreldakse tulemusi algupärase objektiga. Lõpuks lastakse loodud objekt baasist maha.

Tegelikult saab testimist veidi põhjalikumaks teha ning anda ette väärtused, mida testida. See just kaslik siis References ning HasMany puhul. Näide siiski lihtne string-DateTime

[Test]
[Category("Integration")]
public void VerifyMapping_IsSuccessful()
{
    new PersistenceSpecification<Log>(Dao.Session)
    .CheckProperty(x=>x.Date, DateTime.Now)
    .CheckProperty(x=>x.Logger, NewString("Logger"))
        .VerifyTheMappings();
}

Millest seni puudust tundnud? Default Cascade entity tasandil (võibolla ei ole veel leidnud) ning private fieldide mappimine. Kuid viimase kohta käib vastavas arendajate google group-sis elav arutelu ehk ainult ajaküsimus, millal olemas.

nHib 2.0 and EventListener

Filed under: nHibernate — Mart Leet @ 20:17

Nagu alljärgnevalt näha kasutan projektis Castle NHibernateIntegration facility-t ning ka FluentNHibernatet. Castle komponent laseb mugavalt konfida seaded, haldab õige Configuration objekti kokkupanemist (tegelikult paneb selle ja SessionManageri jm IoC-ga alati kättesaadavasse kohta ka – detailidesse laskumine pole hetkel antud postituse point). Fluenti kohta võib aga vabalt omaette seeria postitusi teha.

Käesolevalt aga on teatud hulk entity-sid, mis implementeerivad interfase ITimeStamped. Tegelikult antud juhul on selle juures veel puudusi, kuid selle lahendamiseni veel jõuab.

public partial class TimeStampedSaveEventListener : DefaultSaveOrUpdateEventListener
{
    protected override object PerformSaveOrUpdate(SaveOrUpdateEvent evt)
    {
        if(evt.Entity is ITimeStamped)
        {
            ITimeStamped entity = evt.Entity as ITimeStamped;
            if(entity != null)
            {
                entity.DateLastModified = DateTime.Now;
            }
        }

        return base.PerformSaveOrUpdate(evt);
    }
}

Ja et asja ka nhibernate kasutusse võtaks on konfis see kirjas:

<facility type="Castle.Facilities.NHibernateIntegration.NHibernateFacility, Castle.Facilities.NHibernateIntegration"
          id="nhibernate"
          configurationBuilder="Seda.Pole.Vaja.Teada.FluentConfigurationBuilder, Seda.Pole.Vaja.Teada"
          isWeb="false"
          useReflectionOptimizer="false">
    <factory id="nhibernate.factory">
        <settings>
            <item key="connection.provider">NHibernate.Connection.DriverConnectionProvider</item>
            <item key="connection.driver_class">#{nhibernateDriver}</item>
            <item key="dialect">#{nhibernateDialect}</item>
            <item key="connection.connection_string">#{connectionString}</item>
            <item key="cache.provider_class">NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache</item>
            <item key="cache.use_query_cache">true</item>
            <item key="show_sql">true</item>
            <item key="relativeExpiration">30</item>

        </settings>
        <assemblies>
            <assembly>Seda.Pole.Vaja.Teada</assembly>
        </assemblies>
        <listeners>
            <listener type="Seda.Pole.Vaja.Teada.TimeStampedSaveEventListener, Seda.Pole.Vaja.Teada"
                      event="Save" />
            <listener type="Seda.Pole.Vaja.Teada.TimeStampedSaveEventListener, Seda.Pole.Vaja.Teada"
                      event="SaveUpdate" />
        </listeners>
    </factory>
</facility>

I Am NHibernating…

29/11/2008

DetachedCriteria [nHibernate series]

Filed under: nHibernate — Mart Leet @ 12:26
Käsil projekt, kus palju päringuid tehtud hsql-is. Minujaoks on see aga nagu SQL ehk ma ei viitsi mõelda SQL-is. Ehk ma katsetan nHibernate võimalusi ning jätan sellest märgi maha. 
       public IList<Article> GetDisplayArticlesBySectionAndRole(Section section, SortBy sortBy, SortDirection sortDirection, User user)
{
DetachedCriteria articlePermissions = DetachedCriteria.For<ArticlePermission>();
articlePermissions.SetProjection(Projections.Property(Permission.PropertyNames.Role));

Disjunction disjunction = new Disjunction();
foreach (Role role in user.Roles)
{
disjunction.Add(Property.ForName(Permission.PropertyNames.Role).Eq(role));
}
articlePermissions.Add(disjunction);

DetachedCriteria articleCriteria = DetachedCriteria.For<Article>();
articleCriteria.CreateCriteria(Article.PropertyNames.ArticlePermissions).Add(Subqueries.Exists(articlePermissions));

articleCriteria.Add(Expression.Eq(Article.PropertyNames.Section, section)).Add(Expression.Ge(Article.PropertyNames.DateOffline, DateTime.Now)).Add(
Expression.Le(Article.PropertyNames.DateOnline, DateTime.Now));

articleCriteria.AddOrder(GetOrderByClause(sortBy, sortDirection));

return Dao.Common.GetAllByCriteria<Article>(articleCriteria);
}
Ja genereeritud SQL ka siis:
NHibernate.SQL: 2008-11-29 12:23:21,056 [10] DEBUG NHibernate.SQL [(null)] - SELECT this_.articleid as articleid37_2_, this_.updatetimestamp as updateti2_37_2_, this_.title as title37_2_, this_.summary as summary37_2_, this_.content as content37_2_, this_.dateonline as dateonline37_2_, this_.dateoffline as dateoffl7_37_2_, this_.inserttimestamp as insertti8_37_2_, this_.syndicate as syndicate37_2_, this_.sectionid as sectionid37_2_, this_.createdby as createdby37_2_, this_.modifiedby as modifiedby37_2_, this_.articlecategoryid as article13_37_2_, category3_.articlecategoryid as articlec1_36_0_, category3_.updatetimestamp as updateti2_36_0_, category3_.title as title36_0_, category3_.summary as summary36_0_, category3_.syndicate as syndicate36_0_, category3_.siteid as siteid36_0_, articleper1_.Id as Id38_1_, articleper1_.ViewAllowed as ViewAllo2_38_1_, articleper1_.EditAllowed as EditAllo3_38_1_, articleper1_.RoleId as RoleId38_1_, articleper1_.ArticleId as ArticleId38_1_ FROM cm_article this_ left outer join cm_articlecategory category3_ on this_.articlecategoryid=category3_.articlecategoryid inner join articlerole articleper1_ on this_.articleid=articleper1_.ArticleId WHERE exists (SELECT this_0_.RoleId as y0_ FROM articlerole this_0_ WHERE (this_0_.RoleId = @p0 or this_0_.RoleId = @p1)) and this_.sectionid = @p2 and this_.dateoffline >= @p3 and this_.dateonline <= @p4; @p0 = '5', @p1 = '3', @p2 = '14', @p3 = '29.11.2008 12:23:14', @p4 = '29.11.2008 12:23:14'

Asja saaks oluliselt parandada, kuid see nõuab miski… terve mooduli päringute ümbertegemist ning hetkel pole selleks aega.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Edit:

Jabur loogika viga jäi sisse. Eks Subquery eripärade teadmatusest ka. Igatahes töötab selline variant:

            DetachedCriteria articlePermissions = DetachedCriteria.For<ArticlePermission>();
articlePermissions.SetProjection(Projections.Property(Permission.PropertyNames.Role));
articlePermissions.SetProjection(Projections.Property(ArticlePermission.PropertyNames.Article));
Disjunction disjunction = new Disjunction();
foreach (Role role in roles)
{
disjunction.Add(Property.ForName(Permission.PropertyNames.Role).Eq(role));
}
articlePermissions.Add(disjunction);


DetachedCriteria detachedCriteria = DetachedCriteria.For<Article>();
detachedCriteria.Add(Subqueries.PropertyIn("Id", articlePermissions));
detachedCriteria.Add(Expression.Eq(Article.PropertyNames.Section, section)).Add(Expression.Ge(Article.PropertyNames.DateOffline, DateTime.Now)).Add(
Expression.Le(Article.PropertyNames.DateOnline, DateTime.Now));

detachedCriteria.AddOrder(GetOrderByClause(sortBy, sortDirection));

return Dao.Common.GetAllByCriteria<Article>(detachedCriteria);

Ja SQL ka:

DEBUG NHibernate.SQL [(null)] - SELECT this_.articleid as articleid37_1_, this_.updatetimestamp as updateti2_37_1_, this_.title as title37_1_, this_.summary as summary37_1_, this_.content as content37_1_, this_.dateonline as dateonline37_1_, this_.dateoffline as dateoffl7_37_1_, this_.inserttimestamp as insertti8_37_1_, this_.syndicate as syndicate37_1_, this_.sectionid as sectionid37_1_, this_.createdby as createdby37_1_, this_.modifiedby as modifiedby37_1_, this_.articlecategoryid as article13_37_1_, category2_.articlecategoryid as articlec1_36_0_, category2_.updatetimestamp as updateti2_36_0_, category2_.title as title36_0_, category2_.summary as summary36_0_, category2_.syndicate as syndicate36_0_, category2_.siteid as siteid36_0_ FROM cm_article this_ left outer join cm_articlecategory category2_ on this_.articlecategoryid=category2_.articlecategoryid WHERE this_.articleid in (SELECT this_0_.ArticleId as y0_ FROM articlerole this_0_ WHERE (this_0_.RoleId = @p0)) and this_.sectionid = @p1 and this_.dateoffline >= @p2 and this_.dateonline <= @p3 ORDER BY this_.dateonline desc; @p0 = '4', @p1 = '14', @p2 = '29.11.2008 18:09:55', @p3 = '29.11.2008 18:09:55'

Äge ju…

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.