Массовое удаление записей в CRM
Если Вам приходилось когда-либо удалять записи в огромных количествах… ну, например, под 100 000 🙂 То Вы должны быть значкомы с утилитой Bulk Delete Launcher. Утилита вобщем то хорошая, но помоему излишняя, т.к. для решения это задачи вполне можно обойтись и веб-компонентами!
Итак, что нам требуется: удалить все записи представления по нажатию кнопки на панели инструментов этого представления.
В чем заключается решение? Оно состоит из двух компонентов:
- Кнопки над представлением, которая при нажатии будет выцеплять из представления его GUID и отравлять кастомной ASPX-страницы. Которая в свою очередь будет возвращать GUID созданной операции массового удаления записей. И в последнюю очередь, будем открывать карточку записи массового удаления записей;
- Кастомной ASPX-странички, которая будет получать GUID представления. C помощью этого GUID’а она будет вытаскивать Fetch-запрос соответствующий этому представленияю и на основе его создавать новую операцию группового удаления записей. После чего возвращать GUID этой операции.
Приступим:
ASPX-страничка
- Создайте в Visual Studio новую ASPX-страницу;
- Переименуте default.aspx в bulkdelete.aspx и удалитеиз нее весь контент кроме первой строчки:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="bulkdelete.aspx.cs" Inherits="_Default" %>
- Подключите к странице web-сервис CRM (http://<servername:port>/mscrmservices/2007/crmservice.asmx,с именем CrmSdk);
- Добавьте на страницу bulkdelete.aspx такой код:
using System; using System.Collections.Generic; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using CrmSdk; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { try { // Получаем GUID Электронной почты из строки запроса if (Request.QueryString["viewid"] == null) return; string viewidId = Request.QueryString["viewid"].Replace("{", "").Replace("}", ""); // Конфигурируем CrmService CrmAuthenticationToken token = new CrmAuthenticationToken(); token.AuthenticationType = 0; token.OrganizationName = "superfirma"; CrmService service = new CrmService(); service.Url = "http://win-n22hj23d1b1/mscrmservices/2007/crmservice.asmx"; service.CrmAuthenticationTokenValue = token; service.Credentials = System.Net.CredentialCache.DefaultCredentials; // Вытаскиваем fetch-запрос представления ColumnSet cols = new ColumnSet(); cols.Attributes = new string[] { "fetchxml", "name" }; Guid savedqueryGuid = new Guid(viewidId); savedquery savedquery = (savedquery)service.Retrieve("savedquery", savedqueryGuid, cols); // Конвертируем fetch в QueryExpression FetchXmlToQueryExpressionRequest fetch = new FetchXmlToQueryExpressionRequest(); fetch.FetchXml = savedquery.fetchxml; FetchXmlToQueryExpressionResponse qe = (FetchXmlToQueryExpressionResponse)service.Execute(fetch); // Создаем операцию по массовому удалению записи Guid[] emptyRecipients = new Guid[0]; BulkDeleteRequest request = new BulkDeleteRequest(); request.JobName = "Удаление всех записей из представления \"" + savedquery.name + "\""; request.QuerySet = new QueryBase[] { qe.Query }; request.ToRecipients = emptyRecipients; request.CCRecipients = emptyRecipients; request.SendEmailNotification = false; request.RecurrencePattern = string.Empty; CrmDateTime crmdateTime = new CrmDateTime(); DateTime dateTime = DateTime.Now; crmdateTime.Value = dateTime.ToString(string.Format("yyyy-MM-ddTHH:mm:ss")); request.StartDateTime = crmdateTime; BulkDeleteResponse response = (BulkDeleteResponse)service.Execute(request); // Запрашиваем в CRM GUID операции массовго удаления записей QueryByAttribute bulkQuery = new QueryByAttribute(); bulkQuery.ColumnSet = new AllColumns(); bulkQuery.EntityName = EntityName.bulkdeleteoperation.ToString(); bulkQuery.Attributes = new string[] { "asyncoperationid" }; bulkQuery.Values = new object[1]; bulkQuery.Values[0] = response.JobId; BusinessEntityCollection bulkEntityCollection = service.RetrieveMultiple(bulkQuery); // Смотрим на возвращенный результат: // Если он равен нулю (опреация осинхронная и запись создается не сразу), то ждем секунду и повторяем запрос (и так до 60 раз) // Если запрос содержит данные вытаскиваем их в объект bulkdeleteoperation bulkdeleteoperation bdo = null; const int ARBITRARY_MAX_POLLING_TIME = 60; int secondsTicker = ARBITRARY_MAX_POLLING_TIME; while (secondsTicker > 0) { if (bulkEntityCollection.BusinessEntities.Length > 0) { bdo = (bulkdeleteoperation)bulkEntityCollection.BusinessEntities[0]; // Вытаскиваем запись из массива secondsTicker = 0; // Обнуляем счетчик } else { System.Threading.Thread.Sleep(1000); // Ждем секунду secondsTicker--; // Уменьшаем счетчик bulkEntityCollection = service.RetrieveMultiple(bulkQuery); // Повторяем запрос } } // Возвращаем GUID записи групового удаления данных Response.Write(bdo.bulkdeleteoperationid.Value.ToString()); } catch { // В случаи какой-либо ошибки возвращаем кодовое слово "Ошибка" Response.Write("Ошибка"); } } }
Этот код делает следующее:
- Вытаскивает из строки запроса GUID представления;
- Подключается к CRM-сервису (соответственно, поменяйте настройки на Ваши);
- Вытаскиваем из объекта savedquery Fetch-запрос представления (GUID объекта savedquery равен GUID’у отображаемого представления);
- Конвертируем полученный Fetch-запрос в QueryExpression, т.к. именно этот тип выборки понимает операция групового удаления записей;
- Создаем новую операцию массового удаления записей. Обратите внимаение, что в этом примере я не рассылаю уведомления о завершении ее работы, но Вы можете это изменить, если Вам требуется. Также обратите внимание, что я не задал цикличность повторения этой операции. делается это примерно так:
request.RecurrencePattern = "FREQ=DAILY;INTERVAL=3;";
В даннном случаи задается интервал повторения «каждые три дня». Более подробно об этом смотрите на MSDN: http://msdn.microsoft.com/en-us/library/cc189846.aspx
- Далее запрашиваем в CRM GUID созданной операции группового удаления данных. Т.к. эта операция асинхронная, то она создастся не сразу, поэтому требуется выполнить несколько запросов с некоторым интервалом (в данном случаи в 1 секунду), пока она не будет создана;
- Возвращаем полученый GUID (если же произошла ошибка, то возвращаем ключевое слово «Ошибка»);
- В web.config поместите «стандартный» код:
<?xml version="1.0"?> <configuration> <appSettings> <add key="CrmSdk.CrmServiceWsdl" value="http://win-n22hj23d1b1/MSCrmServices/2007/CrmService.asmx"/> </appSettings> <connectionStrings/> <system.web> <httpModules> <add name="MapOrg" type="Microsoft.Crm.MapOrgEngine, Microsoft.Crm, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="CrmAuthentication" type="Microsoft.Crm.Authentication.AuthenticationEngine, Microsoft.Crm, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </httpModules> <identity impersonate="true"/> <compilation debug="true"> <assemblies> <add assembly="Microsoft.Crm.Sdk, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="Microsoft.Crm.SdkTypeProxy, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> </assemblies> </compilation> </system.web> </configuration>
- Опубликуйте страницу в папке ISV\bulkdelete (предварительно создав ее) сайта CRM;
ISV.Config
- Выгрузите из CRM ISV.Config и откройте его в каком-нибудь текстовом редакторе;
- Добавьте на панель инструментов нужного Вам представления (в данном случаи это Контакт) кнопку, содержащую такой JS-код:
<Grid> <MenuBar> <Buttons> <Button Icon="/_imgs/ico_16_5010.gif" JavaScript=" // Получаем GUID представления var viewid = document.getElementById('viewid').getAttribute('value'); // Отправляем асинхронный запрос в страницу bulkdelete.aspx и передаем ей GUID текущего представления var forwardPath = '/isv/bulkdelete/bulkdelete.aspx?viewid=' + viewid; var objXMLHTTP = new ActiveXObject('Microsoft.XMLHTTP'); objXMLHTTP.Open('POST',forwardPath,false); objXMLHTTP.Send(''); // Получаем ответ var response = objXMLHTTP.ResponseText; // Если ответ не пустой и не равен 'Ошибка', то... if (response != null && response.length != 0 && response != 'Ошибка') { // Открываем форму оюъекта Массового удаления записей var width = 900; var height = 600; var left = (screen.width - width)/2; var top = (screen.height - height)/2; var params = 'width='+width+', height='+height; params += ', top='+top+', left='+left; params += ', directories=no'; params += ', location=no'; params += ', menubar=no'; params += ', resizable=yes'; params += ', scrollbars=no'; params += ', status=yes'; params += ', toolbar=no'; window.open('http://win-n22hj23d1b1/superfirma/tools/bulkdelete/edit.aspx?id={' + response + '}' ,'', params); } else { alert('Ошибка'); } "> <Titles> <Title LCID="1049" Text="Удалить все" /> </Titles> <ToolTips> <ToolTip LCID="1049" Text="Удалить все" /> </ToolTips> </Button> </Buttons> </MenuBar> </Grid>
- Код на кнопке выполняет следующие действия:
- Выцепляет из текущего представления его GUID;
- На основе него формируется запрос к странице /isv/bulkdelete/bulkdelete.aspx (GUID передается в строке запроса URL);
- Смотрите полученный ответ: если это не «Ошибка», то открывается форма записи операции группового удаления записей, на которой Вы можете просмотреть ее результат. Если же было возвращено ключевое слово «Ошибка», то юзверу выдается алерт;
- Импортируйте ISV.Config обратно в CRM.
Тестирование
Откройте Ваше представление, на которое Вы поместили кнопку и которое содержит какие-либо записи (ненужнеы, разумеется 🙂 ) и нажмити на кнопку «Удалить все». Все, смотрим результат (в открывшейся карточке группового удаления записей, Вы, возможно, увидите что операция еще не завершилась, поэтому обновите ее – F5) 🙂
З.Ы. Было бы весьма неплохо, если бы Вы оградили юзверово от доступа к этой кнопке 🙂