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:

Entity Framework, a super dataset?

Entity Framework is quite a good product, but in my opinion still misses some point to be called an ORM. I must admit that I never used the 4.1 Code first in real project, but there are some stuff that still does not convince me when using EF.

When I decide to use EF (and not NH)

Sometimes I need to access small legacy databases to do a small amount of operations and I must admit that adding a EF Model from Visual Studio is a matter of seconds and the LINQ provider of EF is quite good so it is a valuable alternative to create a quick DAL. I have a simple database (four tables), I created an EF model, and then issue this query.

image

Figure 1: A very simple piece of code that query database with EF

This is a very super simple piece of code, in the first line I grab an instance of Term from the database (actually I grab the first one, in real code I have a complex query that returns a specific term). Then I want to grab all instance of Verbs that are related to this term, so I issue the second query, what will be the result?

This image exceptionally capture my face the first time I ran this query.

SNAGHTML4bb74e

Figure 2: Strange error in the query.

I’m really disappointed from this exception for many reasons. First of all I do not know why it does not work, because this is a perfectly and reasonable query to issue.

Fix the query to make it works

To verify that the query is indeed ok lets modify it in this way.

image

Figure 3: Modified version of the query, now it works.

This version works simply changing the order of the Where and ToList operators. It now works because it loads the entire Verbs table in memory and then filters out with LINQ to Object. This clearly makes me sick, because this creates a big memory pressure. The second issue with the above error is the error message, that is really obscure. Unable to create a constraint value of type xxxxxxxx. If you read with caution you could eventually understand that you cannot put a condition based on equality of objects, but only primitive type.

This error could have a good reason to exists: if the Term entity overrides the Equality method, it could be impossible to create an equivalent query in SQL to mimic the same behavior by the EF LINQ provider. Suppose that the Term overrides the Equal operator comparing two entities by checking for equality of the Value and Type properties, how could the EF provider understand this behavior to issue an equivalent query to the database? I can agree with this reason, but only if the error would be something like “Could not issue a comparison between two entities in a EF query because it is impossible to mimic the semantic of equals operator.. bla bla bla”.

A real solution, but you need to make your hands dirty

But working with objects in DDD or Domain Model or even with Active Record pattern, the programmer in 99.99% of the situation, translate the condition V.Term == term with: “gives me all verbs that are connected with term”. This because we are used to the fact that a reference from Verb entity to Term entity is part of the object graph and this can easily be translated with a comparison between identity operators. Given that, you can rewrite the above query in this way.

image

Figure 3: The right way to issue the EF query

Now the query runs fine, but you can have two completely different opinion about it.

The first opinion is negative, because I need to understand the name of the property/properties used as id by the ORM. I know that by convention you can use a property named Id for every entity, but sometimes, especially with legacy database you can have entities with composite key or identity property named differently. LINQ is great to express query in the object model, but in this situation I need to be aware of details of the ORM Mapping to issue the right query and this is bad.

The second type of opinion is positive, because this query does not suffer from mismatch between the LINQ to entities counterpart when someone redefines the equals operator for the entity.

Conclusion

I tend to agree with the dislike part, because I want my ORM to permit me to express query without being aware of the details. So I tend to consider EF not a real ORM but something like a SuperDataset, it is quick to use with database first approach, it permits you to create a DAL quickly, but it forces you to know the detail of the database, like a dataset. Clearly EF has really a lot of interesting feature respect to a dataset, but it still miss something to be considered an ORM.

Alk.

Tags: