В моем приложении ASP.NET MVC я пытаюсь получить все элементы в списке с историей версий, а затем преобразовать их в настраиваемый объект. Для этого я использую Microsoft.SharePoint
.
Изначально я делал это следующим образом:
Метод Util.GetSPItemCollectionWithHistory:
public static SPListItemCollection GetSPItemCollectionWithHistory(string listName, SPQuery filterQuery)
{
using (SPSite spSite = new SPSite(sp_URL))
{
using (SPWeb spWeb = spSite.OpenWeb())
{
SPList itemsList = spWeb.GetList("/Lists/" + listName);
SPListItemCollection listItems = itemsList.GetItems(filterQuery);
return listItems;
}
}
}
Метод GetSPObjectsWithHistory:
protected static List<SPObjectWithHistory<T>> GetSPObjectsWithHistory(SPQuery query = null, List<string> filters = null)
{
List<SPObjectWithHistory<T>> resultsList = new List<SPObjectWithHistory<T>>();
Type objectType = typeof(T);
string listName = "";
query = query ?? Util.DEFAULT_SSOM_QUERY;
if (objectType == typeof(SPProject)) listName = Util.PROJECTS_LIST_NAME;
else if (objectType == typeof(SPTask)) listName = Util.TASKS_LIST_NAME;
else throw new Exception(String.Format("Could not find the list name for {0} objects.", objectType.Name));
SPListItemCollection results = Util.GetSPItemCollectionWithHistory(listName, query);
foreach (SPListItem item in results)
{
resultsList.Add(new SPObjectWithHistory<T>(item, filters));
}
return resultsList;
}
Конструктор класса SPObjectWithHistory:
public SPObjectWithHistory(SPListItem spItem, List<string> filters = null)
{
double.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion);
History = new Dictionary<double, T>();
if (spItem.Versions.Count > 1)
{
for (int i = 1; i < spItem.Versions.Count; i++)
{
if (filters == null)
History.Add(double.Parse(spItem.Versions[i].VersionLabel), SPObject<T>.ConvertSPItemVersionObjectToSPObject(spItem.Versions[i]));
else
{
foreach (string filter in filters)
{
if (i == spItem.Versions.Count - 1 || (string)spItem.Versions[i][filter] != (string)spItem.Versions[i + 1][filter])
{
History.Add(double.Parse(spItem.Versions[i].VersionLabel), SPObject<T>.ConvertSPItemVersionObjectToSPObject(spItem.Versions[i]));
break;
}
}
}
}
}
}
Таким образом код работает, но для больших списков он работает очень медленно. В одном из списков содержится более 80000 элементов, а создание одного элемента SPObjectWithHistory
занимает около 0,3 секунды из-за логики конструктора.
Чтобы ускорить процесс, я хотел использовать Parallel.ForEach
вместо обычного foreach
.
Затем мой GetSPObjectsWithHistory
был обновлен до следующего:
protected static List<SPObjectWithHistory<T>> GetSPObjectsWithHistory(SPQuery query = null, List<string> filters = null)
{
ConcurrentBag<SPObjectWithHistory<T>> resultsList = new ConcurrentBag<SPObjectWithHistory<T>>();
Type objectType = typeof(T);
string listName = "";
query = query ?? Util.DEFAULT_SSOM_QUERY;
if (objectType == typeof(SPProject)) listName = Util.PROJECTS_LIST_NAME;
else if (objectType == typeof(SPTask)) listName = Util.TASKS_LIST_NAME;
else throw new Exception(String.Format("Could not find the list name for {0} objects.", objectType.Name));
List<SPListItem> results = Util.GetSPItemCollectionWithHistory(listName, query).Cast<SPListItem>().ToList();
Parallel.ForEach(results, item => resultsList.Add(new SPObjectWithHistory<T>(item, filters)));
return resultsList.ToList();
}
Однако, когда я сейчас пытаюсь запустить приложение, я получаю следующее исключение в Parallel.ForEach
:
Сообщение: Произошла одна или несколько ошибок.
Тип: System.AggregateException
StackTrace:
в System.Threading.Tasks.Task.ThrowIfExceptional (логическое includeTaskCanceledExceptions)
в System.Threading.Tasks.Task.Wait (Int32 millisecondsTimeout, CancellationToken cancellationToken)
в System.Threading.Tasks.Parallel.ForWorker [TLocal] (Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action'1 body, Action'2 bodyWithState, Func'4 bodyWithLocal, Func'1 localInit, Action'1 localFinally)
в System.Threading.Tasks.Parallel.ForEachWorker [TSource, TLocal] (источник IEnumerable'1, ParallelOptions parallelOptions, Action'1 body, Action'2 bodyWithState, Action'3 bodyWithStateAndIndex, Func'4 bodyWithStateAthingLocal, Func'5 bodyWithE '1 localInit, Action' 1 localFinally)
в System.Threading.Tasks.Parallel.ForEach [TSource] (источник IEnumerable'1, тело Action'1)
в GetSPObjectsWithHistory (запрос SPQuery, фильтры List`1) в ...
InnerException:
Сообщение: Попытка выполнить вызовы более чем одного потока в однопоточном режиме. (Исключение из HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))
Тип: Microsoft.SharePoint.SPException
StackTrace:
в Microsoft.SharePoint.SPGlobal.HandleComException (COMException comEx)
в Microsoft.SharePoint.Library.SPRequest.SetVar (String bstrUrl, String bstrName, String bstrValue)
в Microsoft.SharePoint.SPListItemVersionCollection.EnsureVersionsData ()
в Microsoft.SharePoint.SPListItemVersionCollection.get_Item (Int32 iIndex)
в строке
double.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion);
в конструктореSPObjectWithHistory
.InnerException:
Сообщение: Попытка выполнить вызовы более чем одного потока в однопоточном режиме. (Исключение из HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))
Тип: System.Runtime.InteropServices.COMException
StackTrace:
в Microsoft.SharePoint.Library.SPRequestInternalClass.SetVar (String bstrUrl, String bstrName, String bstrValue)
в Microsoft.SharePoint.Library.SPRequest.SetVar (String bstrUrl, String bstrName, String bstrValue)
Будет ли кто-нибудь, кто знает, как заставить мой код работать?
Заранее спасибо!
Parallel.ForEach
, поскольку приложение выполняло быresultsList.Add(new SPObjectWithHistory<T>(item, filters));
(+ -0,3 секунды) несколько раз одновременно. В коллекции из 80000 предметов это окажет большое влияние. Даже выполнение этого в двух параллельных потоках вместо одного имело бы огромное значение. - person DylanVB   schedule 15.01.2018