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>();
"Simplicity can't be bought later, it must be earned from the start" -- DB
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
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:
Query caching:
Output:
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:
Output:
It's more efficient, both entity 1 and 2 are not re-fetched from DB even there's a change on entity
Happy Coding!
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
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:
This is the query when there's a Fetch, inefficient:
Whereas if you remove Fetch..
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:
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):
And if you need the population, you need to add another column in Linq's Select, resulting to less efficient query:
Here's the proper way to prevent over-selecting and at the same time maximizing the use of second-level cache:
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:
Sample code: https://github.com/MichaelBuen/LocalizationWithFallbacksAndCaching
Happy Coding!
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
Subscribe to:
Posts (Atom)