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; }
}
{
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));
}
}
}
{
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);
}
}
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 ?
- [Serializable]
- public class PostLoadEventListener : IPostLoadEventListener
- {
- public void OnPostLoad(PostLoadEvent @event)
- {
- var trackableEntity = @event.Entity as INotifyPropertyChanged;
- if(trackableEntity != null)
- {
- EntityEntry entry = @event.Session.PersistenceContext.GetEntry(@event.Entity);
- entry.BackSetStatus(Status.ReadOnly);
- entry.BackSetTracer(new EntityTracer(entry, trackableEntity));
- }
- }
- }
- public class EntityTracer
- {
- public EntityTracer(EntityEntry entry, INotifyPropertyChanged trackableEntity)
- {
- trackableEntity.PropertyChanged += (sender, e) => entry.BackSetStatus(Status.Loaded);
- }
- }
- [Serializable]
- public class PreDeleteEventListener : IDeleteEventListener
- {
- public void OnDelete(DeleteEvent @event)
- {
- OnDelete(@event, new IdentitySet());
- }
- public void OnDelete(DeleteEvent @event, ISet transientEntities)
- {
- var session = @event.Session;
- EntityEntry entry = session.PersistenceContext.GetEntry(@event.Entity);
- if (entry != null && entry.RowId is EntityTracer)
- {
- entry.BackSetStatus(Status.Loaded);
- }
- }
- }
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.
Very nice! It would also be nice if the good folks at uNHAddins would wrap all this in a easy to use fashion.
ReplyDeleteIt is so easy as add this two lines to your configuration
ReplyDeleteconfigure.EventListeners.PostLoadEventListeners = new IPostLoadEventListener[] { new PostLoadEventListener() };
configure.EventListeners.DeleteEventListeners = new IDeleteEventListener[] { new PreDeleteEventListener(), new DefaultDeleteEventListener() };
+2, for post and song :)
ReplyDeleteLove the original, but i usually code to the new one ;)
ReplyDeleteFabio you're always more steps forward :), probably T4 is a solution for a lot of scenarios, so, it could be a
ReplyDeletevalid 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!
we need to do a postsharp version; for change tracking and lazy loading..
ReplyDeleteIn other words, we can invade the domain at BUILD time, not a DESIGN time nor a RUN time.
To gain what? 60ms when 1000 entities are uploaded in the session?
ReplyDeleteWOW! 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