Custom XML Serialization

Another advantage of storing properties of entities into a state object based on a Dictionary, is the ability to easily serialize objects in custom formats. As an example I create an XML serializer that is capable to serialize an entity in a custom XML format.

I used this simple serializer to create a NHibernate User Type that permits me to save a child entity in a single XML column of SQL Server, a feature useful when you need to save objects which schema changes quite often and you do not want to keep database schema updated, or you need to store dynamic data into the DB. I now that all of you are screaming “USE NO SQL DB”, like Raven, but it is not simple to introduce new technologies into existing projects, and only to justify the need to save the 2% of objects.

Thanks to the custom serializer and the ability to do DeepCloning, writing such a User Type is really simple. First of all, I’ve implemented a method called EquivalentTo that permits me to compare two entities based on their state, this makes trivial writing the Equals Method of the UserType

bool IUserType.Equals(object x, object y)

{

    if (ReferenceEquals(x, y)) return true;

    if (x == null || y == null) return false;

 

    if (x is BaseEntity)

        return ((BaseEntity)x).IsEquivalentTo(y as BaseEntity);

 

    return x.Equals(y);

}

Same consideration for the IUserType.DeepCopy method, based on the Clone method of the base class. Saving and loading the object is just a matter of using the BaseEntity serialization methods.

public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)

{

    Int32 index = rs.GetOrdinal(names[0]);

    if (rs.IsDBNull(index))

    {

        return null;

    }

    String databaseContent = (String)rs[index];

    return Deserialize(databaseContent);

 

}

 

internal Object Deserialize(String content)

{

    XElement element =  XElement.Parse(content);

    return element.DeserializeToBaseEntity();

}

 

public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)

{

    if (value == null || value == DBNull.Value)

    {

        NHibernateUtil.String.NullSafeSet(cmd, null, index);

    }

    else

    {

        NHibernateUtil.String.Set(cmd, Serialize(value), index);

    }

 

}

 

internal String Serialize(Object obj)

{

    if (!(obj is BaseEntity))

        throw new ArgumentException("Only BaseEntity based entities could be serialized with this usertype", "obj");

    return (obj as BaseEntity).SerializeToXml().ToString();

}

The advantage of this approach, is that I have another base entity class called BaseExpandoEntity that permits to store into state object property by name, it is like a dynamic object, but I used it in .NET 3.5 where dynamics still does not exists. This kind of entity is clearly not really OOP, it is not well encapsulated, because you can set property of any name from external code and it is used mainly as a DataClass, just to store information in database without the need to be schema or class bounded. Now suppose to have a class called Father that has a property of type SpecificNotes, based on this BaseExpandoEntity and saved in database with the above User Type, you can write this code.

Father d1 = new Father () {...};

d1.SpecificNotes.SetProperty("Ciao", "mondo");

d1.SpecificNotes.SetProperty("Age", 80);

Repository.Father.Save(d1);

In the above code, the Father class has a property called SpecificNotes mapped as XML in database, I can store two new properties with the SetProperty and this is what is saved to database.

INSERT INTO Father

            (Id,

             xxx,

             yyy,

             SpecificNotes)

VALUES      (1,

             ...,

             ...,

             <SpecificNotes fname="Myproject.Entities.SpecificNotes, Myproject.Entities" ><Ciao>mondo</Ciao>

  <Age type="System.Int32">80</Age></SpecificNotes>' )

Now suppose you want to retrieve from the database all Father objects that have a SpecificNote with a  property named Ciao with value ‘mondo’, how could you issue the query since the object is stored as XML? The solution is creating a custom Criteria Operator. I do not want to bother you with the details, but the key method is something like this

public override NHibernate.SqlCommand.SqlString ToSqlString(

    NHibernate.ICriteria criteria, 

    ICriteriaQuery criteriaQuery, 

    IDictionary<string, NHibernate.IFilter> enabledFilters)

{

    string objectPropertyColumnName = criteriaQuery

        .GetColumnsUsingProjection(criteria, PropertyName)[0];

 

    StringBuilder criteriaText = new StringBuilder();

    criteriaText.Append(objectPropertyColumnName);

    criteriaText.Append(".value('(/*/");

    criteriaText.Append(XmlCriterionParameters.ChildPropertyName);

    criteriaText.Append(")[1]', 'nvarchar(max)')");

    switch (XmlCriterionParameters.CriteriaOperator)

    {

        case CriteriaOperator.Equal:

            criteriaText.Append("=");

            break;

        case CriteriaOperator.Like:

            criteriaText.Append(" like ");

            break;

        default :

            throw new NotSupportedException("Still not supported operator");

    }

 

    criteriaText.AppendFormat("'{0}'", Value);

    return new SqlString(criteriaText.ToString());

 

}

In this example I’ve implemented only “=” and “like” operators, but it is enough for this sample. Now I can issue the following query.

Query query = Query.CreateXml("SpecificNotes", "Ciao", CriteriaOperator.Equal, "mondo");

var result = Repository.Father.GetByCriteria(query);

This snippet used a Query Model to specify the query, but as you can see the important aspect is that I’m able to create an XML criterion on the “SpecificNotes” sub object, where the expando property “Ciao” is Equal to the value “mondo”. Here is the query that was issued to the DB

SELECT ...

FROM   Father this_

WHERE  this_.SpecificNotes.value('(/*/Ciao)[1]', 'nvarchar(max)') = 'mondo'

This is quite a primitive query, because of the /*/ that basically mean I’m searching for a property Ciao contained in any subobject, but you can modify your Custom Criterion to adapt the XPath to your need; the important aspect is that I’m now able to store and retrieve transparently my expando objects from a standard SQL server thanks to the great flexibility of NHibernate.

Gian Maria.

Use SQLite to test NH–DateAdd function

   1: DateAdd(dd, T.AnalysisFrequence, :refdate)

Quite often I use SQLite to create Unit Tests of NH queries with a fast In-Memory database, and then run the queries against a standard Sql Server database.

Today I have this problem, an HQL query uses the DateAdd Sql Server function

   1: DateDiff(dd, T.AnalysisFrequence, :refdate)

In this query with the DateAdd function I want to obtain a date that is calculated adding an amount of days from a reference date. Running this query against a SQLite database gave me an error, clearly because there is no DateAdd function in SQLite.

The solution is simple, you can use the julianday if you are using the “dd” constant in the DateAdd function (you are subtracting days and not seconds or other time value). You can add this function to your custom dialect to create a DateAdd function in the SQLite dialect.

RegisterFunction("DateAdd", new SQLFunctionTemplate(NHibernateUtil.Date, "julianday(?3) + ?2"));

This is valid only if you use the DateAdd(“dd”, xxx, yy) but since this is my situation, this solves my problems.

Alk.

Using schema with NHibernate and SqlLite

Some time ago I blogged about a technique to use SqLite in Unit testing when you have nhibernate mapping that targets Sql Server tables in a schema different from dbo. The problem was: if you specify the schema name in the mapping of a class, then you are not able to execute the test in SqLite database.

Ex. if you have this mapping.

image

This is a mapping for a table called EngagementView in a schema called report and when u try to run some test on this entity with SqLite you got an error telling that there is no report database. The solution is to use the schema element of the mapping. The right schema should be

   1: <class name="xxxxx"  

   2:     schema="report" table="EngagementView" />

With this mapping NHibernate understand that the table EngagementView is on the schema report and the SqLite dialect is able to handle this without the need of the trick described in my old post. If you now issue a query against the EngagementView object you can verify that the query is issued again a table called report_EngagementView. This happens because SqLite has no concept of schema and the dialect can correctly generate the table name prepending the Table name with Schema and an underscore.

Alk.

A strange bug in NHibernate 2.1

In a previous post I described a technique to insert query hints into NHibernate query with the use of comments. Testing this code in a real project lead to a strange exception when I issue queries with ICriteria

The query should start with ‘SELECT’ or ‘SELECT DISTINCT’

This happens because ICriteria queries inserts comments inside the query and if you enable comments to flow into the query with the setting use_sql_comments something weird happens when you use SetMaxResults to issue a paginated query. If you look into the NHibernate code that is throwing the exception you find this function in the Sql Dialect

   1: private static int GetAfterSelectInsertPoint(SqlString sql)

   2: {

   3:     if (sql.StartsWithCaseInsensitive("select distinct"))

   4:     {

   5:         return 15;

   6:     }

   7:     else if (sql.StartsWithCaseInsensitive("select"))

   8:     {

   9:         return 6;

  10:     }

  11:     throw new NotSupportedException("The query should start with 'SELECT' or 'SELECT DISTINCT'");

  12: }

As the name of the function states, the purpose of this code is finding the insert point in the query immediately after the Select part of the query and is used when you call SetMaxResult to insert the TOP keyword immediately after the select clause. Since using ICriteria can generate comments in the SQL like /* criteria query */, this lead to an obvious exception because the query does not start with a  standard select or select distinct. But having comment does not harms in any way the generation of a paginated query, so I changed the function in this way

   1: private static int GetAfterSelectInsertPoint(SqlString sql)

   2: {

   3:     Int32 selectPosition = 0;

   4:     if ((selectPosition = sql.IndexOfCaseInsensitive("select distinct")) >= 0)

   5:     {

   6:         return selectPosition + 15; // "select distinct".Length;

   7:     }

   8:     else if ((selectPosition = sql.IndexOfCaseInsensitive("select")) >= 0)

   9:     {

  10:         return selectPosition + 6; // "select".Length;

  11:     }

  12:     throw new NotSupportedException("The query should start with 'SELECT' or 'SELECT DISTINCT'");

  13: }

Using InexOfCaseInsensitive function permits me to find the insertion point after the select clause even if some comments are present in the query. Now everything works as expected as you can verify from NhibernateProfiler. I run all the NH tests and they are all green, excepts one that verify that an exception is thrown if the query begins with /* criteria query */, but this is the bug I want to fix :) so I do not care about it :). Now I run the query again with my custom recompiled version of nhibernate and I got.

image

Figure 1: The top 5 clause was correctly inserted in the query even if there is a comment present in the query

Gotcha: the bug is solved, top 5 was correctly inserted even if there are comments on top of the query. Having the source of a library is invaluable if you need to fix bug :)

alk.

Use Sql Server Query Hints with NHibernate HQL and ICriteria

When you work with Big databases with many records and not uniform distribution of data into columns used for join or where conditions, you can have really bad performance problem due to Query Plan caching. I do not want to give a deep explanation of this problem, you can find information here, but I want to outline the general problem to make clear what is happening.

We can argue a lot why this problem happens in a database engine, but basically I have a really bad situation where the db stores data of multiple customers with really non uniform distribution of data (some of them have ~1000 rows for each table, others have ~100.000 rows in some of the tables).

The problem is the following one: the software issue a query (two tables in join) filtered with a customer Id that has few rows on these tables; SQL-Server creates a plan where tables are joined with nested loops and the query executes in 2 milliseconds.

Untitled2

Figure 1: Query with a value of parameter that filters for a customer that has ~100 rows in the two tables used in the join (elapsed time = 2ms).

As you can see the execution time is almost zero, now I execute the very same query, changing the value of the parameter to filter records for a customer that has nearly 100.000 records, since the query is the same, sql server uses the same execution plan.

Untitled3

Figure 2: Query gets executed filtering for a customer that has ~100.000 rows in the two table that joins together (elapsed time = 2824 ms).

The query is three order of magnitude slower (2.824 ms vs 2 ms) and for query that are more complex (three tables in join), sometimes the execution time is more than 5 minutes and the software gives a TimeoutException. As you can see from Figure 2, the gray arrow from the bottom table is really wider respect of the arrows of Figure 1, because this customer has more rows in database. If you issue a DBCC FREEPROCCACHE command to clear all cached query plans and execute again the query of Figure 2, you got an execution time of 28 ms (100 times faster than before XD).

Untitlewerwerd 

Figure 3: Join is done with an hash match, a technique that is more efficient for joining two tables with a lot of rows. (elapsed time = 28 ms).

In figure 3 you can see that if you clear the query plan cache and execute again the query, SQL Server check the table, and since this customer has a lot of rows it uses HASH MATCH join and not a nested loop. Now if you execute the query for the customer used in Figure 1 that has few rows, the execution time is 28 ms, higher than 2 ms because an hash match is less efficient for few rows.

This lead to a nasty problem due to order of execution of the query: if I issue the same query, first for the customer with a lot of rows and then for the customer with few rows, I got an execution time of 28 ms for each query, if I reverse the order of the query I got 2 ms and 2800 ms thus the system is much slower . This happens because the Hash Match is not good for few number of rows, (28 ms against 2 ms), but the loss of performance when we have a small set of record is not so bad; at the contrary, the Nested Loop is a KILLER for big resultset and this can even cause execution Timeout. Having such a problem in production is like sitting on a bomb ready to explode. (A customer calls telling us that the software is completely unresponsive, you verify that this is true, but for other customers everything is quite good :(

Untitled

The only solution to this approach is using the OPTION (NORECOMPILE) for all queries that present this problem, or you can instruct the query governor to prefer some join option (using OPTION (HASH JOIN) for example) if you already know that this is the right execution plan for all data distribution. All these techniques are called “query hint” and are the only way to solve bad performance problem of parameterized query when you have non uniform data distribution.

Now the problem seems solved, but, wait!!!!!, most of the queries are issued by nhibernate, and I need to find a way to add query hints to nhibernate query, a task that is not supported natively by NH. Solving this problem is a two phase process, first of all you need to find a way to insert text into nhibernate generated SQL, a task that can easily solved by an interceptor.

   1: [Serializable]

   2: public class QueryHintInterceptor : EmptyInterceptor

   3: {

   4:     internal const string QueryHintHashJoinCommentString = "queryhint-option-hash";

   5:     internal const string QueryHintRecompileCommentString = "queryhint-recompile";

   6:  

   7:     public override NHibernate.SqlCommand.SqlString OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)

   8:     {

   9:         if (sql.ToString().Contains(QueryHintHashJoinCommentString))

  10:         {

  11:             return sql.Insert(sql.Length, " option(HASH JOIN)");

  12:         }

  13:         if (sql.ToString().Contains(QueryHintRecompileCommentString))

  14:         {

  15:             return sql.Insert(sql.Length, " option(RECOMPILE)");

  16:         }

  17:         return base.OnPrepareStatement(sql);

  18:     }

  19: }

As you can see the code is really simple, the interceptor inherits from EmptyInterceptor and override the OnPrepareStatement(), adding the right Query Hint to the end of the query. This is probably not 100% production ready code, because I’m not 100% sure that for complex query, inserting the hint at the end of the query is the right choice, but for my specific problem is enough and it is a good starting point.

If you look at the code you can verify that I’m checking for certain string in query text to add the appropriate hint, but how can you add these strings to the query to enable query hint to be generated? The answer is “comments”. First of all I add this interceptor to NH configuration, so it got attached to every session.

   1: cfg.SetInterceptor(new QueryHintInterceptor());

But I also enable comments in SQL code in NH configuration setting the property use_sql_comments to true, now I only need a couple of extension methods like this one.

   1: public static IQuery QueryHintRecompile(this IQuery query)

   2:  {

   3:      query.SetComment(QueryHintInterceptor.QueryHintRecompileCommentString);

   4:      return query;

   5:  }

That enables me to write this HQL query.

   1: session.CreateQuery("Select y from LinkResult y where ClieId = :param")

   2:     .QueryHintRecompile()

   3:     .SetInt32("param", 11)

   4:     .List();

As you can see I have now the QueryHintRecompile() method that permits me to specify for each single HQL Query (you can add similar extension method to ICriteria) the query hint to use. Running this query output this query.

23-07-2011 10-36-27

Figure 4: The query with the query hint specified by the QueryHintRecompile()

Thanks to incredible number of extension points offered by NHibernate, adding Query Hint to queries is really a breeze.

Alk.

Tags: