Saturday, January 24, 2009

Workflow SPListItem Out of Date

Error

I ran into this rather strange error a while back that I could not find much information on....

Microsoft.SharePoint.SPException: A file with the name Attachments/oooppp/Sdfkjsdfksdfnjfdsjksfdkjsdfkj fds jksdfkj sdfkj sdffkj fdskj.docx already exists. It was last modified by SHAREPOINT\system on 30 Dec 2008 15:31:28 -0500. ---> System.Runtime.InteropServices.COMException (0x81020067): A file with the name Attachments/oooppp/Sdfkjsdfksdfnjfdsjksfdkjsdfkj fds jksdfkj sdfkj sdffkj fdskj.docx already exists. It was last modified by SHAREPOINT\system on 30 Dec 2008 15:31:28 -0500. at Microsoft.SharePoint.Library.SPRequestInternalClass.PutFile(String bstrUrl, String bstrWebRelativeUrl, Object varFile, PutFileOpt PutFileOpt, String bstrCreatedBy, String bstrModifiedBy, Int32 iCreatedByID, Int32 iModifiedByID, Object varTimeCreated, Object varTimeLastModified, Object varProperties, String bstrCheckinComment, UInt32& pdwVirusCheckStatus, String& pVirusCheckMessage) at Microsoft.SharePoint.Library.SPRequest.PutFile(String bstrUrl, String bstrWebRelativeUrl, Object varFile, PutFileOpt PutFileOpt, String bstrCreatedBy, String bstrModifiedBy, Int32 iCreatedByID, Int32 iModifiedByID, Object varTimeCreated, Object varTimeLastModified, Object varProperties, String bstrCheckinComment, UInt32& pdwVirusCheckStatus, String& pVirusCheckMessage) --- End of inner exception stack trace --- at Microsoft.SharePoint.Library.SPRequest.PutFile(String bstrUrl, String bstrWebRelativeUrl, Object varFile, PutFileOpt PutFileOpt, String bstrCreatedBy, String bstrModifiedBy, Int32 iCreatedByID, Int32 iModifiedByID, Object varTimeCreated, Object varTimeLastModified, Object varProperties, String bstrCheckinComment, UInt32& pdwVirusCheckStatus, String& pVirusCheckMessage) at Microsoft.SharePoint.SPFileCollection.AddInternal(String urlOfFile, Object file, PutFileOpt fileOpt, String createdBy, String modifiedBy, Int32 createdByID, Int32 modifiedByID, DateTime timeCreated, DateTime timeLastModified, Object varProperties, String checkInComment, SPVirusCheckStatus& virusCheckStatus, String& virusCheckMessage) at Microsoft.SharePoint.SPFileCollection.Add(String urlOfFile, Byte[] file, Boolean overwrite, String checkInComment, Boolean checkRequiredFields, SPVirusCheckStatus& virusCheckStatus, String& virusCheckMessage) at Microsoft.SharePoint.SPFileCollection.Add(String urlOfFile, Byte[] file, Boolean overwrite, String checkInComment, Boolean checkRequiredFields) at Microsoft.SharePoint.SPFileCollection.Add(String urlOfFile, Byte[] file) at Portal.Contracts.WF.ContractApproval.CopyArchiveAttachments() at Portal.Contracts.WF.ContractApproval.onCreateAmendmentAttachmentFolder(Object sender, EventArgs e) at System.Workflow.ComponentModel.Activity.RaiseEvent(DependencyProperty dependencyEvent, Object sender, EventArgs e) at System.Workflow.Activities.CodeActivity.Execute(ActivityExecutionContext executionContext) at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext) at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext) at System.Workflow.ComponentModel.ActivityExecutorOperation.Run(IWorkflowCoreRuntime workflowCoreRuntime) at System.Workflow.Runtime.Scheduler.Run()

Cause

As usual the error was pretty perplexing until I dug deeper into what I was doing.

The cause has to do with the SPFile and the SPListItem getting out of sync with each other. In my MOSS workflows I move items between libraries as well as modifying XML in InfoPath form instances. The issues is not so much the initial action I take, any second action to the workflow SPListItem will cause the error.

The error was spread across several places in my workflow; I will try to boil it down for you here:

  • I get the SPListItem, which in this case is an InfoPath form.
  • I get the XML string of the InfoPath form from the SPListItem, deserialize it into a class, do some updates, serialize it back into XML and push it back into the SPListItem.
  • Then I would modify some of the permissions to the SPListItem. This is where the error would occur.

Getting this error was pretty perplexing at first. I my InfoPath form utility (which I will be posting shortly), I was getting the XML string, modifying it, and then saving it back into the binary. Below is some extracted code where I was getting the workflow item and pushing in a fileStream with the modified XML.

SPListItem infoPathItem = WorkflowProperties.Item;
infoPathItem.File.SaveBinary(fileStream);
infoPathItem.File.Update();

Then later on in a different code event, I had some more code where I subsequently called:

WorkflowProperties.Item.Update(); 

The moment I call the second update, the error would come up.

Resolution

After much research I found out the following:

  1. This error would occur whether I was doing this in WF or in some web part. Basically changing the SPListItem's File.Update() does not sync up the entire SPListItem with SharePoint. So when the second update is called, SharePoint scoffs at you saying the object instance you have is out of date with the one on the server. Valid complaint too!
  2. WorkflowProperties.Item is only loaded when the workflow rehydrates. Even though I may reference it many times, across many code events, the WorkflowProperties.Item will only load up once. Let me explain rehydrating (very similar to K2 or BizTalk). What happens is when a WF instances goes into a wait state, like waiting for a TaskChanged event to fire, WF will persist itself to the SharePoint database. When the TaskChanged event fires, the WF instance will rehydrate based on the data that was saved. At that point, the WorkflowProperties.Item will be loaded up. It will not be loaded up every time I reference WorkflowProperties.Item.

My resolution is to add a simple property to my WF class. What it will do is ensure that I always load up the SPListItem every time I need it.


private SPListItem WorkflowItem
{
get
{
SPDocumentLibrary library = (SPDocumentLibrary)WorkflowProperties.Web.Lists[WorkflowProperties.ListId];

return library.GetItemById(WorkflowProperties.ItemId);
}
}

References

5 comments:

Peter Weissbrod said...

Two things:

-As a a newcomer to SharePoint development, I just want you to know that posts such as yours can make my transition much more merciful for me, so THANK YOU for pointing out a fact that was right under my nose this whole time!

-My goal is to create a workflow where a single form passes trough the hands of many different people. I found that by making custom SUBMIT buttons on my form, I can harness the onWorkflowItemChanged event handler to move from state to state, this is very nice in practice.
One challenge I encountered was when I want to update the form inside of the workflow without triggering the workflowItemChanged event. By using some reflection code I was able to disable/re-enable the event firing from outside of the event receiver(http://unclepaul84.blogspot.com/2007/12/disable-event-firing-in-sharepoint-when.html), but I cannot count on the longevity of this hack. I was wondering if you have ever experienced anything of a similar nature.

Jason Apergis said...

I really apprecieate your feedback. I got this error on my way to building this blog entry - http://www.k2distillery.com/2009/02/getting-and-updating-infopath-xml-in-wf.html. Would this help for your second question? It is all about how to update the XML of the form from the process...

Peter Weissbrod said...

Jason

In practice I have found that leveraging the list item event handlers is a much simpler solution to workflows that are triggered by file changes.

I build a list in sharepoint. Then I use the VSEWSS solution generator to convert it into a visual studio list template project, then I attach my own Item event handlers and this works more reliably and better control than I was seeing with workflow engines. This might be my own ignorance of how to properly leverage the WF, but in the end I have something much less complex and more flexible.

Jason Apergis said...

Peter,

I know how you go there. Many, many, many developers go back to list events to create their workflows after painful experiences with WF. To tell you the truth, that is fine with me.

I do personally like the ellegance of WF once I got the hang of it and I have a ton of utilities sitting around at my disposal. I need to find the time to get them up. However they are just the biggest pain. That is why I love K2 when I can get it in because it removes all those challenegs.

Jason

Pritish said...

Thanks for the write up - though I didn't face the same issue precisely, I found your descriptive text / background research on the problem to be the most insightful available. My understanding of the inner workings of WF grows with every challenge. To reliably update a list item's properties, I attempt to atomicize each update (approximate psuedocode):


SPDocumentLibrary library = (SPDocumentLibrary)workflowProperties.Web.Lists[workflowProperties.ListId];

SPListItem splistitem = library.GetItemById(workflowProperties.ItemId);
splistitem.File.CheckOut();

DateTime apprDate = DateTime.Now;

splistitem["Approval Date"] = apprDate;

splistitem.Update();

splistitem.File.CheckIn("Approval Date is recorded as " + apprDate.ToShortDateString() + " (per TI Approval Workflow)");


I'll follow up with an in-depth write up on my blog; http://pritishwrites.blogspot.com