Blog Home  Home Feed your aggregator (RSS 2.0)  
NHibernate and calculated properties - Manuel Abadia's ASP.NET stuff
 
# Tuesday, 11 September 2007

I was optimizing an application that uses NHibernate. This application has some special requirements, and for a few classes, some of its properties are just complex calculations that use other properties. Those calculations cannot be performed using formulas at the database level; they have to be performed by the class. A simplified example is:

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using NHibernateUtil;

using NHibernate.Classic;

 

namespace Sample

{

    public class Item

    {

        private int _id;

        private int _value1;

        private int _value2;

        private decimal _result;

 

        public virtual int Id

        {

            get { return _id; }

            set { _id = value; }

        }

 

        public virtual int Value1

        {

            get { return _value1; }

            set { _value1 = value; }

        }

 

        public virtual int Value2

        {

            get { return _value2; }

            set { _value2 = value; }

        }

 

        public virtual decimal Result

        {

            get { return (decimal)(Math.Cos(Value1)*Math.Exp(Value2)) ; }

            set { }

        }

    }

}

 

 

The property Result is generated using the properties Value1  and Value2. In order for NHibernate to be able to map the Result property, a setter is needed, but as you can see, it does nothing.

An item is stored in the following table:

CREATE TABLE Items
(
    id int IDENTITY (1, 1) NOT NULL CONSTRAINT PK_Item PRIMARY KEY,
    value1 int NOT NULL,
   
value2 int NOT NULL,
   
result decimal(10, 2) NOT NULL
)
 

Optimizing the application I found out that everytime an Item was loaded, the item was also updated, even if the application didn’t change any of the item values. After thinking a bit, I found out the problem.

Imagine an Item with the Value1 = 1 and Value2 = 3.

When NHibernate loads the Item, it saves a copy of the original values for the Item, so the saved values are (Value1=1, Value2=3 and Result=10.85). When the session is flushed, NHibernate compares the old values with the current values, and if there is any difference, the object is marked as dirty, and then an update is sent to the database.

As the Result property is calculated, the value returned by the get method is always 10.852261914198, so NHibernate always marks the item as dirty.

An option to fix this is to round the Result to 2 decimal digits but that has the unwanted side effect to affect the precision of all operations that use the Result property.

A better option is to implement a custom interceptor that does this rounding for some specific properties. A NHibernate interceptor is a class that gets called when in some important instants in the ORM life cycle. In this particular scenario, the method I was interested in is the FindDirty method, that is called to find out if the current entity is dirty or not, and among the parameters passed to this method are the currentState and the previousState of the object.

What I did was to round there the current state of the generated properties. I needed a way to know which properties to round, so I created two custom attributes, GeneratedProperties and GeneratedProperty. In order for my interceptor to round a property, the class needs to have the GeneratedProperties attribute set, if not, it is not further processed.  When an instance implemented the GeneratedProperties attribute is found, its properties are inspected and all of them that have the GeneratedProperty attribute set are automatically rounded. The number of decimal digits to leave is specified in one of the attributes. It is mandatory for the GeneratedProperties attribute and optional in the GeneratedProperty attribute. If it is not specified in the GeneratedProperty attribute, the value specified in the GeneratedProperties attribute is used. So if you have a lot of properties stored in a similar way, you don’t have to specify the number of digits in several places.

The final Item class is:

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using NHibernateUtil;

using NHibernate.Classic;

 

namespace Smaple

{

    [GeneratedProperties(2)]

    public class Item

    {

        private int _id;

        private int _value1;

        private int _value2;

        private decimal _result;

 

        public virtual int Id

        {

            get { return _id; }

            set { _id = value; }

        }

 

        public virtual int Value1

        {

            get { return _value1; }

            set { _value1 = value; }

        }

 

        public virtual int Value2

        {

            get { return _value2; }

            set { _value2 = value; }

        }

 

        [GeneratedProperty]

        public virtual decimal Result

        {

            get { return (decimal)(Math.Cos(Value1)*Math.Exp(Value2)) ; }

            set { }

        }

    }

}

 

Here is the code for the interceptor:

using System;

using System.Reflection;

using NHibernate;

using NHibernate.Type;

 

namespace NHibernateUtil

{

    ///<summary>Interceptor that rounds generated values to avoid unnecessary updates to the DB.</summary>

    public class GeneratedPropertiesInterceptor : EmptyInterceptor

    {

        #region Methods

 

        /// <summary>Finds the dirty properties of an entity.</summary>

        /// <param name="entity">The entity.</param>

        /// <param name="id">The identifier of the entity.</param>

        /// <param name="currentState">Current state of the entity.</param>

        /// <param name="previousState">Previous state of the entity.</param>

        /// <param name="propertyNames">The property names of the entity.</param>

        /// <param name="types">The types associated with the properties.</param>

        /// <returns>The index of the changed properties or null to let NHibernate do the default check.</returns>

        public override int[] FindDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types)

        {

            // search the generated property attribute for the entity

            Type entityType = entity.GetType();

            GeneratedPropertiesAttribute attr = (GeneratedPropertiesAttribute)Attribute.GetCustomAttribute(entityType, typeof(GeneratedPropertiesAttribute), true);

 

            // if the entity has generated properties

            if (attr != null) {

                // iterate through the properties, modifying the generated ones

                for (int i = 0; i < propertyNames.Length; i++) {

                    string propertyName = propertyNames[i];

                    PropertyInfo property = entityType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);

 

                    // if the property does not exists, try the next one

                    if (property == null) {

                        continue;

                    }

 

                    // try to get the GeneratedPropertyAttribute for this property

                    GeneratedPropertyAttribute genAttr = (GeneratedPropertyAttribute)Attribute.GetCustomAttribute(property, typeof(GeneratedPropertyAttribute), true);

 

                    // if the attribute is present and the type is a decimal, round it

                    if ((genAttr != null) && (types[i].GetType() == typeof(DecimalType))) {

                        int numDecimals = (genAttr.DecimalDigits != 0) ? genAttr.DecimalDigits : attr.DecimalDigits;

 

                        currentState[i] = Math.Round((decimal)currentState[i], numDecimals);

                    }

                }

            }

 

            return base.FindDirty(entity, id, currentState, previousState, propertyNames, types);

        }

 

        #endregion

    }

}

 

In future posts I’ll rant on about NHibernate, its limitations and some things I didn’t like about it.

PS: I have changed the way I was displaying code. Previously I was using embedded styles and now the styles are not embedded any more. The reason for it is because the code doesn't look good with the current theme as I have switched to a dark colors in VS.NET to code. Let me know if this breaks the code display for your RSS reader.

GeneratedPropertiesInterceptor.zip (2,13 KB)
Tuesday, 11 September 2007 15:23:49 (Romance Daylight Time, UTC+02:00)  #    Comments [0]   NHibernate  | 
Copyright © 2017 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.