Feb 28 2011

Application-level locks with NHibernate and sp_getapplock

Category: Uncategorizedzvolkov @ 12:18 pm

Sometimes you want to ensure that multiple instances of your application cooperate nicely when using a shared resource. Maybe you want to ensure that only one user at a time can edit a sales order. Maybe you want to ensure that only very first instance of certain event is recorded in a table.

In the past I either used native SQL transactions (beware of deadlocks!), or had to implement my own locking table (easy to get wrong as complexity grows). Luckily, there is an alternative solution that is both easy to use and flexible. I’ve only learned about it today but apparently it was available since SQL Server 2005! The name is sp_getapplock.

Basically, sp_getapplock allows you to place a logical lock on virtual “resource” identified by a varchar key. It supports Shared/Exclusive semantics, timeouts, implicit or explicit lock release and more. Here’s the simplest scenario, assuming you want to implement a global equivalent of simple critical section (i.e. a lock that works across multiple instances of your application):

  1. begin tran
  2. exec sp_getapplock @resource=’my locking key’, @LockMode=Exclusive
  3. …access the protected resource here…
  4. commit tran

In this scheme the lock is implicitely released when transaction commits or rolls back. If you want your lock to live longer that typical transaction, you can increase its scope to SQL Connection level by setting @LockOwner = ‘Session’. The lock will be auto-released when SQL Connection is closed. (There’s no way to make the lock live accross multiple connections. At first this may sound like a deal-breaker, but if you think about it, this eliminates the need to worry about garbage-collecting the old locks remaining after your client application dies. As Stalin used to say: “There is no man — there is no problem”)

What I want to show today is how easy it is to use sp_getapplock from NHibernate to achieve safe alternative to ISession.BeginTransaction(IsolationLevel.Serializable). All you have to do is use ISession.CreateSQLQuery to create a wrapper around sp_getapplock:

using System;
using NHibernate;

namespace Sample
{
    public static class ApplicationLevelDatabaseLock
    {
        public static void ObtainApplicationLevelDatabaseLock(this ISession session, string resourceName, string lockMode, int timeoutMilliseconds)
        {
            int status = (int)session.CreateSQLQuery(
                    @"DECLARE @status INT;
                      exec @status = sp_getapplock @resource=:resourceName, @LockMode = :lockMode, @LockTimeout=:timeout;
                      SELECT @status as status")
               .AddScalar("status", NHibernateUtil.Int32)
               .SetParameter("resourceName", resourceName)
               .SetParameter("lockMode", lockMode)
               .SetParameter("timeout", timeoutMilliseconds)
               .UniqueResult();

            if (status < 0) throw new ApplicationException("Could not obtain application-level " + lockMode + " lock on " + resourceName);
        }
    }
}

Now you can use it like this:

using (var tran = session.BeginTransaction())
{
    session.ObtainApplicationLevelDatabaseLock("my exclusive resource 1", "Exclusive", 5000);
    // do your thing here, only one instance of your app will run this code at any given time.
}

Isn’t this nice? As one of my friends says (in a very dry tone, with almost British accent): “I’m all tingling with excitement” ;)


Feb 23 2011

The Check-in Dance

Category: Uncategorizedzvolkov @ 2:11 pm

Another canonical article called “the check-in dance”:

Basically, instead of

  1. Implement your changes
  2. Check-in
  3. Go home

Should do

  1. Implement your changes
  2. Get latest from SVN
  3. Merge any conflicts
  4. Do a recompile, fix any problems found
  5. Run all unit-tests, fix any problems found
  6. Commit the changes to SVN
  7. Stop coding until the build passes on TeamCity
  8. If TeamCity build breaks, drop everything else and fix the build

This does not apply to legacy projects, but on new projects, this is our golden standard.


Feb 16 2011

ASP.NET Razor: lessons learned

Category: Uncategorizedzvolkov @ 4:04 pm

Today I enabled compilation of .aspx and .cshtml files using MvcBuildViews parameter as described in this post.

This immediately caught a bunch of problems in my MVC project. Here’s a list of gotchas for myself to remember:

1. In Razor, implicit typing (aka the var) is only supported in full blocks. For example the following will not work:

@var i = ViewData["counter"];

while the following will work Ok:

@{ var i = ViewData["counter"]; }

2. When you use Resharper’s refactoring features to rename namespaces or classes, the changes do not propagate to Razor files. This is because Resharper’s support of Razor is very limited. They have promised to fix that in RS 6.0 but for now we have to remember to manually update them.

3. When you delete files or folders from your Visual Studio solution make sure the changes are properly check-in to the source control. Sometimes a file or folder is not visible in solution explorer but is still sitting on the disk, or even deleted from your local disk but not from the source control, naturally this messes up the aspnet_compiler.exe

4. If you want to deploy your MvcBuildViews-enabled project using the DeployOnBuild method (i.e. in one step with compilation) you have to know that the two features are really in conflict with each other. The MSDeployPublish runs first, generating the package folder, and MvcBuildViews runs second, stumbling upon the second Web.Config (the one in the package folder). One way to solve it is modify the .csproj file to make MvcBuildViews run before MSDeployPublish if DeployOnBuild is enabled or after the AfterBuild if DeployOnBuild is disabled:

<Target Name="MvcBuildViews" Condition="'$(MvcBuildViews)'=='true'">
  <AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>
<PropertyGroup>
  <MSDeployPublishDependsOn Condition="'$(DeployOnBuild)|$(DeployTarget)'=='true|MsDeployPublish'">
    MvcBuildViews;
    $(MSDeployPublishDependsOn)
  </MSDeployPublishDependsOn>
  <BuildDependsOn Condition="'$(DeployOnBuild)'!='true'">
    $(BuildDependsOn);
    MvcBuildViews
  </BuildDependsOn>
</PropertyGroup>


Feb 08 2011

Entity Framework CTP5 basics

Category: Uncategorizedzvolkov @ 2:02 pm

New concepts in CTP5:

http://blogs.msdn.com/b/efdesign/archive/2010/06/21/productivity-improvements-for-the-entity-framework.aspx

Conventions (default mapping rules):

http://blogs.msdn.com/b/efdesign/archive/2010/06/01/conventions-for-code-first.aspx

Mapping API changes:

http://blogs.msdn.com/b/adonet/archive/2010/12/10/code-first-mapping-changes-in-ctp5.aspx

http://blogs.msdn.com/b/adonet/archive/2010/12/14/ef-feature-ctp5-fluent-api-samples.aspx

Wiring DbContext and DbSets:

http://blogs.msdn.com/b/adonet/archive/2011/01/27/using-dbcontext-in-ef-feature-ctp5-part-2-connections-and-models.aspx

Annotations (model attributes):

http://blogs.msdn.com/b/efdesign/archive/2010/03/30/data-annotations-in-the-entity-framework-and-code-first.aspx

http://blogs.msdn.com/b/adonet/archive/2010/12/15/ef-feature-ctp5-validation.aspx

http://blogs.msdn.com/b/adonet/archive/2010/12/14/ef-feature-ctp5-code-first-walkthrough.aspx (full list of annotations at the end)