SharePoint "Thread was being aborted" looping through a SPListItemCollection - sharepoint

I have a SPListItemCollection with ~500 items in. I am looping through using a for each loop, each time grabbing the file and writing it out to a pdf document. I am getting the error "Thread was being aborted". It looks to be a time out issue but I dont know how to fix it? Collections with 200-300 items work fine. Is there any way I can stop getting this error. If I cant increase the timeout I will need to batch it up.
Update
I have tried splitting up the processing in batches of 100 items. With each new 100 items using a new spweb and site and pulling the items from sharepoint. This is all done within the same method (so I am not recreating my custom object) however the same error is still occuring...

http://blogs.msdn.com/solutions/archive/2009/01/08/getting-around-thread-was-being-aborted-error-when-creating-ep-site.aspx
In a basic text editor such as Notepad, open the web.config file for example ' %SYSTEMDRIVE%\Inetpub\wwwroot
-or-
%SYSTEMDRIVE%\\Inetpub\wwwroot\wss\VirtualDirectories\80 folder
Press CTRL + F to open the Find dialog box.
Find the following tag:
<httpRuntime maxRequestLength="51200" />
Replace it with this tag:
<httpRuntime executionTimeout="6000" maxRequestLength="51200" />
This seems to have got it working for me.

Keep in mind there is a right and a very wrong way to iterate over list items, and it makes a huge difference in performance. The wrong way will cause n+1 calls to the database for the list data; the right way only makes a single call to the database.
Worst:
for (int i = 0; i < myList.Items.Count; i++)
{
SPListItem thisItem = myList.Items[i];
// yada yada yada
}
Bad:
foreach (SPListItem item in myList.Items)
{
// yada yada yada
}
Right:
SPListItemCollection myItems = myList.Items;
foreach (SPListItem item in myItems)
{
// yada yada yada
}
Updated: To reflect more accurate guidance

Iterating through an SPListItemCollection is never a good idea (but sadly sometimes the only way to go). You could consider wrapping up this code in a LongRunningOperation. Here's some code I adapted from some of my own:
ACTUAL CLASS:
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.SharePoint;
namespace Common.LongRunningJobs
{
/// <summary>
/// Provides a long running job wrapper around converting multiple files
/// </summary>
[CLSCompliant(false)]
public class FileToPdfLongRunningJob : LongRunningOperationJob
{
private List<string> fileUrls;
/// <summary>
/// Initializes a new instance of the <see cref="FileToPdfLongRunningJob"/> class.
/// </summary>
/// <param name="urls">The urls of the files to create pdfs from.</param>
public FileToPdfLongRunningJob(List<string> urls)
{
fileUrls = urls;
}
/// <summary>
/// Does the actual work of converting the files, while providing the user with status updates.
/// </summary>
public override void DoWork()
{
try
{
using (var currentWeb = Site.OpenWeb(parentWeb))
{
OperationsPerformed = 0;
foreach (var url in fileUrls)
{
SPFile file = currentWeb.GetFile(url);
// DO PDF OUTPUT
StatusDescription = string.Format(CultureInfo.InvariantCulture, "busy converting {0} van {1}, file: {2}...", (OperationsPerformed + 1), TotalOperationsToBePerformed, file.Name);
UpdateStatus();
OperationsPerformed++;
}
}
}
catch (Exception ex)
{
// LOG ERROR
}
}
}
}
USING ABOVE CLASS:
private void ConvertFiles()
{
const string PROGRESS_PAGE_URL = "/_layouts/LongRunningOperationProgress.aspx";
var urls = new List<string>();
foreach (SPListItem item in yourlistitemcollection)
{
urls.Add(item.File.Url);
}
var longRunningJob = new FileMoveLongRunningJob(urls)
{
Title = "Pdf conversion Long Running Job",
TotalOperationsToBePerformed = urls.Count,
RedirectWhenFinished = true,
NavigateWhenDoneUrl = urlToRedirectTo;//i.e. SPContext.GetContext(Context).List.RootFolder.ServerRelativeUrl
};
longRunningJob.Start(SPContext.Current.Web);
string url = string.Format("{0}{1}?JobId={2}", SPContext.Current.Web.Url, PROGRESS_PAGE_URL, longRunningJob.JobId);
SPUtility.Redirect(url, SPRedirectFlags.Default, Context);
}

Try creating new SPSite and SPWeb objects before getting items (and get them from new webs).
Often times that solves the problem.

There can be potential pit falls in the code that iterates list. It will be good if you can paste your code here.
Also, its better to use Caml to query for list items and select only those fields that are required for your operation rather than all.

Related

Refresh the view to avoid the error another process has updated ARRegister

Upon deletion of my custon item LELettering in which I have a view with ARRegister lines, I want to :
- Reverse the payment application for every Invoice ARRegister lines for the payment also included in my ARRegister lines.
- Clean the LetteringCD I did put in a custom field in my ARRegister extension.
Now when I pick one or the other alone, it works.
My problem is when I do both : reverseapplication() do it's job, but this, as a side effect, updates the ARRegister records when I call the reverseapplication method from the ARPaymentEntry.
Which lead to an error : "Another process has update the ARRegister record and your changes will be lost", when I try to update the ARRegister records to clean my custom field LetteringCD.
I think my problem is my view Lines is not refreshed once reverseApplication is called, so it still has the not yet updated records of ARRegister.
I tried ClearQueryCache() but it doesnt seem to work, how to I force a refresh on my view Lines so I can update them again ?
public PXSelect<LELettering> Piece;
public PXSelect<ARRegister> Lines;
protected virtual void LELettering_RowDeleting(PXCache sender, PXRowDeletingEventArgs e)
{
// Cancel the lettering by removing every LetteringCD from the ARRegister lines and reverse application paiements
cancelLettering();
}
protected void cancelLettering()
{
reverseApplication();
eraseLetteringCD();
}
protected void reverseApplication()
{
string refNbr = "";
List<ARRegister> lines = new List<ARRegister>();
foreach (ARRegister line in PXSelect<ARRegister, Where<ARRegisterLeExt.lettrageCD,
Equal<Required<ARRegisterLeExt.lettrageCD>>>>.Select(this, Piece.Current.LetteringCD))
{
if (line.DocType == "PMT") refNbr = line.RefNbr;
else lines.Add(line);
}
ARPaymentEntry graphPmt = getGraphPayment(refNbr, "PMT");
foreach(ARAdjust line in graphPmt.Adjustments_History.Select())
{
graphPmt.Adjustments_History.Current = line;
graphPmt.reverseApplication.Press();
}
graphPmt.release.Press();
graphPmt.Actions.PressSave();
}
// Here is my problem
protected void eraseLetteringCD()
{
foreach (var line in Lines.Select())
{
line.GetItem<ARRegister>().GetExtension<ARRegisterLeExt>().LettrageCD = null;
Lines.Current = Lines.Update(line);
}
Actions.PressSave();
}
protected ARPaymentEntry getGraphPayment(string refNbr, string docType)
{
ARPaymentEntry graphPmt = CreateInstance<ARPaymentEntry>();
ARPayment pmt = PXSelect<ARPayment, Where<ARPayment.refNbr, Equal<Required<ARPayment.refNbr>>,
And<ARPayment.docType, Equal<Required<ARPayment.docType>>>>>
.Select(this, refNbr, docType);
if (pmt == null) throw new PXException(Constantes.errNotFound);
graphPmt.Document.Current = pmt;
return graphPmt;
}
Edit:
The problem comes from the fact the records ARRegister are saved two times, once with the reversepaymentapplication, and once in the eraseLetteringCD, but I dont know how to avoid this in my case.
Some things I might try...
I do see that there are multiple graphs involved. The second graph will need to refresh the results before it can process. There are a few ways of doing this that I try...
You can try to clear the query cache as shown below. My guess when you call Lines.Select it has an old cached value?
protected void cancelLettering()
{
reverseApplication();
Lines.Cache.ClearQueryCache()
eraseLetteringCD();
}
I find it helpful some items in reverse if the select is not returning the cached results to find the cached row myself. The reverse could be to use PXSelectReadonly<> as your foreach select statement as this should use the records from the DB vs any cached values.
protected void eraseLetteringCD()
{
// Also try PXSelectReadonly<> in place of Lines.Select()
foreach (ARRegister line in Lines.Select())
{
//Get cached row
var cachedRow = (ARRegister)Lines.Cache.Locate(line) ?? line;
cachedRow.GetExtension<ARRegisterLeExt>().LettrageCD = null;
Lines.Update(cachedRow );
}
Actions.PressSave();
}
After the first press save you could also try to just clear the cache and the query cache to setup for the next call. Ideally if possibly just do one persist.
If the first graph is running a long operation you can make the code pause to wait for the operation to complete. This would be useful when using multiple graphs. The second graph persist should not run until the first long running process has finished. Example using the ID of your graphPmt instance:
graphPmt.release.Press();
PXLongOperation.WaitCompletion(graphPmt.UID)

EventReceiver to Create folder sharepoint 2010

I have this event receiver c# class that I am trying to implement on a Sharepoint site. It did not work. I have deployed it from visual studio 2010 after it was build ok. Does anyone see what is the problem? Is the code ok? or is the problem on the SP? Thank you.
- here is the new code
using System;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;
namespace EventReceiverCFolder.EventReceiver1
{
/// <summary>
/// List Item Events
/// </summary>
public class EventReceiver1 : SPItemEventReceiver
{
/// <summary>
/// An item is being added.
/// </summary>
public override void ItemAdded(SPItemEventProperties properties)
{
try
{
if (properties.ListTitle == "CF") // list where the item was added
{ // if item was added to this list then create a folder on - Dlib - list
UpdateFolder(properties);
}
}
catch (Exception ex)
{
properties.Status = SPEventReceiverStatus.CancelWithError;
properties.ErrorMessage = ex.Message;
properties.Cancel = true;
}
}
private void UpdateFolder(SPItemEventProperties properties)
{
string foldername = properties.ListItem["Title"].ToString();
try
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
//inside RunWithElevatedPriviliges I need to open a new site (an elevated site)
using (SPSite site = new SPSite(properties.Web.Site.ID))
{
using (SPWeb web = site.OpenWeb())
{
web.AllowUnsafeUpdates = true;
SPList list = web.Lists.TryGetList("DLib"); // this is doc Library where the new folder will be created
//note that we are creating a list item, not a folder - a folder IS a list item
SPListItem createdFolder = list.Items.Add(list.RootFolder.ServerRelativeUrl, SPFileSystemObjectType.Folder, null);
if (createdFolder != null)
{
createdFolder["Name"] = foldername;
createdFolder.Update();
}
list.Update();
}
}
});
}
finally { }
}
}
}
Don't do this: SPUser privilegedAccount = properties.Web.AllUsers[#"SHAREPOINT\SYSTEM"];
Read up on using SPSecurity.RunWithElevatedPrivileges. See the MSDN documentation here.
Also don't do a using (SPSite... and inside the using block you try to get the web via SPContext.Current - that web won't be elevated anymore.
The correct way is something along these lines (I didn't try this, so it' just to give you an idea where you are headed):
private void UpdateFolder(SPItemEventProperties properties)
{
string foldername = properties.ListItem["Title"].ToString();
SPSecurity.RunWithElevatedPrivileges(delegate()
{
//inside RunWithElevatedPriviliges I need to open a new site (an elevated site)
using (SPSite site = new SPSite(properties.Web.Site.ID))
{
using (SPWeb web = site.OpenWeb())
{
web.AllowUnsafeUpdates = true;
SPList list = web.Lists.TryGetList("ListTitle"); //is that really the list title?
//note that we are creating a list item, not a folder - a folder IS a list item
SSPListItem createdFolder = list.Items.Add(list.RootFolder.ServerRelativeUrl, SPFileSystemObjectType.Folder, null);
if (newFolder != null)
{
createdFolder["Name"] = foldername;
createdFolder.Update();
}
list.Update();
}
}
});
}
Also try to debug your code, set breakpoints etc.
I had to get folder name like this:
string foldername = Convert.ToString(properties.AfterProperties["Title"]);
Did you try to debug it? try to debug and tell us what error you are getting.
BUT before you debug first use sharepoint manager to see if your event receiver is attached properly.
If you dont know how to debug sharepoint event receiver then please see this

How to update field value in current item via event receiver?

EDIT: I've realized that my approach in the second code block was unnecessary. I could accomplish the same thing by doing the following in ItemUpdated:
SPListItem thisItem = properties.ListItem;
thisItem.File.CheckOut();
thisItem["Facility Number"] = "12345";
thisItem.Update();
thisItem.File.CheckIn("force check in");
Unfortunately, I'm still getting the same error message when "thisItem.Update();" is executed: he sandboxed code execution request was refused because the Sandboxed Code Host Service was too busy to handle the request
I actually was receiving the error above when deploying my sandbox solution originally and used this link (http://blogs.msdn.com/b/sharepointdev/archive/2011/02/08/error-the-sandboxed-code-execution-request-was-refused-because-the-sandboxed-code-host-service-was-too-busy-to-handle-the-request.aspx) to fix it.
I am trying to write a C# event receiver that changes the value of a field when a document is added/changed in a library. I have tried using the following code:
public override void ItemUpdating(SPItemEventProperties properties)
{
base.ItemUpdating(properties);
string fieldInternalName = properties.List.Fields["Facility Number"].InternalName;
properties.AfterProperties[fieldInternalName] = "12345";
}
Unfortunately, this is only working for certain fields. For example, if I replaced "Facility Number" with "Source", the code will execute properly. This may be the fact that we are using a third party software (called KnowledgeLake) that replaces the default edit form in SharePoint with a Silverlight form. Anyway, because I was having challenges with the code above (again, because I think the Silverlight form may be overriding the field after the ItemUpdating event fires), I have tried the following code:
public override void ItemUpdated(SPItemEventProperties properties)
{
base.ItemUpdated(properties);
//get the current item
SPListItem thisItem = properties.ListItem;
string fieldName = "Facility Number";
string fieldInternalName = properties.List.Fields[fieldName].InternalName;
string fieldValue = (string)thisItem["Facility Number"];
if (!String.IsNullOrEmpty(fieldValue))
{
//properties.AfterProperties[fieldInternalName] = "123456789";
SPWeb oWebsite = properties.Web as SPWeb;
SPListItemCollection oList = oWebsite.Lists[properties.ListTitle].Items;
SPListItem newItem = oList.GetItemById(thisItem.ID);
newItem.File.CheckOut();
thisItem[fieldInternalName] = "12345";
thisItem.Update();
newItem.File.CheckIn("force");
}
}
First off, the above seems a little klunky to me as I would love to just use the AfterProperties method. Additionally, I am getting the following error when "newItem.Update()" is executed: he sandboxed code execution request was refused because the Sandboxed Code Host Service was too busy to handle the request
Am I missing something here? I would love to utilize the first code block. Any help would be appreciated.
Josh was able to answer his own question, which helped me fix my problem as well. Here is a working code snippit.
public override void ItemUpdated(SPItemEventProperties properties)
{
string internalName = properties.ListItem.Fields[columnToUpdate].InternalName;
//Turn off event firing during item update
base.EventFiringEnabled = false;
SPListItem item = properties.ListItem;
item[internalName] = newVal;
item.Update();
//Turn back on event firing
base.EventFiringEnabled = true;
base.ItemUpdated(properties);
}

How to add tag to document in doc library through programming in liferay?

I am new to liferay. I am using liferay 6.0.5
I want to add "Archive" tag to doc library document through code so latter on I can fetch the document with "Archive" tag.
how to do this?
Here is the code being worked upon :
private void addArchive(List<DLFileEntry> fileEntryList) {
try
{
long groupId=0;
long userId=0;
String className=null;
long classPK=0;
long categoryIds[]=null;
String tagNames[]=null;
String newTagNames[] = new String[20];
long entryId = 0;
List<AssetTag> TNames = new ArrayList<AssetTag>();
int i = 0;
for(DLFileEntry cur:fileEntryList)
{
groupId=cur.getGroupId();
userId=cur.getUserId();
className=cur.getClass().getName();
classPK=cur.getPrimaryKey();
AssetEntry ae=AssetEntryLocalServiceUtil.getEntry(groupId, cur.getUuid());
categoryIds=ae.getCategoryIds();
entryId = ae.getEntryId();
TNames = ae.getTags();
System.out.println(cur.getTitle());
i=0;
for(AssetTag tag : TNames)
{
System.out.println(tag.getName());
newTagNames[i]=tag.getName().toString();
i++;
}
newTagNames[i]="NameArchive";
AssetEntryLocalServiceUtil.updateEntry(userId, groupId, className, classPK, categoryIds, newTagNames);
System.out.println("------------------------------------------------");
}
System.out.println("outside for loop");
}
catch (Exception e) {
// TODO: handle exception
}
}
Firstly you need to find the "entryId" of the File in your Document Library using the DLFileEntryLocalServiceUtil class (and the other associated DL*LocalServiceUtil classes).
The "entryId" relates to the "classPK" field on the assetentry table in the database. Then you can use the following method to update the AssetEntry with the Tag "Archive":
AssetEntryLocalServiceUtil.updateEntry(userId, groupId, DLFileEntry.class.getName(), fileEntryIdYouJustGot, categoryIds, new String[] {"Archive"});
This will remove any other tags though so you may want to retrieve the current tags from the Asset Entry and then add "Archive" to them, then pass the resulting array to the method.
But this is the basis of what you need to do.
~~ EDIT ~~
Amend your getEntryLine to use the following. You're passing in the DLFileEntry UUID, but if you look at the Source Code it's asking for a classUuid which I don't believe are the same thing. Also add logging in your Exception handling to see if a Exception is being thrown.
AssetEntry ae=AssetEntryLocalServiceUtil.getEntry(groupId, classPK);

Will using a SPListItemCollection returned from a function reopen the SPWeb?

After reading Stefan Gossner's post about disposing objects and this question about Cross method dispose patterns, I found that I was guilty of accidentally reopening some SPWebs. I know in Stefan Gossner's post he mentions you should dispose of an SPWeb after you are finished with any child object. However, the microsoft documentation mentions Caching the SPListItemCollection object. Is the following code correct? Would the returned SPListItemCollection reopen an SPWeb object? Is there any way to tell for sure?
// is this correct????
private SPListItemCollection GetListItems()
{
SPListItemCollection items = null;
try
{
using (SPSite site = new SPSite(GetListSiteUrl()))
{
using (SPWeb web = site.OpenWeb())
{
// retrieve the list
SPList list = web.Lists[_ListName];
// more code to create the query...
items = list.GetItems(query);
}
}
}
catch (Exception e)
{
// log error
}
return items;
}
Edit 09/09/09
I am mainly referring to this part of Stefan Grossner's post:
You should dispose a SPWeb or SPSite
object after the last access to a
child object of this object.
I believe what he is saying is that if I use the SPListItemCollection after I dispose of the SPWeb that I used to get it... the SPWeb will be reopened automatically.
I found out after asking Stefan directly that the SPListItemCollection can indeed reopen the SPWeb after you dispose of it. This means that my code posted above is INCORRECT and I would only be able to dispose of the SPWeb after I use the SPListItemCollection.
Update: It is better to convert to the SPListItemCollection to something else and return that instead.
private DataTable GetListItems()
{
DataTable table = null;
try
{
SPListItemCollection items = null;
using (SPSite site = new SPSite(GetListSiteUrl()))
{
using (SPWeb web = site.OpenWeb())
{
// retrieve the list
SPList list = web.Lists[_ListName];
// more code to create the query...
items = list.GetItems(query);
// convert to a regular DataTable
table = items.GetDataTable();
}
}
}
catch (Exception e)
{
// log error
}
return table;
}
As far as I know the answer is no, but I would have written the code something like
private void FetchItems(Action<SPListItemCollection> action)
{
using(...)
{
var items = list.GetItems(query);
action(items);
}
}
By doing this, to call this method you would need to send a method along (delegate) that the SPListItemCollection should be used for, an example:
FetchItems( items => ....) or FetchItems( DoStuffWithItems(SPListItemCollection) )
If you are talking about whether you need an SPWeb in the same scope when you get around to using the SPListItemCollection, I think the answer is no.
For example, I routinely do the following:
private IEnumerable<SPListItem> AllItems;
public void GetItems()
{
var results = SPContext.Current.Web.Lists[ListName].Items.Cast<SPListItem>();
this.AllItems = results;
}
and then I use AllItems all over the place, and it works fine.
Incase you are wondering, the cast is done so I can use Linq on the result set - much much faster than submitting a query to the list, especially if you are doing multiple subselects on the data.

Resources