Разработка
28
Фев
4

Fetch-запросы из плагинов… и 5000 записей

Простой Fetch-запрос

Саымый простой пример Fetch-запроса на языке C# выглядит так:

  • Создайте плагин, подключите SDK сборки и System.Web.Services;
  • Повесьте на него такой код:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    using System.Xml;
    
    public class FetchXML : IPlugin
    {
        // При выполнении плагина...
        public void Execute(IPluginExecutionContext context)
        {
            // Настраиваем CRM Service через контекст плагина
            ICrmService crmService = context.CreateCrmService(true);
    
            // Задаем строку с Fetch-запросом
            string fetchXml = "<fetch mapping='logical'><entity name='account'><attribute name='accountid'/></entity></fetch>";
    
            // Выполняем Fetch-запрос
            string result = crmService.Fetch(fetchXml);
    
            // Создаем новый XML документ и подсовываем ему результат Fetch-запроса
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(result);
    
            // Отбираем из XML документа узлы result и просматриваем их все
            XmlNodeList xmlNodes = xmlDoc.GetElementsByTagName("result");
            foreach (XmlNode item in xmlNodes)
            {
                // Проверяем что accountid заполнен
                if (item["accountid"] != null)
                {
                    // Выводим в сообщении accountid
                    throw new Exception(item["accountid"].InnerText);
                }
            }
        }
    }
    
  • Зарегистрируйте на любое событие и вызовите его в CRM.


P.S. Составить Fetch-запрос Вы сможите с помощью Расширенного поиска, FetchXML Wizard’а и FetchXML Builder’а.

Ну, и как альтернативу Fetch-запросу приведу равнозначный (по результату) пример запроса с помощью QueryExpression:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk.Query; 

public class QueryEx : IPlugin
{    
    // При выполнении плагина...
    public void Execute(IPluginExecutionContext context)
    {
        // Настраиваем CRM Service через контекст плагина
        ICrmService crmService = context.CreateCrmService(true);

        // Составляем запрос, чтобы вытащить accountid всех бизнес-партнеров
        QueryExpression query = new QueryExpression();
        query.EntityName = "account";
        query.ColumnSet = new ColumnSet(new String[] { "accountid" });

        // Формируем объект запроса
        RetrieveMultipleRequest req = new RetrieveMultipleRequest();
        req.Query = query;

        // Отправляем запрос в CRM Service
        RetrieveMultipleResponse response = (RetrieveMultipleResponse)crmService.Execute(req);

        // Просматриваем каждую возвращенную запись и выводим в сообщении ее accountid
        foreach (account acct in response.BusinessEntityCollection.BusinessEntities)
        {
            throw new Exception(acct.accountid.Value.ToString());
        }
    }
}

5000 записей

У выше приведенного кода есть один существенный недостаток! CRM сервер на Fetch-запрос возвращает максимум 5000 записей за один раз!

Тут есть два решения:

  1. Вы должны добавить DWORD-ключ TurnOffFetchThrottling в реестр в ветке HKLM\Software\Microsoft\MSCRM. Если установить значение этого ключа в 1 (и перезапустить IIS), то у Вас не будет лимита в 5000 записей. Но соответственно, это скажется на производительности, так что применять этот метод следует с осторожностью;


  1. Изменить код Fetch-запроса так, чтобы он «постранично» (при том, что на одну «страницу» приходится максимум 5000 записей) обработал все записи и вернул результат полной выборки.

Расмотрим как реализовать второй вариант…

  • Создайте плагин и повесьте на него такой код:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    using System.Xml;
    using System.Data;
    
    public class FetchXML : IPlugin
    {
        // Создаем переменную сервиса
        ICrmService crmService;
        
        // При выполнении плагина...
        public void Execute(IPluginExecutionContext context)
        {
            // Настраиваем CRM Service через контекст плагина
            crmService = context.CreateCrmService(true);
    
            // Задаем строку с Fetch-запросом
            string FetchXml = "<fetch mapping='logical'><entity name='account'><attribute name='accountid'/></entity></fetch>";
    
            // Вызываем функцию fetchAll и передаем ей строку Fetch-запроса
            // Возвращенный результат помещаем в XML объект
            XmlDocument FetchXmlResult = new XmlDocument();
            // Загружаем строку Fetch-запроса в XML документ
            FetchXmlResult.LoadXml(fetchAll(FetchXml));
    
            // Просматриваем XML документ и выводим в сообщении значение поля accountid
            foreach (XmlNode node in FetchXmlResult.DocumentElement.ChildNodes)
            {
                string attributevalue = node.SelectSingleNode("accountid").InnerText;
                throw new Exception(attributevalue);
            }
    
        }
    
        // Функция возвращает все записи Fetch-запроса
        private string fetchAll(string sFetchXml)
        {
            // Переменная опеределяющая необходимость продолжать цикл
            bool bComplete = false;
            // Начинаем просмотр с первой страницы
            int iPage = 1;
            // Общий массив, в коториый будем помещать результаты временный выборок
            XmlDocument oResults = new XmlDocument();
    
            // Повторяем постраничную выборку пока bComplete равен false
            while (!bComplete)
            {
                // XML документ для "разбора" строки Fetch-запроса
                XmlDocument oFetchXml = new XmlDocument();
                // Загружаем строку Fetch-запроса в XML документ
                oFetchXml.LoadXml(sFetchXml);
                // Находим в XML документе узел fetch
                XmlNode oFetchNode = oFetchXml.SelectSingleNode("/fetch");
    
                // Если в узле fetch нет атрибута, то создаем его
                // Но в любом случаи помещаем в этот атрибут номер текуще страницы (итерации)
                if (oFetchNode.Attributes["page"] == null)
                {
                    XmlAttribute oPageAttribute = oFetchXml.CreateAttribute("page");
                    oPageAttribute.Value = iPage.ToString();
                    oFetchNode.Attributes.Append(oPageAttribute);
                }
                else
                {
                    oFetchNode.Attributes["page"].Value = iPage.ToString();
                }
    
                // Выполняем Fetch-запрос
                string sResultsXml = crmService.Fetch(oFetchXml.InnerXml);
    
                // Создаем временный массив, в который мы помещаем до 5000 записей за одну итерацию
                XmlDocument oTempXml = new XmlDocument();
                oTempXml.LoadXml(sResultsXml);
    
                // Затем мы добавляем результаты временной выборки в собирательный XML объект oResults
                if (iPage == 1)
                {
                    oResults.LoadXml(oTempXml.InnerXml);
                }
                else
                {
                    XmlNodeList oNodes = oTempXml.SelectNodes("/resultset/result");
                    XmlNode oResultsetNode = oResults.SelectSingleNode("/resultset");
                    foreach (XmlNode oNode in oNodes)
                    {
                        XmlNode oNewNode = oResults.ImportNode(oNode, true);
                        oResultsetNode.AppendChild(oNewNode);
                    }
                }
    
                // Теперь мы должны выяснить, есть ли еще страницы для выборки
                // Для этого мы проверяем атрибут morerecords узла resultset
                // Если таке страницы есть, мы только инкриминипуем iPage, иначе устанавливаем bComplete в true, чем прерываем цикл 
                XmlNode oMore = oTempXml.SelectSingleNode("/resultset");
                if (oMore.Attributes["morerecords"].Value == "0")
                {
                    bComplete = true;
                }
                else
                {
                    iPage++;
                }
            }
    
            // Возвращаем содержимое собирательного массива oResults
            return oResults.InnerXml;
        }
    }
    

    Этот код делает следующие:

    • При вызове плгаина объявляется строка Fetch-запроса и происходит вызов функции fetchAll с передачей ей этой строки в качестве параметра;
    • fetchAll «постранично» (максимум по 5000 записей на страницу) выполняет Fetch-запрос и скаладывает результат каждого промежуточного Fetch-запроса в собирательную переменную;
    • Когда все «страницы» будут просмотрены общий результат будет возвращен в контекст выполнения плагина, где с ним можно провести дальнейшую обработку.
  • Зарегистрируйте плагин в CRM и вызовите событие, на которое он был зарегистрирован.


Комментарии (4)
  • Дмитрий 28.02.2010

    Добрый день!

    подскажите как можно выполнить фетч запрос из жава скрипта. к примеру я хочу из формы заказ выбрать в скрипте все продукты, посчитать их количество и сохранить количество в поле количество продуктов заказа.

    Спасибо

  • slivka_83 28.02.2010
  • Дмитрий 28.02.2010

    просто волшебно!
    большое спасибо!
    можно еще вопросик:
    у меня на форме есть поле со статусом «только для чтения». значение поля формируется автоматически (при помощи запроса фетч), однако после сохранения формы это поле остается пустым. я так понимаю что стандарт механизм сохранения не сохраняет поля для чтения. Каким образом я могу самостоятельно сохранить значение этого поля?

    С Уважением,

  • slivka_83 28.02.2010

    Вам нужно выполнить такой код

    crmForm.all.{Field}.ForceSubmit;

    чтобы отправить на сервер новое значение поля, которое с посощью настроек переведено в состояние «только для чтения»

*

code