8

In some unit/integration tests of the code we wish to check that correct usage of the second level cache is being employed by our code.

Based on the code presented by Ayende here:

http://ayende.com/Blog/archive/2006/09/07/MeasuringNHibernatesQueriesPerPage.aspx

I wrote a simple class for doing just that:

public class QueryCounter : IDisposable
{
    CountToContextItemsAppender _appender;

    public int QueryCount
    {
      get { return _appender.Count; }
    }

    public void Dispose()
    {
      var logger = (Logger) LogManager.GetLogger("NHibernate.SQL").Logger;
      logger.RemoveAppender(_appender);
    }

    public static QueryCounter Start()
    {
      var logger = (Logger) LogManager.GetLogger("NHibernate.SQL").Logger;

      lock (logger)
      {
        foreach (IAppender existingAppender in logger.Appenders)
        {
          if (existingAppender is CountToContextItemsAppender)
          {
            var countAppender = (CountToContextItemsAppender) existingAppender;

            countAppender.Reset();

            return new QueryCounter {_appender = (CountToContextItemsAppender) existingAppender};
          }
        }

        var newAppender = new CountToContextItemsAppender();
        logger.AddAppender(newAppender);
        logger.Level = Level.Debug;
        logger.Additivity = false;

        return new QueryCounter {_appender = newAppender};
      }
    }

    public class CountToContextItemsAppender : IAppender
    {
      int _count;

      public int Count
      {
        get { return _count; }
      }

      public void Close()
      {
      }

      public void DoAppend(LoggingEvent loggingEvent)
      {
        if (string.Empty.Equals(loggingEvent.MessageObject)) return;
        _count++;
      }

      public string Name { get; set; }

      public void Reset()
      {
        _count = 0;
      }
    }
}

With intended usage:

using (var counter = QueryCounter.Start())
{
  // ... do something 
  Assert.Equal(1, counter.QueryCount); // check the query count matches our expectations
}

But it always returns 0 for Query count. No sql statements are being logged.

However if I make use of Nhibernate Profiler and invoke this in my test case:

NHibernateProfiler.Intialize()

Where NHProf uses a similar approach to capture logging output from NHibernate for analysis via log4net etc. then my QueryCounter starts working.

It looks like I'm missing something in my code to get log4net configured correctly for logging nhibernate sql ... does anyone have any pointers on what else I need to do to get sql logging output from Nhibernate?

Additional info:

Logging.config:

<log4net>

  <appender name="trace" type="log4net.Appender.TraceAppender, log4net">
    <layout type="log4net.Layout.PatternLayout,log4net">
      <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &amp;lt;%P{user}&amp;gt; - %m%n" />
    </layout>
  </appender>

  <appender name="console" type="log4net.Appender.ConsoleAppender, log4net">
    <layout type="log4net.Layout.PatternLayout,log4net">
      <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &amp;lt;%P{user}&amp;gt; - %m%n" />
    </layout>
  </appender>

  <appender name="debug" type="log4net.Appender.DebugAppender, log4net">
    <layout type="log4net.Layout.PatternLayout,log4net">
      <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &amp;lt;%P{user}&amp;gt; - %m%n" />
    </layout>
  </appender>

  <logger name="NHibernate.SQL" additivity="false">
    <level value="DEBUG" />
    <appender-ref ref="ConsoleAppender" />
  </logger>

  <root>
    <priority value="DEBUG" />
    <appender-ref ref="trace" />
    <appender-ref ref="console" />
    <appender-ref ref="debug" />
  </root>

</log4net>

show_sql: true

Based on jfneis response, I wrote a far simpler class which just uses NHibernate's factory statistics:

public class QueryCounter
{
  long _startCount;

  QueryCounter()
  {
  }

  public int QueryCount
  {
    get { return (int) (UnitOfWork.CurrentSession.SessionFactory.Statistics.QueryExecutionCount - _startCount); }
  }

  public static QueryCounter Start()
  {
    return new QueryCounter {_startCount = UnitOfWork.CurrentSession.SessionFactory.Statistics.QueryExecutionCount};
  }
}

Which works just fine once statistics is enabled.

Bittercoder
  • 11,753
  • 10
  • 58
  • 76
  • Bittercoder, I'm out of my dev machine right now so I can't post code, but have you tried to use Statistics to do that? I've also faced this kind of problem to test L2 and Statistics where enough to my scenario. Back to the office (12h from now) I'll post (if still needed) a complete answer. Hope this helps until there. – jfneis Apr 23 '10 at 01:19
  • In your nhibernate configuration have you set show_sql to true? Please can you also post your log4net.config? – mhanney Apr 22 '10 at 23:02
  • Yes, tried it with show_sql set to true and false. Though I thought show_sql didn't use log4net and just dumped the sql statements to stdout? I've now included my log4net configuration in the initial question. The fact that NHibernateProfiler.Initialize() then causes my class to work suggests to me that It's probably some programmatic log4net configuration I'm missing. – Bittercoder Apr 22 '10 at 23:41
  • Are you calling log4net.Config.XmlConfigurator.Configure(); when your app starts up? – mhanney Apr 23 '10 at 03:20
  • should be . I'd be surprised if that was it, just thought I'd mention it. – mhanney Apr 23 '10 at 03:22

1 Answers1

13

There's another (simpler, IMO) way to assert if cache is being hit or if queries are being executed: using Statistics.

First of all, you have to enable statistics in your NH config file:

 <property name="generate_statistics">true</property>

After that, you can ask your session factory whenever you want how things are going. You've talked about L2 cache testing, so you could have something like that:

        // act
        MappedEntity retrievedEntity = session.FindById(entity.Id);
        long preCacheCount = sessionFactory.Statistics.SecondLevelCacheHitCount;
        retrievedEntity = session.FindById(entity.Id);
        long postCacheCount = sessionFactory.Statistics.SecondLevelCacheHitCount;
        // assert
        Assert.AreEqual(preCacheCount + 1, postCacheCount);

But, if what you really want is the query count, there are plenty other options in the Statistics interface:

        sessionFactory.Statistics.QueryExecutionCount;
        sessionFactory.Statistics.TransactionCount;

Well, that's it. Hope this helps you as helped me.

Regards,

Filipe

jfneis
  • 2,139
  • 18
  • 31
  • I gave statistics a look, and didn't find any query stats - but I was look at ISessionStatistics, ISessionFactory.Statistics (IStatistics). Will give it a go :) – Bittercoder Apr 23 '10 at 23:47
  • Implemented simple class using statistics - works great - thanks for the help José! – Bittercoder Apr 23 '10 at 23:58
  • 3
    QueryExecutionCount doesn't doesn't actually show me the number of SELECT statements executed? I've got an N+1 situation I'm trying to improve and the single session List call is creating the first select and then 27 more. But QueryExecutionCount is still 1. I think PrepareStatementCount is what you want. – jrwren Sep 16 '10 at 14:10