Try-Catch-FAIL

Failure is inevitable.

Castle ActiveRecord: Quite Possibly the Greatest Thing Ever

clock October 20, 2008 09:22 by author Matt

Well, maybe not, but it has quickly become my favorite tool for writing .NET applications.  I've been using it to write the persistence layer for a brand-new enterprise application we're building, and so far, it's elegantly supported every single thing I've wanted to do.  LINQ?  Got it.  Part-of relationships?  Easy.  Many-to-many?  No sweat.  Multiple data sources?  Simple.  Validation?  Available

Some may argue that using something like ActiveRecord is bad because it requires you to use inheritance, and while that may be true some of the time, I don't think it is generally true.  To me, architectural purity is only important if it makes things easier to build and maintain.  If it doesn't, what's the point?  I would gladly take on a bit of "impurity" in order to gain tremendous flexibility.  No more shall I fear a change in requirements that will lead to database changes!

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Easy testing via ActiveRecord and SQLite

clock August 20, 2008 07:50 by author Matt

As I've mentioned, I'm a big fan of ActiveRecord.  I like having all of my data-access related code stored in exactly one place.  There's no separate mapping file to maintain, and if I'm really lazy, I can even let ActiveRecord generate the schema for me (yeah, that doesn't really work once you have to worry about migrating databases between schema versions, but there's probably a way around that). 

Testing code that depends on my ActiveRecord object model can be complicated.  The tests require that the underlying database be in a known, consistent state, and that means that I have to use test setup/tear down methods to initialize the database with the schema and/or wipe out data created by other tests.  Worse, it means I have to actually have a database accessible for use in the tests!  Since this behavior tends to be the same across my ActiveRecord-dependent test fixtures, I have created a base test fixture and a simple utility class that keeps my real NUnit test fixtures very simple and free of ActiveRecord initialize and cleanup code, and it uses an in-memory database so there's no dependency on an external database.  First, the utility class:

   1: /// <summary>
   2: /// Contains various utilty methods and properties
   3: /// to aide in the configuration and management
   4: /// of ActiveRecord.  All objects that are to be persisted
   5: /// by ActiveRecord are defined here.
   6: /// </summary>
   7: public static class ActiveRecordHelper
   8: {
   9:     #region Private Static Fields
  10:  
  11:     /// <summary>
  12:     /// This field holds the types that are to be persisted
  13:     /// by ActiveRecord.  These should be listed bottom-up in order
  14:     /// by dependency, so use C, B, A if C depends on B, and B depends on A.
  15:     /// </summary>
  16:     static readonly Type[] mModelTypes = new Type[]
  17:                                              {
  18:                                                  typeof(Widget)
  19:                                              };
  20:  
  21:     #endregion
  22:  
  23:     #region Public Properties
  24:  
  25:     /// <summary>
  26:     /// Gets all the models that are available for persistence
  27:     /// in ActiveRecord.
  28:     /// </summary>
  29:     public static Type[] ModelTypes
  30:     {
  31:         get
  32:         {
  33:             return mModelTypes;
  34:         }
  35:     }
  36:  
  37:     #endregion
  38: }

Note how the types used by ActiveRecord are listed in order of dependency.  This is important because the test fixture will clean up the database by running DeleteAll commands on the types in that order.

Next is our base test fixture class:

   1: /// <summary>
   2: /// Provides a base class that database-dependent unit tests can utilize.  It 
   3: /// handles managing the ActiveRecord data layer setup/init, clean up, etc.
   4: /// </summary>
   5: public abstract class DatabaseDependentTestFixture
   6: {
   7:     #region Protected Methods
   8:  
   9:     /// <summary>
  10:     /// Deletes all data in the database.
  11:     /// </summary>
  12:     protected void DeleteAllData()
  13:     {
  14:         Type[] types = ActiveRecordHelper.ModelTypes;
  15:  
  16:         foreach (Type t in types)
  17:         {
  18:             if (typeof(ActiveRecordBase).IsAssignableFrom(t) &&
  19:                 t.GetMethod("DeleteAll", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy, null, new Type[0], null) != null)
  20:             {
  21:                 t.InvokeMember("DeleteAll", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.InvokeMethod, null, null, null);
  22:             }
  23:         }
  24:     }
  25:  
  26:     /// <summary>
  27:     /// Initializes ActiveRecord to work with an in-memory temporary database
  28:     /// for the RBCMS objects.
  29:     /// </summary>
  30:     protected void SetupActiveRecord()
  31:     {
  32:         Dictionary<string, string> settings = new Dictionary<string, string>();
  33:         settings.Add("connection.driver_class", "NHibernate.Driver.SQLite20Driver");
  34:         settings.Add("dialect", "NHibernate.Dialect.SQLiteDialect");
  35:         settings.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
  36:         settings.Add("connection.connection_string", "Data Source=ActiveRecord.dat;Version=3;");
  37:  
  38:         InPlaceConfigurationSource config = new InPlaceConfigurationSource();
  39:         config.PluralizeTableNames = true;
  40:         config.Add(typeof(ActiveRecordBase), settings);
  41:  
  42:         if (ActiveRecordStarter.IsInitialized)
  43:         {
  44:             ActiveRecordStarter.ResetInitializationFlag();
  45:         }
  46:  
  47:         ActiveRecordStarter.Initialize(config, ActiveRecordHelper.ModelTypes);
  48:     }
  49:  
  50:     /// <summary>
  51:     /// Drops and recreates the RBCMS database schema.
  52:     /// </summary>
  53:     protected void RecreateSchema()
  54:     {
  55:         ActiveRecordStarter.CreateSchema();
  56:     }
  57:  
  58:     #endregion
  59:  
  60:     #region Public Methods
  61:  
  62:     /// <summary>
  63:     /// Initializes the data layer for first use.  
  64:     /// </summary>
  65:     [TestFixtureSetUp]
  66:     public virtual void TestFixtureSetup()
  67:     {
  68:         SetupActiveRecord();
  69:         RecreateSchema();
  70:     }
  71:  
  72:     /// <summary>
  73:     /// Wipes out all existing data in the database, allowing
  74:     /// unit tests to start with a blank slate each time (which
  75:     /// is what they should be doing anyway).
  76:     /// </summary>
  77:     [SetUp]
  78:     public virtual void TestSetup()
  79:     {
  80:         DeleteAllData();
  81:     }
  82:  
  83:     #endregion
  84: }

There's quite a bit going on here, so let's walk through it.  When inherited by a derived class, the TestFixtureSetup and TestSetup mehods will be called by NUnit automatically.  The first method will initialize ActiveRecord, configuring it to use a temporary SQLite database, then populating the database with the schema generated by ActiveRecord.  Using SQLite means that the database resides entirely in memory, so I don't have to worry about configuring a separate MS SQL database or something like that.  The second method is called by NUnit before each test runs, and it uses the ActiveRecordBase<T>.DeleteAll method to clean out any existing data.  Reflection is used because DeleteAll is a static, generic method that must be called on the derived type, not the base type, and there's not a way to do that without reflection unless you couple the code to your models.

The setup methods are declared as virtual, so derived test fixtures can override their behavior if needed.  Most of the time though, you can just passively take advantage of the functionality like so:

   1: /// <summary>
   2: /// Test fixture for <see cref="ActiveRecordLinqContext"/>.
   3: /// </summary>
   4: [TestFixture]
   5: public class ActiveRecordLinqContextTests : DatabaseDependentTestFixture
   6: {
   7:     /// <summary>
   8:     /// Verifies that LINQ works as expected.
   9:     /// </summary>
  10:     [Test]
  11:     public void LinqTest1()
  12:     {
  13:         //First, insert a few widgets.
  14:         for (int i = 0; i < 10; i++)
  15:         {
  16:             Widget model = new Widget {Name = "Test" + i, Description = "Test Description " + i};
  17:             model.SaveAndFlush();
  18:         }
  19:  
  20:         using (new SessionScope())
  21:         {
  22:             ActiveRecordLinqContext context = new ActiveRecordLinqContext();
  23:  
  24:             string[] names = (from cm in context.Session.Linq<CostModel>()
  25:                          select cm.Name).ToArray();
  26:  
  27:             Assert.AreEqual(10, names.Length);
  28:  
  29:             names = (from cm in context.Session.Linq<CostModel>()
  30:                      where cm.Id > 5
  31:                      select cm.Name).ToArray();
  32:  
  33:             Assert.AreEqual(5, names.Length);
  34:         }
  35:     }
  36: }
This is the test fixture from yesterday's post, but all its setup logic is now handled by the base class, freeing you up to write simple, clean tests.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Using LINQ with ActiveRecord

clock August 19, 2008 09:42 by author Matt

One of the new projects at my day job is using ActiveRecord for data access.  I'm a huge fan of ActiveRecord (and of all things Castle), but I like the fact that LINQ makes it very easy to do ad-hoc queries with a compile-time safety net.  Unfortunately, ActiveRecord does not support LINQ out of the box.  Luckily though, ActiveRecord is built on top of NHibernate, and LINQ support is available via NHibernate Contrib, at least if you aren't afraid to venture into the source.  If you follow the steps below, you can build your own LINQ provider for ActiveRecord and get the syntactical sugar of language-integrated query with the simplicity of ActiveRecord.  Note that you will need to know the basics of using Subversion in order to follow these steps (I highly recommend TortoiseSVN), and you must have NAnt installed and in working order.

  1. Checkout NHibernate.LINQ from https://nhcontrib.svn.sourceforge.net/svnroot/nhcontrib/trunk/src/NHibernate.Linq.
  2. Checkout Castle from http://svn.castleproject.org:8080/svn/castle/trunk.
  3. Copy NHibernate.dll from "NHibernate.Linq\lib" to "Castle\SharedLibs\net\2.0".  This is necessary because the latest release build of NHibernate doesn't work with NHibernate.LINQ.  You could try to build NHibernate from its trunk and copy the DLL to both Castle and NHibernate.LINQ, but since the projects occasionally end up out-of-sync, I prefer the copy-and-paste method.
  4. Build Castle using NAnt by running this command in the root of your Castle checkout: nant rebuild -D:common.testrunner.enabled=false. Do *NOT* try to build using the solution; the NAnt scripts create some of the required AssemblyInfo files, and you can't build without those.
  5. Copy the following DLLs from the Castle build output to "NHibernate.Linq\lib": Castle.Core.dll, Castle.DynamicProxy2.dll, Iesi.Collections.dll.
  6. Build NHibernate.Linq using MSBuild, Visual Studio, or whatever you want.
  7. That's it (sort-of).
  8. (Recommended) I like to copy the required DLLs to a common folder that I keep under version control with my project.  This insures that nothing gets lost since I'm lazy and really don't want to jump through these hoops more often than absolutely necessary.  If you go that route, copy the following DLLs from the various build output directories to a common folder: Castle.ActiveRecord.dll, Castle.Components.Validator.dll, Castle.Core.dll, Castle.DynamicProxy.dll, Iesi.Collections.dll, NHibernate.dll, and NHibernate.Linq.dll. 

At this point, you should now have Castle, NHibernate, and NHibernate.LINQ all synced up where they can play together.  All that remains is to create our ActiveRecord LINQ context.  So, let's begin!

  1. Create a new class library in Visual Studio.
  2. Add references to the DLL's listed in the optional step 8 above.  If you didn't copy things to a central folder, you probably will do so now. :) 
  3. Create a new class named ActiveRecordLinqContext.  Go ahead and create a corresponding test fixture class now as well (you *do* practice unit testing, right??)

A quick note: I'm using SQLite for my tests.  If you go that route, be sure you place the SQLite DLL where NHibernate can find it.  Alternatively, you can test with any other database that NHibernate supports.  A future post will detail how to use SQLite for clean and simple ActiveRecord testing.

That's it for the boring stuff, on to the glorious code!

   1: /// <summary>
   2: /// Provides a LINQ-enabled data context that works with ActiveRecord.
   3: /// </summary>
   4: public class ActiveRecordLinqContext : NHibernateContext
   5: {
   6:     #region Public Constructors
   7:  
   8:     /// <summary>
   9:     /// Creates and configures the context.
  10:     /// </summary>
  11:     public ActiveRecordLinqContext() : base(GetSession())
  12:     {
  13:     }
  14:  
  15:     #endregion
  16:  
  17:     #region Private Static Methods
  18:  
  19:     /// <summary>
  20:     /// Gets a correctly-initialized session.
  21:     /// </summary>
  22:     /// <returns></returns>
  23:     private static ISession GetSession()
  24:     {
  25:         ISessionScope scope = SessionScope.Current;
  26:  
  27:         if (scope == null)
  28:         {
  29:             throw new InvalidOperationException("You must have an active SessionScope object to use this class.");
  30:         }
  31:  
  32:         ISessionFactoryHolder holder = ActiveRecordMediator.GetSessionFactoryHolder();
  33:  
  34:         return holder.CreateSession(typeof (ActiveRecordBase));
  35:     }

That's pretty simple, but how do we use it?  Here's the test fixture and a test class:

   1: /// <summary>
   2: /// A simple widget!
   3: /// </summary>
   4: [ActiveRecord]
   5: public class Widget : ActiveRecordBase<Widget>
   6: {
   7:     /// <summary>
   8:     /// The ID.
   9:     /// </summary>
  10:     [PrimaryKey]
  11:     public int Id { get; set; }
  12:  
  13:     /// <summary>
  14:     /// Its name.
  15:     /// </summary>
  16:     [Castle.ActiveRecord.Property]
  17:     public string Name { get; set; }
  18:  
  19:     /// <summary>
  20:     /// Its description.
  21:     /// </summary>
  22:     [Castle.ActiveRecord.Property]
  23:     public string Description { get; set; }
  24: }
  25:  
  26: /// <summary>
  27: /// Test fixture for <see cref="ActiveRecordLinqContext"/>.
  28: /// </summary>
  29: /// <author>MBH</author>
  30: /// <dateAuthored>7/22/08</dateAuthored>
  31: [TestFixture]
  32: public class SimpleLinqTests
  33: {
  34:     #region Private Helpers
  35:  
  36:     /// <summary>
  37:     /// Initializes ActiveRecord to work with an in-memory temporary database
  38:     /// via SQLite.
  39:     /// </summary>
  40:     private static void SetupActiveRecord()
  41:     {
  42:         Dictionary<string, string> settings = new Dictionary<string, string>();
  43:         settings.Add("connection.driver_class", "NHibernate.Driver.SQLite20Driver");
  44:         settings.Add("dialect", "NHibernate.Dialect.SQLiteDialect");
  45:         settings.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
  46:         settings.Add("connection.connection_string", "Data Source=LinqTest.dat;Version=3;");
  47:  
  48:         InPlaceConfigurationSource config = new InPlaceConfigurationSource();
  49:         config.PluralizeTableNames = true;
  50:         config.Add(typeof(ActiveRecordBase), settings);
  51:  
  52:         if (ActiveRecordStarter.IsInitialized)
  53:         {
  54:             ActiveRecordStarter.ResetInitializationFlag();
  55:         }
  56:  
  57:         ActiveRecordStarter.Initialize(config, typeof (Widget));
  58:     }
  59:  
  60:     /// <summary>
  61:     /// Drops and recreates the database schema.
  62:     /// </summary>
  63:     private static void RecreateSchema()
  64:     {
  65:         ActiveRecordStarter.CreateSchema();
  66:     }
  67:  
  68:     #endregion
  69:  
  70:     /// <summary>
  71:     /// Initializes the data layer for first use.  
  72:     /// </summary>
  73:     [TestFixtureSetUp]
  74:     public virtual void TestFixtureSetup()
  75:     {
  76:         SetupActiveRecord();
  77:         RecreateSchema();
  78:     }
  79:  
  80:     /// <summary>