Всем разработчикам Sharepoint 2010 (да и вообще любой версии Sharepoint или MOSS) известна ситуация с медленной работой объектной модели и большом времени поиска\модификации через нее.
Так вот мне однажды пришлось писать систему массового удаления данных из разных списков сайта под управлением Sharepoint 2010. Решения через
SPListItem.Delete(),
SPList.Items.DeleteItem(idx) и даже
SPList.Items.DeleteItemById(itemId) давали жуткие показатели времени работы (удаление 100 элементов за
60000(!)-120000(!) секунд). И тут я вспомнил про волшебную возможность
SPWeb.ProcessBatchData которая позволяет выполнять CAML запросы напрямую, минуя слой объектной модели.( Забегая вперед скажу что такое решение мне дало производительность в районе ~650сек на 5000 записей.)
Что-ж покопав документацию на сайте Microsoft (
http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.processbatchdata.aspx) я подумал что все тривиально и создал свое первое решение. Радостно посапывая я ткнул в кнопочку деплой и запустил свой код. Как ни странно код рухнул. Не буду описывать процесс поиска ошибки и сразу скажу:
Разбивайте свои запросы на пачки! Не больше 180 запросов в одном теге . Лично
я разбиваю свои запросы в пачки по 100.
Поправив код и сделав разбиение на пачки я запустил все снова. Как и ожидалось пепелац не взлетел. А я поимел совершенно смутившую меня ошибку
ArgumentException. Тут надо отметить удивительную абсолютную невразумительности сообщений Sharepoint об ошибках.
Вот к примеру как выглядел мой Expcetion
Exception type: ArgumentException
[Message] = "0x80070057"
[ParamName] = ""
[Data] = "System.Collections.ListDictionaryInternal"
[TargetSite] = "Void GetListItemDataWithCallback2(Microsoft.SharePoint.Library.IListItemSqlClient, System.String, System.String, System.String, System.String, Microsoft.SharePoint.Library.SAFEARRAYFLAGS, Microsoft.SharePoint.Library.ISP2DSafeArrayWriter, Microsoft.SharePoint.Library.ISPDataCallback, Microsoft.SharePoint.Library.ISPDataCallback, Microsoft.SharePoint.Library.ISPDataCallback, Microsoft.SharePoint.Library.ISPDataCallback, Microsoft.SharePoint.Library.ISPDataCallback, Boolean ByRef)"
[StackTrace] = " в Microsoft.SharePoint.Library.SPRequestInternalClass.GetListItemDataWithCallback2(IListItemSqlClient pSqlClient, String bstrUrl, String bstrListName, String bstrViewName, String bstrViewXml, SAFEARRAYFLAGS fSafeArrayFlags, ISP2DSafeArrayWriter pSACallback, ISPDataCallback pPagingCallback, ISPDataCallback pPagingPrevCallback, ISPDataCallback pFilterLinkCallback, ISPDataCallback pSchemaCallback, ISPDataCallback pRowCountCallback, Boolean& pbMaximalView)
в Microsoft.SharePoint.Library.SPRequest.GetListItemDataWithCallback2(IListItemSqlClient pSqlClient, String bstrUrl, String bstrListName, String bstrViewName, String bstrViewXml, SAFEARRAYFLAGS fSafeArrayFlags, ISP2DSafeArrayWriter pSACallback, ISPDataCallback pPagingCallback, ISPDataCallback pPagingPrevCallback, ISPDataCallback pFilterLinkCallback, ISPDataCallback pSchemaCallback, ISPDataCallback pRowCountCallback, Boolean& pbMaximalView)
в Microsoft.SharePoint.SPListItemCollection.EnsureListItemsData()
в Microsoft.SharePoint.SPListItemCollection.GetEnumerator()
в MRR.DVIZ.Workflows.ClearFormsWorkflow.ClearFormsWorkflow.FindRunning(Object sender, EventArgs e)
в System.Workflow.ComponentModel.Activity.RaiseEvent(DependencyProperty dependencyEvent, Object sender, EventArgs e)
в System.Workflow.Activities.CodeActivity.Execute(ActivityExecutionContext executionContext)
в System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext)
в System.Workflow.ComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext)
в System.Workflow.ComponentModel.ActivityExecutorOperation.Run(IWorkflowCoreRuntime workflowCoreRuntime)
в System.Workflow.Runtime.Scheduler.Run()"
[HelpLink] = ""
[Source] = ""
Поиски google ничего вразумительного не дали. Правда у меня зародилось смутное сомнение в том что я что-то не так делаю с форматом CAML запроса. Я даже не могу сказать что мена натолкнуло на эту мысль, интуиция если только.
Не буду рассказывать свои танцы вокруг подбора нового формата. скажу лишь что:
<Batch OnError="Continue">
<SetList>b15da36c-6ed8-4fe1-bbcc-89a1996c1dbe</SetList>
<Method ID="1" Cmd="Delete"><Field Name="ID">638</Field></Method>
<Method ID="2" Cmd="Delete"><Field Name="ID">639</Field></Method>
<Method ID="3" Cmd="Delete"><Field Name="ID">648</Field></Method>
<Method ID="4" Cmd="Delete"><Field Name="ID">649</Field></Method>
<Method ID="5" Cmd="Delete"><Field Name="ID">650</Field></Method>
<Method ID="6" Cmd="Delete"><Field Name="ID">651</Field></Method>
<Method ID="7" Cmd="Delete"><Field Name="ID">652</Field></Method>
<Method ID="8" Cmd="Delete"><Field Name="ID">653</Field></Method>
<Method ID="9" Cmd="Delete"><Field Name="ID">654</Field></Method>
<Method ID="10" Cmd="Delete"><Field Name="ID">655</Field></Method>
<Method ID="11" Cmd="Delete"><Field Name="ID">656</Field></Method>
<Method ID="12" Cmd="Delete"><Field Name="ID">657</Field></Method>
</Batch>
не красиво но не правильно. Надо:
<?xml version="1.0" encoding="UTF-8"?>
<Batch OnError="Return">
<Method ID="1,Del"><SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">20</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="2,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">21</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="3,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">22</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="4,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">23</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="5,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">24</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="6,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">25</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="7,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">26</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="8,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">27</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="9,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">28</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="10,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">29</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="11,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">30</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="12,Del"> <SetList
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar
Name="ID">31</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
</Batch>
Код создающий такую структуру и выполняющие его в пачках (listMods - IEnumerable
с ИД строк для уделания):
List<string> listBatch = new List();
StringBuilder batch = new StringBuilder();
StringBuilder log = new StringBuilder();
if (!string.IsNullOrEmpty(listTitle) && listMods != null && listMods.Count > 0)
{
log.AppendFormat("Очистка списка {0}. Удалеине {1} элементов.", listTitle, listMods.Count);
batch.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
batch.AppendLine("<Batch OnError=\"Return\">");
int batchSize = 0;
foreach (int toDelete in listMods)
{
batchSize++;
batch.AppendFormat(
"<Method ID=\"{0},Del\"> {2} <SetVar Name=\"ID\">{1}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar></Method>",
batchSize, toDelete,
string.Format("<SetList Scope=\"Request\">{0}</SetList>",
queryRoot.Lists[listTitle].ID))
.AppendLine();
if (batchSize >= 100)
{
batch.AppendLine("</Batch>");
listBatch.Add(batch.ToString());
batchSize = 0;
batch = new StringBuilder(batch.Capacity);
batch.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
batch.AppendLine("<Batch OnError=\"Return\">");
}
}
batch.AppendLine("</Batch>");
listBatch.Add(batch.ToString());
foreach (string cmd in listBatch)
{
try
{
log.Append("Исполнение запроса :").AppendLine().Append(cmd).AppendLine();
string cmd1 = cmd;
SPSite siteColl = currentSite;
SPWeb site = currentWeb;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (
SPSite ElevatedsiteColl =
new SPSite(siteColl.ID))
{
using (
SPWeb ElevatedWeb =
ElevatedsiteColl.OpenWeb(
site.ID))
{
ElevatedWeb.ProcessBatchData(cmd1);
}
}
});
}
catch (Exception e)
{
var ex = new ApplicationException("Ошибка при исполнении комманды: " + cmd, e);
throw ex;
}
}
}