NHibernate Pitfalls: Refreshing Manually Created Entities Does Not Bring Lazy Properties of Base Types
This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.
Suppose you have some properties of an entity, including its id, and you want to refresh its state from the database:
1: var product = new Product { ProductId = 1 };
2:
3: //product.Image is null
4:
5: session.Refresh(product);
6:
7: //product.Image is null
Image is a Byte[] and is configured as lazy. The problem is that NHibernate is not capable of returning a proxy for it, because the entity itself is not a proxy.
This does not happen for associated entities (one-to-one, many-to-one, one-to-many and many-to-many):
1: var order = new Order { OrderId = 1 };
2:
3: //order.Customer is null
4:
5: session.Refresh(order);
6:
7: //order.Customer is a proxy and accessing the property will load it
In this case, Customer is another entity, and NHibernate can assign the Order.Customer property a proxy to it.
Because of this problem, I created a simple extension method that loads all properties. It is even smart enough to use proxies, if we so require it:
1: public static void InitializeLazyProperties(this ISession session, Object entity, Boolean useProxyWhenPossible = true)
2: {
3: if (entity.IsProxy() == true)
4: {
5: //if entity is a proxy, use the default mechanism
6: NHibernateUtil.Initialize(entity);
7: }
8: else
9: {
10: var metadata = session.SessionFactory.GetClassMetadata(entity.GetType());
11: var propertiesToLoad = new List<String>();
12:
13: for (var i = 0; i < metadata.PropertyNames.Length; ++i)
14: {
15: if (metadata.GetPropertyValue(entity, metadata.PropertyNames[i], session.ActiveEntityMode) == null)
16: {
17: if ((metadata.PropertyTypes[i].IsEntityType == false) || (useProxyWhenPossible == false))
18: {
19: //either load the value
20: propertiesToLoad.Add(metadata.PropertyNames[i]);
21: }
22: else
23: {
24: //or the id of the associated entity
25: propertiesToLoad.Add(String.Concat(metadata.PropertyNames[i], ".id"));
26: }
27: }
28: }
29:
30: var hql = new StringBuilder();
31: hql.Append("select ");
32: hql.Append(String.Join(", ", propertiesToLoad));
33: hql.AppendFormat(" from {0} where id = :id", entity.GetType());
34:
35: var query = session.CreateQuery(hql.ToString());
36: query.SetParameter("id", metadata.GetIdentifier(entity, session.ActiveEntityMode));
37:
38: var result = query.UniqueResult();
39: var values = result as Object[] ?? new Object[] { result };
40:
41: for (var i = 0; i < propertiesToLoad.Count; ++i)
42: {
43: var parts = propertiesToLoad[i].Split('.');
44: var value = values[i];
45: var propertyName = parts.First();
46:
47: if (parts.Length > 0)
48: {
49: var propertyIndex = Array.IndexOf(metadata.PropertyNames, propertyName);
50: var propertyType = metadata.PropertyTypes[propertyIndex].ReturnedClass;
51:
52: //create a proxy
53: value = session.Load(propertyType, values[i]);
54: }
55:
56: metadata.SetPropertyValue(entity, propertyName, value, session.ActiveEntityMode);
57: }
58: }
59: }
Of course, it requires that at leat the id property is set. It can be used as:
1: var order = new Order { OrderId = 360448 };
2:
3: session.InitializeLazyProperties(order);
Have fun!