Sunday, September 7, 2014

Unit Testing NHibernate Caching

I read somewher that says HashtableCacheProvider is more ok on unit testing, somehow true, have to use it for unit testing for now

cfg.Cache(x =>
            {

                // This is more stable on unit test
                x.Provider<NHibernate.Cache.HashtableCacheProvider>();       

                // I don't know why SysCacheProvider has problem on simultaneous unit testing, 
                // might be SysCacheProvider is just giving one session factory, so simultaneous test see each other caches
                // This solution doesn't work: http://stackoverflow.com/questions/700043/mstest-executing-all-my-tests-simultaneously-breaks-tests-what-to-do

                // x.Provider<NHibernate.Caches.SysCache.SysCacheProvider>();                    
                

Use entity cache when fetching an entity

The following code contrast the difference on cache re-use between getting an entity via cached query, and via cached entity


Here's the common code:

namespace TestTheSecondLevelCache
{
    public class Common
    {
        public static ISessionFactory BuildSessionFactory()
        {
            var sf = DomainMapping.Mapper.BuildSessionFactory();

            using (var session = sf.OpenStatelessSession())
            using (var tx = session.BeginTransaction())
            {
                Console.WriteLine("Stateless update");
                var p = session.Get<Person>(1);
                p.FirstName = "John";
                session.Update(p);
                tx.Commit();
            }


            return sf;
        }
    }
}

Query caching:
[TestMethod]
public void Test_Query_Caching_Compare_This_To_Entity_Caching()
{

    var sf = Common.BuildSessionFactory();
    
    Action query = delegate
    {

        using (var session = sf.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            Console.WriteLine("Query 1");
            var person = session.Query<Person>().Where(x => x.PersonId == 1).Cacheable().Single();
        }

        using (var session = sf.OpenSession())
        using (var tx = session.BeginTransaction())
        {

            Console.WriteLine("Query 2");
            var person = session.Query<Person>().Where(x => x.PersonId == 2).Cacheable().Single();
        }


        using (var session = sf.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            Console.WriteLine("Query 3");
            var person = session.Query<Person>().Where(x => x.PersonId == 1).Cacheable().Single();
        }

        using (var session = sf.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            Console.WriteLine("Query 4");
            var person = session.Query<Person>().Where(x => x.PersonId == 2).Cacheable().Single();
        }

    };


    query();

    using (var session = sf.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        Console.WriteLine("Update");
        var p = session.Get<Person>(1);
        p.FirstName = "ZX-" + p.FirstName;
        session.Save(p);
        tx.Commit();
    }


    query();


    using (var session = sf.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        Console.WriteLine("Assert");
        var p = session.Get<Person>(1);
        Assert.AreEqual("ZX-John", p.FirstName);
    }


}


Output:
Test Name:    Test_Query_Caching_Compare_This_To_Entity_Caching
Test Outcome:    Passed
Result StandardOutput:    
Stateless update
NHibernate: 
    SELECT
        person0_.PersonId as PersonId4_0_,
        person0_.FirstName as FirstName4_0_,
        person0_.LastName as LastName4_0_ 
    FROM
        Person person0_ 
    WHERE
        person0_.PersonId=@p0;
    @p0 = 1 [Type: Int32 (0)]
Query 1
NHibernate: 
    select
        person0_.PersonId as PersonId4_,
        person0_.FirstName as FirstName4_,
        person0_.LastName as LastName4_ 
    from
        Person person0_ 
    where
        person0_.PersonId=@p0;
    @p0 = 1 [Type: Int32 (0)]
Query 2
NHibernate: 
    select
        person0_.PersonId as PersonId4_,
        person0_.FirstName as FirstName4_,
        person0_.LastName as LastName4_ 
    from
        Person person0_ 
    where
        person0_.PersonId=@p0;
    @p0 = 2 [Type: Int32 (0)]
Query 3
Query 4
Update
Query 1
NHibernate: 
    select
        person0_.PersonId as PersonId4_,
        person0_.FirstName as FirstName4_,
        person0_.LastName as LastName4_ 
    from
        Person person0_ 
    where
        person0_.PersonId=@p0;
    @p0 = 1 [Type: Int32 (0)]
Query 2
NHibernate: 
    select
        person0_.PersonId as PersonId4_,
        person0_.FirstName as FirstName4_,
        person0_.LastName as LastName4_ 
    from
        Person person0_ 
    where
        person0_.PersonId=@p0;
    @p0 = 2 [Type: Int32 (0)]
Query 3
Query 4
Assert

As you can see, the query cache always gets evicted when there's a change on any of the row. Hence, even we didn't update person #2, all cached query on person gets evicted, resulting to re-fetching them from database. Not efficient


Contrast that query caching with entity caching, notice the use of Get:
[TestMethod]
public void Test_Entity_Caching_Compare_This_To_Query_Caching()
{

    var sf = Common.BuildSessionFactory();

    Action query = delegate
    {

        using (var session = sf.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            Console.WriteLine("Query 1");
            var person = session.Get<Person>(1);
        }

        using (var session = sf.OpenSession())
        using (var tx = session.BeginTransaction())
        {

            Console.WriteLine("Query 2");
            var person = session.Get<Person>(2);
        }


        using (var session = sf.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            Console.WriteLine("Query 3");
            var person = session.Get<Person>(1);
        }

        using (var session = sf.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            Console.WriteLine("Query 4");
            var person = session.Get<Person>(2);
        }

    };


    query();

    using (var session = sf.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        Console.WriteLine("Update");
        var p = session.Get<Person>(1);
        p.FirstName = "ZX-" + p.FirstName;
        session.Save(p);
        tx.Commit();
    }


    query();


    using (var session = sf.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        Console.WriteLine("Assert");
        var p = session.Get<Person>(1);
        Assert.AreEqual("ZX-John", p.FirstName);
    }





}

Output:
Test Name:    Test_Entity_Caching_Compare_This_To_Query_Caching
Test Outcome:    Passed
Result StandardOutput:    
Stateless update
NHibernate: 
    SELECT
        person0_.PersonId as PersonId4_0_,
        person0_.FirstName as FirstName4_0_,
        person0_.LastName as LastName4_0_ 
    FROM
        Person person0_ 
    WHERE
        person0_.PersonId=@p0;
    @p0 = 1 [Type: Int32 (0)]
Query 1
NHibernate: 
    SELECT
        person0_.PersonId as PersonId4_0_,
        person0_.FirstName as FirstName4_0_,
        person0_.LastName as LastName4_0_ 
    FROM
        Person person0_ 
    WHERE
        person0_.PersonId=@p0;
    @p0 = 1 [Type: Int32 (0)]
Query 2
NHibernate: 
    SELECT
        person0_.PersonId as PersonId4_0_,
        person0_.FirstName as FirstName4_0_,
        person0_.LastName as LastName4_0_ 
    FROM
        Person person0_ 
    WHERE
        person0_.PersonId=@p0;
    @p0 = 2 [Type: Int32 (0)]
Query 3
Query 4
Update
Query 1
Query 2
Query 3
Query 4
Assert

It's more efficient, both entity 1 and 2 are not re-fetched from DB even there's a change on entity



Happy Coding!

Saturday, September 6, 2014

Unlimited undo even you close your Visual Studio

Install this autogit then automark


How it works, autogit automatically makes a git of your changes everytime you save your code. To view your changes, use automark, you can view your changes in browser, or you can view it in markdown format

NHibernate Second-Level Cache XOR Fetch

TL;DR

Don't use Fetch on entities that are already in the second level cache


Simulation:


If an entity(e.g., Country) needed from the main query (e.g., Order) are all already in second level cache(e.g., caused by displaying them in dropdowns, or pre-loaded at startup) and it infrequently changes, don't use Fetch on that entity:

var list = session.Query<Order>() 
           .Fetch(x => x.Country) // Remove this 
           .OrderBy(x => x.Country.CountryName); 

foreach (var item in list) 
{                      
    Console.WriteLine("Order Id: {0}\nOrder Date: {1}\nCountry: {2}", item.OrderId, item.OrderDate, item.Country.CountryName);                  
}

This is the query when there's a Fetch, inefficient:

SELECT  
     o.OrderId,  
     o.OrderDate,      
     c.CountryId, 
     o.Comment, 
     c.Country, 
     c.CountryName, 
     c.Population 
FROM  
    [Order] o 
LEFT JOIN 
    Country c 
        ON o.CountryId = p 
ORDER BY 
    c.CountryName 


Whereas if you remove Fetch..
var list = session.Query<Order>() 
           .OrderBy(x => x.Country.CountryName); 

foreach (var item in list) 
{                      
    Console.WriteLine("Order Id: {0}\nOrder Date: {1}\nCountry: {2}", item.OrderId, item.OrderDate, item.Country.CountryName);                  
} 
..it's less taxing for the database. Don't worry, you can still display the CountryName above, that's how awesome NHibernate second level caching is. Here's the resulting query:

SELECT  
     o.OrderId,  
     o.OrderDate,      
     c.CountryId, 
     o.Comment 
FROM  
     [Order] o 
LEFT JOIN 
     Country c 
         ON o.CountryId = c.CountryId
ORDER BY 
     c.CountryName 



Now, if you want to minimize the database load further, say you don't want to include the Order's comment, you can just select all the fields you need:

var list = session.Query<Order>().OrderBy(x => x.Country.CountryName).Select(x => new { x.OrderId, x.OrderDate, x.Country.CountryName });  


Though the above looks correct, the Order's comment is removed, the query bypassed the use of second level cache, and the resulting query is less efficient, int(CountryId) is more efficient than nvarchar(CountryName):


SELECT  
    o.OrderId,  
    o.OrderDate,          
    c.CountryName 
FROM  
    [Order] o 
LEFT JOIN 
    Country c 
        ON o.CountryId = c.CountryId
ORDER BY 
    c.CountryName 

And if you need the population, you need to add another column in Linq's Select, resulting to less efficient query:

SELECT  
     o.OrderId,  
     o.OrderDate,      
     c.CountryName, 
     c.Population 
FROM  
    [Order] o 
LEFT JOIN 
    Country c 
        ON o.CountryId = c.CountryId
ORDER BY 
    c.CountryName 


Here's the proper way to prevent over-selecting and at the same time maximizing the use of second-level cache:
var list = session.Query<Order>() 
           .OrderBy(x => x.Country.CountryName) 
           .Select(x => new { x.OrderId, x.OrderDate, Country = session.Get<Country>(x.Country.CountryId) });  
 
foreach (var item in list) 
{                      
    Console.WriteLine("Order Id: {0}\nOrder Date: {1}\nCountry: {2}\nPopulation: {3}", item.OrderId, item.OrderDate, item.Country.CountryName, item.Country.Population);                  
} 


The resulting query is very efficient, no over-select(e.g., Comment is not included) on main entity(Order), and the referenced entity's key is the only column included in the SELECT:

SELECT  
    o.OrderId,  
    o.OrderDate,      
    c.CountryId 
FROM  
    [Order] o 
LEFT JOIN 
    Country c 
        ON o.CountryId = c.CountryId
ORDER BY 
    c.CountryName 


Sample code: https://github.com/MichaelBuen/LocalizationWithFallbacksAndCaching

Happy Coding!

Thursday, September 4, 2014

There's one specific culture whose parent culture is not neutral

using System;
using System.Globalization;
using System.Linq;

public class SamplesCultureInfo
{

   public static void Main()
   {

      // Prints the header.
    Console.WriteLine("  {0} {1, 80}","SPECIFIC CULTURE", "PARENT CULTURE");
    
    
    Action<CultureTypes, Func<CultureInfo, bool>> display = (c, exp) => 
    {
        foreach (CultureInfo ci in CultureInfo.GetCultures(c).Where(exp).OrderBy(x => x.TwoLetterISOLanguageName))
        {
            Console.Write("0x{0} {1} {2, -70}", ci.LCID.ToString("X4"), ci.Name, ci.EnglishName);
            Console.WriteLine("0x{0} {1} {2} : {3}", ci.Parent.LCID.ToString("X4"), ci.Parent.Name, ci.Parent.EnglishName, ci.Parent.IsNeutralCulture);     
        }
    };
    
    Action<CultureTypes> displayAll = c => display(c, x => true);
    
    // displayAll(CultureTypes.SpecificCultures);
    // displayAll(CultureTypes.NeutralCultures);
    
    //// Only one specific culture which parent culture is not neutral:
    // CultureInfo.GetCultures(CultureTypes.SpecificCultures).All(x => x.Parent.IsNeutralCulture).Dump();        
    // CultureInfo.GetCultures(CultureTypes.SpecificCultures).Count(x => !x.Parent.IsNeutralCulture).Dump();
    //// This is that culture:        
    display(CultureTypes.SpecificCultures, x => !x.Parent.IsNeutralCulture);
    
    
        
    
    // CultureInfo.GetCultures(CultureTypes.NeutralCultures).Count().Dump();

   }

}

Live codes:
https://dotnetfiddle.net/PCq4vR
https://dotnetfiddle.net/17Bkny

Output:
SPECIFIC CULTURE                                                                    PARENT CULTURE
0x0803 ca-ES-valencia Valencian (Spain)                                             0x0403 ca-ES Catalan (Catalan) : False


Interesting: http://stackoverflow.com/questions/8354352/how-can-i-get-the-region-and-culture-info-for-bahamas