I have a distributed client server system which has multiple ‘worker’ engines and a WCF service, lets call it, FileManagerService, which adds ‘jobs’ to a database for the ‘worker’ engines to pick up and process.
The database consists of two tables: Jobs and File. Every job operates on a File ( many to one relationship ). Multiple jobs can be added to the Jobs table at once, they are processed in the fashion of a queue.
Example of Job‘s are actions such as : MoveFile or DeleteFile, etc.
I am trying to achieve mutual exclusion on the File record by using a ‘locked’ ( bool ) column in the File table itself.
The mutual exclusion zones that I think are needed are within the WCF service FileManagerService‘s method: addJob() and within the ‘worker’ engines’ TakeAndProcessAJob().
At the moment I am having trouble with working out how linq-to-sql deferred execution and how the linq-to-sql transaction system works.
I currently have the following method which is supposed to take a Job from the Jobs table, check if its related File is locked, if it is locked we will return nothing, if it is not locked we will lock the File and return a Job, and delete that Job from the Jobs queue:
public static Job GetAnAvailableJob()
{
using (DBDataContext db = new DBDataContext())
{
DataLoadOptions loadOptions = new DataLoadOptions();
loadOptions.LoadWith<Job>(f => f.File);
db.LoadOptions = loadOptions;
var jobAvailable = from ja in db.Jobs
where ja.File.locked == false
select ja;
var jobToTake = jobAvailable.FirstOrDefault();
// This file temp is here so that we can return
// an associated File with the Job.
// We delete
File fileTemp = null;
if (jobToTake != null)
{
fileTemp = jobToTake.File;
jobAvailable.File.locked = true;
Console.WriteLine("Locked file:" + fileTemp.FileID);
db.Jobs.DeleteOnSubmit(jobToTake);
db.SubmitChanges();
jobToTake.Asset = fileTemp;
}
return jobToTake;
}
}
The ‘worker’ engines TakeAndProcessAJob() basically does the following: Call GetAnAvailableJob(), if return object is not null -> proccess the job, if it is null -> sleep.
The WCF Service FileManagerService uses this locking method:
public static bool LockAsset(long FileID)
{
using (DBDataContext db = new DBDataContext())
{
var fileToLock = db.Files.Where(f => f.FileID == FileID && f.locked == false).Single();
if (fileToLock == null)
{
return false;
}
else
{
Console.WriteLine("Locked File:" + fileToLock.AssetID);
fileToLock.locked = true;
db.SubmitChanges();
return true;
}
}
}
What do I need to change in these two methods to make then behave in a transactional manner and have mutual exclusion on the File record. Currently I am getting change conflict exceptions – row not changed or found – on the SubmitChanges() within the TakeAndProcessAJob() method.
Thanks
I could not figure out the answer to my question and so I did some logic restructuring and devised a workaround using plain old SQL. To lock the record I now use a single line ( atomic ) command on the record:
UPDATE dataTable SET locked = 'True' WHERE (locked = 'False') AND (recordID= 15)I then use
SqlDataReaderand check theRecordsAffectedfield. If records have changed, it means that I have successfully locked the record. If records have not changed, it means the record is already locked, and so I returnfalseon my locking method.The process that is trying to lock the record then sits in a while loop calling
lockRecordand waiting for a successful callback.I’m not sure if it was completely necessary to make the switch from LINQ to SQL. I made the logic changes in my application, and the switch to plan SQL at the same time and so it is difficult to identify if using LINQ was THE problem.