Monthly Archives: September 2012

Unit Testing classes which use ISession

One of the original principles behind MicroLite was that it should be easy to write tests for code which uses it. Therefore, a lot of thought has gone into the architecture of the API to make sure that it is well abstracted.

Lets take a simple class which processes an order for a customer:

public class OrderProcessor
{
    private ISession session;

    public OrderProcessor(ISession session)
    {
        this.session = session;
    }

    public void ProcessOrder(int orderId, int customerId)
    {
        // Use an include for the customer so we don't do 2 database calls.
        var includeCustomer = this.session.Include.Single<Customer>(customerId);
        var order = this.session.Single<Order>(orderId);
        var customer = includeCustomer.Value;

        // Process the order.
        // ...

        order.Processed = DateTime.Now;

        this.session.Update(order);
    }
}

Note: Although transactions have not been used in this example for brevity, they should be used in all production code!

And an example unit test (using NUnit and Moq):

[Test]
public void ProcessOrderUpdatesProcessedDateAndSavesOrder()
{
    // Use the Moq framework to create an ISession which we can configure to behave in a specific way.
    var mockSession = new Mock<ISession>();

    // Set up the customer id & a customer instance.
    var customerId = 1345;
    var customer = new Customer();

    // Set up the mock session to return the customer.
    // By using the helper class in MicroLite.Testing, we save having to create a pretend implementation of IInclude<T>.
    mockSession
        .Setup(s => s.Include.Single<Customer>(customerId))
        .Returns(Include.Single(customer));

    // Set up the order id & order instance.
    var orderId = 73578;
    var order = new Order();

    // Since we are returning the order via the standard interface (not as an include) we don't need to do anything special with it.
    mockSession.Setup(s => s.Single<Order>(orderId)).Returns(order);

    // Set up the update method so we can check it was called.
    mockSession.Setup(s => s.Update(order));

    // Create an order processor, supplying it with the mocked ISession implementation with the behaviour we set up above.
    var orderProcessor = new OrderProcessor(mockSession.Object);

    // Call the method that we actually want to test.
    orderProcessor.ProcessOrder(orderId, customerId);

    // Check that all the methods on the mock session which we were expecting to be called were called.
    mockSession.VerifyAll();

    // Lastly, check that the processed property has been set.
    Assert.GreaterOrEqual(order.Processed, DateTime.Now.AddSeconds(-1));
}

Hopefully this gives you an idea of how you can architect your code which uses MicroLite to take advantage of the testability of the framework.

This article was written for version 2.0.0 of the MicroLite framework and version 1.0.0 of the MicroLite.Testing package.

MicroLite.Testing 1.0 released

The new Includes feature in MicroLite 2.0 has made it easier to reduce your data access footprint by batching multiple database queries into a single call to load all the data you need in one go. This however has had a slight negative impact on the simplicity of testing code which uses MicroLite, this is because the include methods return interfaces rather than objects (in the way that Single, Fetch and Paged do).

Now, since interfaces are being returned, it is still possible to test the methods by creating mocks of the interface which leads to code such as this:

var mockInclude = new Mock<IInclude<Customer>>();
mockInclude.Setup(i => i.Value).Returns(customer);

mockSession
    .Setup(s => s.Include.Single<Customer>(customerId))
    .Returns(mockInclude.Object);

While this works, we can do better (imagine if you were including 3 or 4 object in the method you are testing!).

Therefore, a new project has been released – MicroLite.Testing. The initial release has some classes to make testing includes easier.

If we install the MicroLite.Testing package and add a using MicroLite.Testing; to our test class, we no longer have to mock the include interfaces and can do this instead:

mockSession
    .Setup(s => s.Include.Single<Customer>(customerId))
    .Returns(Include.Single(customer));

Much better 🙂

There are 4 methods on the MicroLite.Testing.Include class:

  • Many<T>() – This method will return an Include many which contains no values to simulate no results for an include.
  • Many<T>(IList<T> values) – This method will return an Include many which contains the specified values to simulate results being returned.
  • Single<T>() – This method will return an Include with the default value of T (null for a reference type, 0 for an integer etc) to simulate no results for the include.
  • Single<T>(T value) – This method will return an Include with the specified value to simulate a single result being included.

This article was written for version 2.0.0 of the MicroLite framework and version 1.0.0 of the MicroLite.Testing package.

MicroLite 2.0 Dynamic Projections

The new dynamic projections in MicroLite make it easier to show ad-hoc data. Prior to version of MicroLite 2.0, the main way to project data was to create a view which you mapped a class to.

In MicroLite 2.0, as long as you are using the .net 4.0 build, you can simplify things by using the ISession.Advanced.Projection method. This method will accept an SqlQuery and return an IList<dynamic>. Each dynamic in the list will correspond to a single row in the results and each property on the object will map to a column in the results.

Here are a couple of examples to give you an idea of how you could use this new feature.

If we only want to show a couple of columns from a table, we can just read them instead of reading entire objects.

using (var session = sessionFactory.OpenSession())
{
    // Create an ad-hoc query, this could select a number of columns accross multiple tables if desired.
    var query = new SqlQuery(&amp;quot;SELECT Name, DoB FROM Customers&amp;quot;);

    // The results of the projection will be an IList&amp;lt;dynamic&amp;gt;
    var results = session.Advanced.Projection(query);

    foreach (var item in results)
    {
        // The property names of each dynamic result will match (including case) the column names specified in the query.
        Console.WriteLine(item.Name);
        Console.WriteLine(item.DoB);
    }
}

If we wanted a summary which included a calculated value, we could do something like this:

Note that in order for this to work properly, it is essential to alias the calculated column in order for it to be used as a property value.

using (var session = sessionFactory.OpenSession())
{
    var query = new SqlQuery(@&amp;quot;SELECT Customer.Name AS CustomerName, SUM(Invoices.InvoiceTotal) AS InvoiceTotal
        FROM Customers
        INNER JOIN Invoices ON Invoices.CustomerID = Customers.CustomerID
        GROUP BY Customers.Name
        ORDER BY InvoiceTotal DESC&amp;quot;);

    var results = session.Advanced.Projection(query);

    foreach (var item in results)
    {
        // The property names of each dynamic result will match the column names specified in the query.
        Console.WriteLine(item.CustomerName);
        Console.WriteLine(item.InvoiceTotal);
    }
}

It is important to note that the property names are case sensitive when matched to column names.

This article was written for version 2.0.0 of the MicroLite framework.

MicroLite 2.0 Includes

As we covered in the previous post, MicroLite now supports including additional results in a single query.

The include methods are all exposed via the ISession.Include property and enable following:

  • Many<T>(SqlQuery)
  • Scalar<T>(SqlQuery)
  • Single<T>(object identifier)
  • Single<T>(SqlQuery)

You can call include for multiple things, they will all be loaded in a single database call once either ISession.Single, ISession.Fetch or ISession.Paged is called.

The include methods return a token which can be inspected after the all the results have been loaded.

Many

The Many method is the equivalent of the Fetch method, it should be used to retrieve associated records (e.g. the Invoices for a Customer, or Order Items for an Order).

using (var session = sessionFactory.OpenSession())
{
    // Query to fetch the invoices for the customer.
    var invoicesQuery = new SqlQuery("SELECT * FROM Invoices WHERE CustomerId = @p0", 1792);
        
    // Tell the session to include the invoices.
    var invoices = session.Include.Many<Invoice>(invoicesQuery);
        
    // At this point, invoices will point to an IIncludeMany<Invoice> which will have no values.
        
    // Load the customer.
    var customer = session.Single<Customer>(1792);
        
    // We can now acces the invoices for the customer
    foreach (var invoice in invoices.Values)
    {
        // ...
    }
}

Scalar

The Scalar method allows a single value to be included such as the count of invoices for a customer.

using (var session = sessionFactory.OpenSession())
{
    // Query to count the invoices for the customer.
    var invoicesCountQuery = new SqlQuery("SELECT COUNT(InvoiceId) FROM Invoices WHERE CustomerId = @p0", 1792);

    // Tell the session to include the invoices count.
    var invoicesCount = session.Include.Scalar<int>(invoicesQuery);

    // At this point, invoices will point to an IInclude<int> which will have it's default value of 0.

    // Load the customer.
    var customer = session.Single<Customer>(1792);

    // We can now acces the invoices count for the customer
    Console.WriteLine(invoicesCount.Value.ToString());
}

Single

The Single method has 2 overloads and allows a single instance to be included either by it’s identifier or an SqlQuery.

using (var session = sessionFactory.OpenSession())
{
    // Tell the session to include the customer.
    var includeCustomer = session.Include.Single<Customer>(3264);
        
    // At this point, includeCustomer will point to an IInclude<Customer> which will have no value.
        
    // Query to fetch the invoices for the customer.
    var invoicesQuery = new SqlQuery("SELECT * FROM Invoices WHERE CustomerId = @p0", 3264);
        
    // Load the invoices.
    var invoices = session.Fetch<Invoice>(query);
        
    // We can now acces the customer
    Console.WriteLine(includeCustomer.Value.Name);
}

This article was written for version 2.0.0 of the MicroLite framework.

MicroLite 2.0 Enhancements

MicroLite 2.0 has been released with a number of enhancements:

  • Includes
  • Dynamic Projections
  • Updates to SqlBuilder
  • Updates to PagedResult

Includes

Includes allow additional results to be returned with a single database connection.

Example: Get a Customer record and all his associated Invoice records.

In MicroLite 1.2.2 and earlier, you would have to call Single and Fetch which would result in 2 separate queries to the database.

var customer = session.Single<Customer>(12345);

var invoices = session.Fetch<Invoice>(new SqlQuery("SELECT * FROM Invoices WHERE CustomerId = @p0", 12345));

In MicroLite 2.0 the queries can be combined:

// The values to include are all specified first via the ISession.Include property.
var includeInvoices = session.Include.Many<Invoice>(new SqlQuery("SELECT * FROM Invoices WHERE CustomerId = @p0", 12345));

// Calling ISession.Single, ISession.Fetch or ISession.Paged will result in all queries being executed.
var customer = session.Single<Customer>(12345);

// The returned invoices are available under the Values property of the include.
var invoices = includeInvoices.Values;

Dynamic Projections

Sometimes we simply want to show some data to a user which may consist of a number of columns across some joined tables.

In MicroLite 1.2.2 and earlier, the easiest way to do this was to create a view which projected the relevant columns and create a class which mapped to the view. (If you are using the .net 3.5 build of MicroLite 2.0 this is still the case as dynamic types were not introduced until .net 4.0).

As of MicroLite 2.0 in the .net 4.0 build, a new method has been added to the ISession.Advanced options to perform a dynamic projection.

var query = new SqlQuery(@"SELECT Customer.Name AS CustomerName, SUM(Invoices.InvoiceTotal) AS InvoiceTotal
FROM Customers
INNER JOIN Invoices ON Invoices.CustomerID = Customers.CustomerID
GROUP BY Customers.Name
ORDER BY InvoiceTotal DESC");

var results = session.Advanced.Projection(query);

foreach (var item in results)
{
    // The property names of each dynamic result will match (including case) the column names specified in the query.
    Console.WriteLine(item.CustomerName);
    Console.WriteLine(item.InvoiceTotal);
}

Updates to SqlBuilder

There are a number of new methods on SqlBuilder to support functions and grouping, there is also a small (although breaking) change too.

SqlBuilder.SelectFrom(typeof(Customer))..

is now

SqlBuilder.Select("*").From(typeof(Customer))...

The following SQL functions are supported:

  • Average
  • Count
  • Min
  • Max
  • Sum

The functions can be used as follows-

var sqlQuery = SqlBuilder
     .Select()
     .Average("Total")
     .From(typeof(Invoice))
     .Where("CustomerId = @p0", 1022)
     .ToSqlQuery();

var total = session.Advanced.ExecuteScalar<decimal>(query);

Updates to PagedResult

The PagedResult class how has 3 extra properties, MoreResultsAvailable, TotalResults and TotalPages.

The total results are calculated by re-writing the original query to perform a SELECT COUNT(*) in addition to the actual query (this is actually done as an Include so there is only one connection and one command used).

The total pages are calculated as total results divided by the requested results per page.

The next few blog posts will go into these in further detail.

There is also a compiled help which can be downloaded from the MicroLite Downloads on the GitHub site.

Registering MicroLite with Ninject

If you are using MicroLite in an application and using Ninject as your IOC container, it is extremely easy to configure Ninject to resolve an ISession as a dependency.

In your application startup, where you are registering types in the kernel, simply add the 2 bindings below:

var sessionFactory = Configure
    .Fluently()
    .ForConnection("Northwind")
    .CreateSessionFactory();

var kernel = new StandardKernel();
kernel.Bind<ISessionFactory>().ToConstant(sessionFactory);
kernel.Bind<ISession>().ToMethod(ctx => ctx.Kernel.Get<ISessionFactory>().OpenSession());

If you use the default binding scope of transient, your class should take ownership of the ISession and dispose of it when the class has finished using it.

If you add the Ninject Named Scope extension, you can bind the ISession with .InParentScope(). In this case, Ninject will dispose of the ISession when the parent object is garbage collected so you don’t have to dispose of it yourself.

It should be just as simple to register MicroLite with any other IOC container (Autofac, Unity, Windsor etc).

This article was written for version 1.2.2 of the MicroLite framework.

Using MicroLite with ASP.NET MVC

It is very easy to use MicroLite within ASP.NET MVC out of the box, however an extension package has been created to make it even easier.

To use it, firstly install the NuGet package Install-Package MicroLite.Extensions.Mvc

Then, in your application startup simply register the extension before creating the session factories.

protected void Application_Start()
{
    ... // common mvc startup, register routes etc.

    // Load the extension for mvc
    Configure
       .Extensions() // If you are also using a logging extension, that should be loaded first.
       .WithMvc();

    // Create the session factory...
    Configure
       .Fluently()
       ...
}

The extension includes a controller MicroLiteController which defines an ISession proeprty called Session, since the base controller class already defines a Session property, this has been redirected to a property called HttpSession. Inherit your controllers from MicroLiteController.

public class CustomerController : MicroLiteController
{
    ...
}

The extension also includes an ActionFilterAttribute called MicroLiteSessionAttribute. This can be applied to specific methods on your controller if only certain actions require an ISession or to the controller class if all methods require an ISession.

[HttpPost]
[MicroLiteSession]
public ActionResult Edit(int id, FormCollection collection)
{
    ...
}

or

[MicroLiteSession]
public class CustomerController : MicroLiteController

The attribute will ensure that a new ISession is created and assigned to the Session property of the controller before a method is called. It will also begin a transaction. Once the method has completed, it will either commit the transaction or if an exception was thrown within the method, it will roll the transaction back.

If you are using the automatic transaction management, it is important not to swallow exceptions inside your method, otherwise the transaction will not be rolled back. You should use error handler attributes to deal with exceptions which will allow the Mvc extension to properly manage the transaction.

If you want to opt out of the auto transaction management, simply initialize the attribute and set the property to false.

[MicroLiteSession(AutoManageTransaction = false)]

You should then begin and commit/rollback a transaction within the method yourself.

This article was written for version 1.2.2 of the MicroLite framework.