Try fast search NHibernate

11 October 2010

TURBO NHibernate with domain invaders

The idea of this post have started at the local Buenos Aries CodeCap 2010 where I have attended : “In loving by Entity Framework”… well at that moment my nice girlfriend was at home. I saw a really nice girl… well… they have shown me her face and a big nice dress that covered the entire body ... I asked to see her in a bikini but it was not possible.
Seriously (I’ll try) the question to myself was: can we take some advantage, from a code generator, creating those fat POCOs ?
I know that some of my posts may seems does not have sense… to find some hidden gems in NHibernate is not so easy ;-)

Puorte 'e cazune cu nu stemma arreto...na cuppulella cu 'a visiera aizata...

public class PocoEntity
{
    public virtual Guid Id { get; set; }
    public virtual string Description { get; set; }
}

Tu vuo’ fa’ ll’americano…sient'a mme chi t' 'o ffa fa'?

public class SelfTrackingEntity : INotifyPropertyChanged
{
    private string description;
    public virtual Guid Id { get; set; }

    public virtual string Description
    {
        get { return description; }
        set
        {
            if (value != description)
            {
                description = value;
                NotifyPropertyChanged("Description");
            }
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    private void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

Ok, I have the two entities, and now I can start the proof of concept.
I’ll fill the DataBase with one thousand instances for each entity, then I’ll upload 1000 instances of each, all in one session, I’ll modify few (only 10 entities) and then I’ll flush the session tracking the time of the flush. In other words:

[TestFixtureSetUp]
public void DbCreation()
{
    nhinit = new NHibernateInitializer();
    nhinit.Initialize();
    nhinit.CreateSchema();
    sf = nhinit.SessionFactory;
    FillDb();
}

public void FillDb()
{
    using (var s = sf.OpenSession())
    using (var tx = s.BeginTransaction())
    {
        for (int i = 0; i < 1000; i++)
        {
            s.Persist(new PocoEntity { Description = "pocoValue" + i });
        }
        tx.Commit();
    }

    using (var s = sf.OpenSession())
    using (var tx = s.BeginTransaction())
    {
        for (int i = 0; i < 1000; i++)
        {
            s.Persist(new SelfTrackingEntity { Description = "SelfTrackingEntityValue" + i });
        }
        tx.Commit();
    }
}

[Test]
public void TimeToFlushPocoEntities()
{
    using (var s = sf.OpenSession())
    using (var tx = s.BeginTransaction())
    {
        var entities = s.QueryOver<PocoEntity>().List();
        var someToModify = entities.Skip(4).Take(10);
        foreach (var entity in someToModify)
        {
            entity.Description = "Modified";
        }
        var stopWath = new Stopwatch();
        stopWath.Start();
        tx.Commit();
        stopWath.Stop();
        Console.WriteLine("Milliseconds to flush and commit:" + stopWath.ElapsedMilliseconds);
    }          
}

[Test]
public void TimeToFlushSelfTrackingEntities()
{
    using (var s = sf.OpenSession())
    using (var tx = s.BeginTransaction())
    {
        var entities = s.QueryOver<SelfTrackingEntity>().List();
        var someToModify = entities.Skip(4).Take(10);
        foreach (var entity in someToModify)
        {
            entity.Description = "Modified";
        }
        var stopWath = new Stopwatch();
        stopWath.Start();
        tx.Commit();
        stopWath.Stop();
        Console.WriteLine("Milliseconds to flush and commit:" + stopWath.ElapsedMilliseconds);
    }
}

 

Tu abball' o' rocchenroll tu giochi a baisiboll... ma e solde p' e' Ccamel chi te li dá ?

The result ?

For POCO entities: 72 Milliseconds
For entities with INotifyPropertyChanged : 11 Milliseconds

The difference is really big (auto-dirty-check is 7 times slower), but the unit of measurement is not (milliseconds).

Tu vuo' fa' ll'americano ma si' nato in Italy!

How much code I wrote to have this result ?
what about this ?
  1. [Serializable]
  2. public class PostLoadEventListener : IPostLoadEventListener
  3. {
  4.     public void OnPostLoad(PostLoadEvent @event)
  5.     {
  6.         var trackableEntity = @event.Entity as INotifyPropertyChanged;
  7.         if(trackableEntity != null)
  8.         {
  9.             EntityEntry entry = @event.Session.PersistenceContext.GetEntry(@event.Entity);
  10.             entry.BackSetStatus(Status.ReadOnly);
  11.             entry.BackSetTracer(new EntityTracer(entry, trackableEntity));
  12.         }
  13.     }
  14. }
  15.  
  16. public class EntityTracer
  17. {
  18.     public EntityTracer(EntityEntry entry, INotifyPropertyChanged trackableEntity)
  19.     {
  20.         trackableEntity.PropertyChanged += (sender, e) => entry.BackSetStatus(Status.Loaded);
  21.     }
  22. }
  23.  
  24. [Serializable]
  25. public class PreDeleteEventListener : IDeleteEventListener
  26. {
  27.     public void OnDelete(DeleteEvent @event)
  28.     {
  29.         OnDelete(@event, new IdentitySet());
  30.     }
  31.  
  32.     public void OnDelete(DeleteEvent @event, ISet transientEntities)
  33.     {
  34.         var session = @event.Session;
  35.         EntityEntry entry = session.PersistenceContext.GetEntry(@event.Entity);
  36.         if (entry != null && entry.RowId is EntityTracer)
  37.         {
  38.             entry.BackSetStatus(Status.Loaded);
  39.         }
  40.     }
  41. }

 

sient' a mme: nun ce sta niente 'a fa' ok, napulitan!

This was only a proof of concept, perhaps it has memory leaks, perhaps I have used some nasty tricks… but it works.
Now is the time to apply some very little little changes in NHibernate core to allow a more easy implementation of this and others possible features.
P.S.: the sound track is available here.

8 comments:

  1. Very nice! It would also be nice if the good folks at uNHAddins would wrap all this in a easy to use fashion.

    ReplyDelete
  2. It is so easy as add this two lines to your configuration
    configure.EventListeners.PostLoadEventListeners = new IPostLoadEventListener[] { new PostLoadEventListener() };
    configure.EventListeners.DeleteEventListeners = new IDeleteEventListener[] { new PreDeleteEventListener(), new DefaultDeleteEventListener() };

    ReplyDelete
  3. Love the original, but i usually code to the new one ;)

    ReplyDelete
  4. Fabio you're always more steps forward :), probably T4 is a solution for a lot of scenarios, so, it could be a
    valid alternative than to the dynamic proxy. For example I'm starting an implementation for asynchronous proxy for WCF in Silverlight, on http://hyperionsdk.codeplex.com.

    The post accompanied with the song is wonderful!

    ReplyDelete
  5. we need to do a postsharp version; for change tracking and lazy loading..
    In other words, we can invade the domain at BUILD time, not a DESIGN time nor a RUN time.

    ReplyDelete
  6. To gain what? 60ms when 1000 entities are uploaded in the session?

    ReplyDelete
  7. WOW! Fábio, NHibernate is always surprising me! Well, I think it is like a skate, in the right hands you can do radical things easily heheheh

    ReplyDelete