by Mikael Henriksson
15. December 2009 16:06
If I had it my way we would probably have the whole darn thing in one and the same table but that would not work out long term. The problem I was facing was to fetch one entity based on values in the parent and 2 other (to the parent) related entities. Then I want to eagerly fetch information from the parent.
// Create criteria and add sorting based on version
var criteria = work.Session.CreateCriteria<ContactVersion>("v")
.AddOrder(Order.Desc("v.Version"));
// Create restriction based on that the contact should:
// 1. Not be deleted and
// 2. Match the accountId
criteria.CreateCriteria("v.Contact", "c")
.Add(Restrictions.IsNull("c.DeletedAt"))
.Add(Restrictions.Eq("c.Id", accountId));
// Also add a restriction based on uid
criteria.CreateCriteria("c.Devices", "dc")
.Add(Restrictions.Eq("dc.Id", uid));
// Lastly add a restriction for the current device id and
// eagerly fetch the corresponding contact.
criteria.CreateCriteria("dc.Device", "d")
.Add(Restrictions.Eq("d.Id", deviceId))
.SetFetchMode("c.Contact", FetchMode.Join)
.SetFetchSize(1)
.SetMaxResults(1);
var c = criteria.UniqueResult<ContactVersion>();
This gives me the desired result and the query looks like follows:
SELECT top 1 * /* removed all the columns */
FROM contact_version this_
inner join contact_def c1_
on this_.contact_id = c1_.contact_id
inner join contact_device_uid dc2_
on c1_.contact_id = dc2_.contact_id
inner join device_def d3_
on dc2_.device_id = d3_.device_id
WHERE this_.contact_version_ordinal = 3 /* @p0 */
and c1_.dte_deleted is null
and c1_.subscription_id = 4 /* @p1 */
and dc2_.contact_uid = 1 /* @p2 */
and d3_.device_id = 1 /* @p3 */
A quick look at the query execution plan shows nothing really strange so I suppose I sort of got it right. At least I got the data that I asked for..
by Mikael Henriksson
13. November 2009 01:34
It’s great for several reasons. I’ll try and cover some of them briefly
- It detects possible problems with your configuration / mappings and this is fantastic. You spot those N+1 and Unbounded result sets before someone complains about a crash or performance issues.
- It outputs the SQL in a very understandable and readable format. I never could stand watching the SQL Profiler.
- It tells you about internal NHibernate errors. I spent too much time opening management studio trying to see if things ended up in the database or not. If there is an error it’s likely I don’t have to bother.
- It tells you if a query was read from the cache or database. I had some implementation problems that cause my queries to always be fetched from the database without nh prof it would have taken me some time to figure out why.
- It is so easy to use. Add a reference to the Appender.dll and make sure you run Initialize() on the chosen profiler and your done.
Try it out http://nhprof.com/download
by Mikael Henriksson
19. September 2009 13:12
NHibernate performance sucked yesterday. Not because NHibernate itself is slow but because I wasn’t using it correctly. In a (by yours truly) newly created system I was not only using NHibernate for minimal performance. I was also using it in an unsafe manner. In case something went to hell there was no easy way for me to roll back the changes. This is particularly bad in a system for let’s say billing. Let’s say we grade/rate/prepare 80,000 customers for being billed. I don’t want this process to complete if there is something wrong with even a single record I want it to hit everyone in the face that –“Hey guys we have no customers to bill, wtf should be done about it?”. At the same time rating 1000 records was taking around 5 minutes.
After some tweaking here and there without much of a difference I decided maybe it’s time to try something crazy like using a unit of work for the entire process. The logical unit of work with minimal effort is of course the transaction. Easy enough I place all the existing code in a transaction, check if a transaction is open in the dao/repository/data context and then only issue a SaveOrUpdate(entity) if this is true. At the end of the processing of all the message I issue a transaction.Commit() and I have set the FlushMode to FlushMode.Commit. I went from 1000 records in 5 minutes to 20,000 records in 30 seconds. After the first 20,000 inserts/saves/updates depending on what the rating does it becomes slower. This does not surprise me a bit since I am not releasing any resources and that has nothing to do with NHibernate. That has to do with bad resource allocation and bad crap management on my end.
This is the short version of what the main code would look like:
using (session.BeginTransaction())
{
try
{
session.FlushMode = FlushMode.Commit;
IEnumerable<Record> records = GetAllRecords(); // load tons of records
foreach (var record in records)
{
// do loads of operations
_recordRepositoryDataContextDAO.SaveOrUpdate(record);
}
session.Transaction.Commit();
}
catch
{
session.Transaction.RollBack();
throw;
}
}
And the method to persist the changes:
if (_session.Transaction == null)
{
using (var transaction = _session.BeginTransaction())
{
_session.SaveOrUpdate(billingRecord);
transaction.Commit();
}
}
else
{
_session.SaveOrUpdate(billingRecord);
}
by Mikael Henriksson
13. August 2009 20:39
First of all I just want to state that I have no idea what I am doing but by the looks of things it is working… :) I found a ICustomType that I was almost happy with except that it stored the XmlDocument as varchar(4000). I’ll add that code at the end because it is 100 something lines. Let’s start with how difficult this was… NOT but maybe I just got lucky. Just using my XmlType class it didn’t work. Complained about System.Xml.XmlDocument could not be found when I tried mapping it so I had a look in the NHibernate source code and quickly I was able to find MsSql2005Dialect.cs and SqlTypeFactory.cs and two spots where I could potentially add code to make it work. I started with the dialect and since 2008 dialect inherits from 2005 it also inherits the xml type:
RegisterColumnType(DbType.Xml, "XML");
And then of course in SqlTypeFactory:
public static readonly SqlType Xml = new SqlType(DbType.Xml);
Now all that should be needed is to add that the column of your choice is of a custom type:
Map(x => x.ContactData, "contact_data").CustomType(typeof(XmlType));
Then a neat little class that inherits from SqlType. The last two classes could reside in
public class SqlXmlType : SqlType
{
public SqlXmlType()
: base(DbType.Xml)
{
}
}
And lastly the class that does the work:
using System;
using System.Data;
using System.Data.Common;
using System.Xml;
using global::NHibernate.SqlTypes;
using global::NHibernate.UserTypes;
public class XmlType : IUserType
{
public new bool Equals(object x, object y)
{
if (x == null || y == null)
return false;
var xdoc_x = (XmlDocument)x;
var xdoc_y = (XmlDocument)y;
return xdoc_y.OuterXml == xdoc_x.OuterXml;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
if (names.Length != 1)
throw new InvalidOperationException("names array has more than one element. can't handle this!");
var document = new XmlDocument();
var val = rs[names[0]] as string;
if (val != null)
{
document.LoadXml(val);
return document;
}
return null;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var parameter = (DbParameter)cmd.Parameters[index];
if (value == null)
{
parameter.Value = DBNull.Value;
return;
}
parameter.Value = ((XmlDocument)value).OuterXml;
}
public object DeepCopy(object value)
{
var toCopy = value as XmlDocument;
if (toCopy == null)
return null;
var copy = new XmlDocument();
copy.LoadXml(toCopy.OuterXml);
return copy;
}
public object Replace(object original, object target, object owner)
{
throw new NotImplementedException();
}
public object Assemble(object cached, object owner)
{
var str = cached as string;
if (str != null)
{
var doc = new XmlDocument();
doc.LoadXml(str);
return doc;
}
else
{
return null;
}
}
public object Disassemble(object value)
{
var val = value as XmlDocument;
if (val != null)
{
return val.OuterXml;
}
else
{
return null;
}
}
public SqlType[] SqlTypes
{
get
{
return new SqlType[] { new SqlXmlType() };
}
}
public Type ReturnedType
{
get { return typeof(XmlDocument); }
}
public bool IsMutable
{
get { return true; }
}
}
I created a patch for this in jira and I found the original file here.
by Mikael Henriksson
8. May 2009 00:13
By coincidence I found something called Fluent NHibernatewow automatic mappings etc. After having a read up on NHibernateI also found something called NHibernate Validationthat seemed to serve my purposes well. The only reason for having a look around was to easily add validation to my classes. I found this a bit hard with Entity Framework.
All was well and good I started of with learning how to map entities with fluent. After a couple of hours I had produced a model that I could use. Then I hooked in the validation dll. Tried to validate one of my entities with an attribute. I am not very fond of the whole xml configuration thing to tell you the truth. It’s more work in the long run. I find that if I need to change something I am changing at code level so I might as well change the attribute! Anyway, when I try to instantiate the ValidatorEngie with:
var validator = new ValidatorEngine();
It throws an exception! It’s due to using an older version of NHibernate so I try to upgrade NHibernate and then everything else starts throwing exceptions or complaining about versions. I added some version remaps in the app.config but not luck.
That’s the time I stop trying and go back to Entity Framework. Even if it is still a bit immature it is a piece of cake setting up even for more advanced scenarios and with version 2 on the way I rather have to quickly create a validation service than have to get in to every single detail about NHibernate!
Thanks but no thanks!