Donnerstag, 18. Juni 2009

Writing custom timer jobs with multiple content databases

When creating your custom timer job you probably start using code snippets from msdn or other blogs. Most of them are using the SPJobLockType.ContentDatabase intantiating the job SPJobDefinition-Object. While having just one content database on your development machine, it will always be the correct content database. So you are developing your code that is aimed to do something in a particular SPSite and everything works fine.
For executing your code you might start the Execute method with:

public override void Execute(Guid contentDbId)
{
SPSite siteCollection = new SPSite(http://xyz/);
...



Deploying this job in your production environment you might experience multiple executions because there are more than one content databases in your Web Application and your code executes for every content database per run cycle.

There are three different types of locking, as described in the SDK:
  • SPJobLockType.ContentDatabase - Locks the content database associated with the job.
  • SPJobLockType.Job - Locks the job to prevent multiple instances of the job from running on a single server.
  • SPJobLockType.None - No locking.

That means the following:

  • None - In a multi server farm the job runs on every server in your farm. You can use this when you have an switch for the job to check, if it has to run or not so that you can assure their work will not interfere.
  • Job - This means the job is locked in your farm and does run only on one server.
  • ContentDatabase - This locks the job and the content databases so that your job runs on a single server and executes for every database per cycle.

So the semantics of the job lock and the Execute method should match. If not, you could experience this confusing behaviour. Starting your Execute method with a fixed url and using the SPJobLockType.ContentDatabase will cause the job to run for every content database but it is not doing something on that particular database. That results in multiple executions of your code. In this case you should use the SPJobLockType.Job or modify the Execute method to work only on that particular content database.

public override void Execute(Guid contentDBId)
{

SPWebApplication webApp = this.Parent as SPWebApplication;
SPSiteCollection siteColl = webApp.ContentDatabases[contentDBId].Sites;
foreach (SPSite site in siteColl)
{
...

You should consider what lock type your code needs. To have the correct SPJobLockType set you might set it to a fix value in the constructor.