суббота, апреля 30, 2011

Удаление элементов из SPList через ProcessBatchData

Всем разработчикам 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;
        }
    }
}

Комментариев нет:

Отправить комментарий