This error occurs when you upload a file to a document library through the SharePoint UI and when the document library has an event receiver which updates the list item on ItemAdded(). The error doesn’t happen all the time which lead us to conclude that it is due to some race conditions, especially knowing that event receivers are run in separate thread.
The error message is “The file … has been modified by … on ….” and the callstack leads to a COMException in the Microsoft.SharePoint.Library assembly, which is the managed wrapper of OWSSVR.dll. The full stacktrace looks something like this:
[COMException (0x81020037): The file Administration/test.doc has been modified by SHAREPOINT\system on 12 May 2008 15:14:29 +1000.]
at Microsoft.SharePoint.Library.SPRequestInternalClass.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish) +0
at Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish) +411
[SPException: The file Administration/test.doc has been modified by SHAREPOINT\system on 12 May 2008 15:14:29 +1000.]
at Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish) +556
at Microsoft.SharePoint.SPListItem.AddOrUpdateItem(Boolean bAdd, Boolean bSystem, Boolean bPreserveItemVersion, Boolean bNoVersion, Boolean bMigration, Boolean bPublish, Boolean bCheckOut, Boolean bCheckin, Guid newGuidOnAdd, Int32& ulID, Object& objAttachmentNames, Object& objAttachmentContents, Boolean suppressAfterEvents) +3030
at Microsoft.SharePoint.SPListItem.UpdateInternal(Boolean bSystem, Boolean bPreserveItemVersion, Guid newGuidOnAdd, Boolean bMigration, Boolean bPublish, Boolean bNoVersion, Boolean bCheckOut, Boolean bCheckin, Boolean suppressAfterEvents) +632
at Microsoft.SharePoint.SPListItem.UpdateOverwriteVersion() +190
at Microsoft.SharePoint.WebControls.SaveButton.SaveItem(SPContext itemContext, Boolean uploadMode, String checkInComment) +256
at Microsoft.SharePoint.WebControls.SaveButton.SaveItem() +111
at Microsoft.SharePoint.WebControls.SaveButton.OnBubbleEvent(Object source, EventArgs e) +476
at System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +50
at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +39
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3215
I spent some time writing test code to try and reproduce the problem when multiple threads are doing updates to the same file and I finally managed to get the error. Then I isolates the cause and reworked the code so the exception is thrown even from a single thread. I finally ended up with the code below which always causes the error. It is running in a single thread but it represents exactly what is happening with the event receiver and the EditForm.aspx pages if you think of it this way: All objects with index “1” are accessed from the event receiver and all objects with index “2”are accessed from the EditForm.aspx code-behind.
using (SPSite site2 = new SPSite(“http://server/sites/test/”))
{
SPFilefile1 = site1.OpenWeb().Lists[“ListName”].RootFolder.Files[0];
SPFilefile2 = site2.OpenWeb().Lists[“ListName”].RootFolder.Files[0];
// It is important to read a field value
object readValue2 = file2.Item[“FieldName”];
file1.Item.UpdateOverwriteVersion();
// The next line throws exception
file2.Item.UpdateOverwriteVersion();
}
The issue only happens when the code is executed in the exact sequence listed above. There is no exception thrown if we first update item2 and then item1. It doesn’t happen if we read a value from item1 rather than item2 and it doesn’t happen if we don’t read any value at all. It also happens no matter if we are using one or two different SPSite objects. All this explains why the issue is not happening all the time but only when specific race conditions are met.
The next question is of course how do we prevent the problem from happening. Well we cannot change the SharePoint code so we have to find a way to update our event receiver code (which is the one with index “1” above). Also note that the EditForm.aspx uses exactly the SPListItem.UpdateOverwriteVersion()method to do the item metadata update and this cannot be changed either.
There are four ways to uppdate a list item through the object model. All of them call the internal method UpdateInternal() which has some interesting arguments like bSystem, bPreserveItemVersion and bNoVersion. The table below shows the different ways to update a list item and how they call the UpdateInternal() method:
Method |
bSystem |
bPreserveItemVersion |
bNoVersion |
SystemUpdate() |
true |
false |
false |
SystemUpdate(false) |
true |
true |
false |
UpdateOverwriteVersion() |
false |
false |
true |
Update() |
false |
false |
false |
The experiment showed that all of them but one were leading to an exception being thrown. The method that worked was SystemUpdate(false).
So one way to fix the problem is changing your event receiver to update the list item using SystemUpdate(false). Some people are additionally recommending to disable the event firing when calling the update as well:
this.DisableEventFiring();
try
{
itemToUpdate.SystemUpdate(false);
}
finally
{
this.EnableEventFiring();
}
This will work well unless you actually want to create a new version of the list item or call any of the other update methods for whateever reason. If that’s your case you could try to use a workaround of delaying the event handler for a couple of seconds (using Thread.Sleep() ) which of course is far from an elegant solution or you could see whether you can move your code to ItemUpdated() or ItemUpdating() rather than ItemAdded().
If you are getting the error in a different case i.e. when your custom code is where the error is thrown (in the case described here the error is thrown in the SharePoint code) you could implement a workaround of waiting and retrying your action again and again after a few seconds for a couple times. As an implementation you could use a try – catch block with a goto statement that moves the execution before the try. This is a very legitimate and good example of when using a “goto” statement in your code is actually good. If you don’t agree with me about the “goto” statement then look at this different but similar problem caused by race conditions and how it is solved using a try-catch-goto block in this Microsoft article: http://msdn.microsoft.com/en-us/library/cc303696.aspx
Usually if you are getting the error in your own code it is due to an SPSite created in a completely different branch of the solution and then SPWebs or SPLists are being passed as arguments down to your function where you try to update the list item. To resolve this error always remember to create a new fresh SPSite object to retrieve the list and the file objects from, in the same method where you are doing the update. This helps big time and solves 90% of the causes of this error I’ve seen. If needed you can additionally add more advanced thread synchronisation techniques to your code such as lock statements, mutexes, semaphors etc.