SPWorkItemJobDefinition is the special type of the regular SPJobDefinition – base class for custom Sharepoint jobs. The main difference of work item jobs from regular jobs is that they allow to pass parameters, which can be useful in different scenarios. For example when you need to specify that data for processing is located on specific site collection and on specific site inside this site collection.
First of all you need to define class – inheritor of SPWorkItemJobDefinition:
There are several important notes. First of all in constructor we pass SPJobLockType.None, which, according to the documentation, means:
Provides no locks. The timer job runs on every machine in the farm on which the parent service is provisioned, unless the job I associated with a specified server in which case it runs on only that server (and only if the parent service is provisioned on the server).
But in case of SPWorkItemJobDefinition it has also other reason. Let’s see how SPWorkItemJobDefinition.Execute() method is implemented (this is the starting point of the work item job):
The logic here is quite simple: if in constructor we passed SPJobLockType.ContentDatabase it gets instance of content database using its id passed in targetInstanceId parameter and calls HandleOneContentDatabase() method (lines 5-9). There may be problem with this code: Sharepoint may pass incorrect targetInstanceId. E.g. we faced with situation when it passed correct targetInstanceId on development environment, but it was incorrect on production. In this case you have to override Execute() method and fix it e.g. like this:
It may be the one of the reasons of why your job doesn’t work. This workaround will work only with 1st content database, if you have several you need to write additional code there. However let's return to our analysis. If SPJobLockType.None was used then code enumerates all content databases and calls HandleOneContentDatabase method for each of them. As you probably know there is also SPJobLockType.Job lock type, however if it is used for work item job, this job won’t be run at all. This is second important finding.
Let’s see how HandleOneContentDatabase is implemented:
Here it also checks lock. If ContentDatabase lock is used, it calls ProcessWorkItems() method with the following signature:
If None lock is used, then it uses the following signature:
This is another reason of why your SPWorkItemJobDefinition doesn’t work: if you passed SPJobLockType.None, but then override ProcessWorkItems with two parameters, it won’t run, because in this case you need to override ProcessWorkItems with three parameters (and in opposite way for SPJobLockType.ContentDatabase).
Another important moment is that it uses SPWorkItemJobDefinition.WorkItemType() guid for retrieving items from the database. Each custom work item class should override this method in order to specify what items should be handled by this job type (line 4 in the HandleOneContentDatabase() method).
Now our custom job is ready and we need to add the work item to the queue (to the dbo.ScheduledWorkItems table in content database). It can be done by the following code:
Parameters are well described in the following post: Processing items with Work Item Timer Jobs in SharePoint 2010. Here it is important to note that we pass time in UTC format (line 2) and the same work item type as used in the custom work item job class in WorkItemType() method (line3). After this if you will check dbo.ScheduledWorkItems table you should see new row for the item which was added by the code above. But this item won’t be processed without running the job instance. So the next step will be running our custom job:
This example shows how to run custom SPWorkItemJobDefinition one time. If you need to run it e.g. hourly or daily you need to use another class for the schedule.
However all of this also don’t guarantee that your work item job will run. Let’s check code of HandleOneContentDatabase() method again. In order to get active work items for processing it calls SPContentDatabase.GetActiveWorkItems() method. In turn this method uses proc_GetRunnableWorkItems stored procedure in the content database. There is a lot of code in this stored procedure, but we need to see it also in order to find one more reason of why job can’t be run:
Here it makes several queries to the ScheduledWorkItems table and it is important to note that every time it uses the following condition: DeliveryDate <= @Now. I.e. retrieves all items which were created earlier that current datetime. This is very important, because if database server’s time is unsynchronized with time on WFE server (e.g. when it is earlier than on the WFE) from where we created job using SPSite.AddWorkItem() method, your job may not be run because proc_GetRunnableWorkItems stored procedure won’t run items. It will happen because delivery date time will belong to the future on the database server. In order to fix the issue you need to synchronize time on your database server (it will be better if time will be synchronized on all servers in the farm. You will have less problems in general in this case).
This is all what I wanted to say about internal mechanisms of SPWorkItemJobDefinition and the reasons of why it may not work. Hope that it was interesting and it will help in your work.