Разработка
14
Июн
14

LINQ и CRM

Если Вы еще не слышали, то знайте: вышла новая версия MS CRM SDK (12-ая), которая помимо прочего (очень кстати полезных вещей) содержит новый LINQ-провайдер (который позволяет строить LINQ-запросы) для работы с MS CRM. LINQ это SQL-подобный дотнетовский язык запросов, который в значительной степени может облегчить Вам жизнь (если научиться им пользоваться 🙂 ). А как им пользоваться и в чем его преимущество, Вы сейчас увидите – создадим простенький плагин для MS CRM с использованием этого самого LINQ’а:

  • Качаем и устанавливаем новый MS CRM SDK 4.0.12;
  • Этот SDK включает в себя новые dll-сборки, которые нам понадобятся для LINQ-запросов, и которые мы будем использовать в коде. А чтобы при регистрации и во время работы плагина, система смогла их найти и «подцепить», необходимо зарегистрировать их в GAC’е. Для этого существует как ручной способ так и визуальный. А т.к. я ленив по природе 🙂 проведем регистрацию визуальным способом:
    • Скачайте и установите Microsoft .NET Framework 2.0 Configuration Tool;
    • После чего в винде появится новая оснастка для управления GAC’ом. Откройте ее, перейдите Start – Control Panel – Administrative Tools – Microsoft .NET Framework 2.0 Configuration;
    • Далее щелкните правой кнопкой мыши по Assembly Cache, выберите Add… и добавьте три сборки из папки sdk\microsoft.xrm\bin:
      • microsoft.xrm.client.dll
      • microsoft.xrm.portal.dll
      • microsoft.xrm.portal.files.dll



  • Далее нам нужно сгенерировать метаданные необходимые для работы LINQ-провайдера (которые также будет использоваться в Visual Studio как IntelliSense). В этом нам поможет консольная утилита, поставляемая вместе с новым SDK:
    • Запустите CMD (консольную программу-интерпритатор; для этого просто перейдите Start — Run — CMD);
    • В CMD запустите утилитку crmsvcutil.exe из папки sdk\microsoft.xrm\tools с таким набором команд:
      crmsvcutil /server:http://<crm_server>/<org> /out:xrm.cs

      В результате на диске C сформируется файл xrm.cs, который мы затем используем в проекте плагина.
      З.Ы. у этой утилитки есть также и другие команды, которые Вы можете посмотреть выполнив команду:

      crmsvcutil /help


  • Создайте в Visual Studio новый плагин (подпишите его) и подцепите к нему следующие сборки:
    • Стандартные:
      • microsoft.crm.sdk.dll
      • microsoft.crm.sdktypeproxy.dll
    • Новенькие 🙂 из папки sdk\microsoft.xrm\bin:
      • microsoft.crm.sdk.dll
      • microsoft.xrm.client.dll
      • microsoft.xrm.portal.files.dll
    • И из .Net Framaeworks’а (вкладка .Net):
      • System.Data.Services
      • System.Data.Services.Client
  • Для двух последних dll-ок обязательно понадобится .Net Framework не ниже версии 3.5 SP1 (в прежних версиях они просто отсутствуют)!
  • Далее нужно подключить в проект созданный ранее файл xrm.cs. Для этого щелкните правой кнопкой мыши по заголовку проекта — Add — Existing Item… — и выберите файл xrm.cs;
  • Теперь добавьте в плагин такой код:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Entities;
    using Microsoft.Xrm.Client;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    
    namespace ClassLibrary3
    {
        public class LinqTest : IPlugin
        {
            public void Execute(IPluginExecutionContext context)
            {
                // Подключаем контекс CRM
                var crm = new DataContext(CrmConnection.Parse("Authentication Type=Integrated; Server=http://win-n22hj23d1b1/superfirma"));
                
                // Формируем LINQ-запрос
                var query = from acct in crm.accounts
                            where acct.name.Equals("Gazprom")
                            select acct;
    
                string alert = "";
    
                // Просматриваем данные полученные из LINQ-запроса и вытаскиваем нужные поля
                if (query != null)
                    foreach (var acct in query)
                        alert += acct.telephone1 + ", ";
    
                // Выводим в сообщении номера телефонов всех Бизнес-партнеров с названием Gazprom
                throw new Exception(alert);
                // Очищаем кэш 
                var cache = Microsoft.Xrm.Client.Caching.CacheManager.GetBaseCache();
                cache.Remove("adxdependency:crm:entity:account");
            }
        }
    }
    
  • Ну, на этом все: собираем плагин, регистрируем в CRM и вызываем событие на которое его зарегистрировали! 🙂




Спрашиваете в чем же преимущество? 🙂 А преимущество в сравнении… с QueryExpression и Fetch-запросами! Вот два кода созданные с помощью вышеупомянутых методов и выполняющих абсолютно аналогичный запрос:

QueryExpression

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

namespace ClassLibrary4
{
    public class QE : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            ICrmService service = context.CreateCrmService(true);

			// Создаем объект QueryExpression
			QueryExpression query = new QueryExpression();
			query.EntityName = EntityName.account.ToString();

			// Указываем какие поля необходимо вернуть
			ColumnSet cols = new ColumnSet();
			cols.AddColumn("telephone1");
			query.ColumnSet = cols;

			// Добавляем фильтр
			ConditionExpression condition = new ConditionExpression();
			condition.AttributeName = "name";
			condition.Operator = ConditionOperator.Equal;
			condition.Values = new string[] { "Gazprom" };

			FilterExpression filter = new FilterExpression();
			filter.FilterOperator = LogicalOperator.And;
			filter.Conditions.Add(condition);
			query.Criteria = filter;

			// Выполняем запрос
			RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
			retrieve.Query = query;
                
			RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)service.Execute(retrieve);
			BusinessEntityCollection accounts = retrieved.BusinessEntityCollection;
 
			// Просматриваем полученный результат
			string alert = null;
			foreach (account account in accounts.BusinessEntities)
			{
				alert += account.telephone1.ToString() + ", ";
			}

			// Выводим сообщение
			throw new Exception(alert);
        }
    }
}

Fetch

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

namespace ClassLibrary5
{
    public class Fetch : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            ICrmService service = context.CreateCrmService(true);

            // Составляем Fetch-запрос
            string fetch = @"
                <fetch mapping='logical'>
                    <entity name='account'>
                        <attribute name='telephone1'/>
                        <filter type='and'>
                            <condition attribute='name' operator='eq' value='Gazprom' />
                        </filter>
                    </entity>
                </fetch>
            ";

            // Отправляем Fetch-запрос в CRM
            String result = service.Fetch(fetch);

            // Создаем новый XML документ и подсовываем ему результат Fetch-запроса
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(result);

            string alert = null;
            // Отбираем из XML документа узлы result и просматриваем их все
            XmlNodeList xmlNodes = xmlDoc.GetElementsByTagName("result");
            foreach (XmlNode item in xmlNodes)
            {
                // Проверяем что accountid заполнен
                if (item["telephone1"] != null)
                {
                    // Выводим в сообщении accountid
                    alert += item["telephone1"].InnerText + ", ";
                }
            }

            // Выводим в сообщении accountid
            throw new Exception(alert);
        }
    }
}

Как видите читабельность и простота использования LINQ-запросов превосходит на порядок другие методы! 🙂

P.S. Больше о LINQ’е Вы сможете больше узнать из статей на MSDN: http://msdn.microsoft.com/ru-ru/library/bb397933(v=VS.90).aspx, а также о конкретной его реализации в CRM’ном провайдере из справки поставляемой вместе с SDK (SDK\microsoft.xrm\microsoftxrm.chm).

Комментарии (14)
  • DZ 14.06.2010

    А для кастомных сущностей Linq-запросы работают?

  • DZ 14.06.2010

    Нашел ответ на свой вопрос 🙂 работают.

  • slivka_83 14.06.2010

    Угу 🙂 при формировании cs-файллика с помощью crmsvcutil мы как раз и задаем объекты, которые хотим использовать 🙂

  • Andrey 14.06.2010

    хм, у меня при построении проекта выдает ошибки в файле xrm.cs, ошибки однотипные:
    The type ‘Entities.salesorder’ already contains a definition for ‘new_account_salesorder’
    Вот сгенерированный фрагмент код:

    ///
    /// Уникальный идентификатор для объекта Организация, связанного с объектом Заказ. (Attribute for new_account_salesorder)
    ///
    [global::Microsoft.Xrm.Client.Linq.CrmProperty(«new_account_salesorder»)]
    public global::System.Guid? new_account_salesorder
    {
    get { return this.GetPropertyValue(«new_account_salesorder»); }
    set { this.SetPropertyValue(«new_account_salesorder», value, typeof(global::Microsoft.Crm.Sdk.Lookup), «account»); }
    }

    ///
    /// Уникальный идентификатор для объекта Организация, связанного с объектом Заказ. (N:1 Association for new_account_salesorder)
    ///
    [global::Microsoft.Xrm.Client.Linq.CrmNamed(«new_account_salesorder»)]
    [global::Microsoft.Xrm.Client.Linq.CrmAssociation(«new_account_salesorder»)]
    public global::Entities.account new_account_salesorder
    {
    get { return this.GetRelatedEntity(«new_account_salesorder», «accountid»); }
    set { this.SetRelatedEntity(«new_account_salesorder», value, typeof(global::Microsoft.Crm.Sdk.Lookup)); }
    }

    Второе new_account_salesorder он подчеркивает!
    В трех местах такие же ошибки для других связей. Может какие-то проблемы с отношениями или они дублируют друг друга?

  • Andrey 14.06.2010

    Я так понимаю, что сама утилита не совсем верно сгенерила связи. В системе нет связи Организация к Заказу N:1, есть наоборот)
    И это верно! должно быть у одной организации несколько Заказов, а не наоборот.
    Но при правке, все равно пишет ту же ошибку…

  • slivka_83 14.06.2010

    Даже не знаю чем помочь… поле new_account_salesorder кастомное так что воспроизвести не получится.

  • Andrey 14.06.2010

    хм, с ошибками linq вроде разобрался(просто удалил 4 объявления поля)))
    теперь проблема с авторизацией
    стандарно использовался такой код:
    CrmAuthenticationToken token = new CrmAuthenticationToken();
    token.AuthenticationType = 0;
    token.OrganizationName = context.OrganizationName;

    CrmService service = new CrmService();
    RegistryKey regkey = Registry.LocalMachine.OpenSubKey(«SOFTWARE\\Microsoft\\MSCRM»);

    service.Url = String.Concat(regkey.GetValue(«ServerUrl»).ToString(), «/2007/crmservice.asmx»);
    service.CrmAuthenticationTokenValue = token;
    service.UseDefaultCredentials = true;

    Но! с использование linq предлагается следующее:
    var crm = new CrmQueryProvider(new CrmConnection());
    кул! а теперь что пихать в эту строку?
    SDK предлогает такие варианты:
    The following example shows the connection string using integrated authentication:

    AuthenticationType=Integrated; Server=http://crm-server-name/crm-organization-name
    The following example shows the connection string using Active Directory authentication:

    Authentication Type=AD; Server=http://crm-server-name:port/crm-organization-name; User ID=user-domain\user-name; Password=user-password
    The following example shows the connection string for an Internet-facing deployment (IFD):

    Authentication Type=SPLA; Server=http://crm-server-name/crm-organization-name; User ID=user-domain\user-name; Password=user-password
    The following example shows the connection string using Windows Live ID to connect to Microsoft Dynamics CRM Online:

    Authentication Type=Passport; Server=https://crm-organization-name.crm.dynamics.com/crm-organization-name; User ID=user-windows-live-id; Password=user-password; Device ID=user-defined-device-id; Device Password=user-defined-device-password

    Это все хорошо, но «AuthenticationType=Integrated» не катит(я так понимаю) и чего делать? явно использовать лог и пасс? Это как-то странно)

  • Akka 14.06.2010

    У меня проблема следущая:

    написал плагин с linq, работает ок, но:
    в первый раз отработки, он получает корректные данные, потом что-то меняю, снова инициирую событие плагина, и уже во второй раз в плагин через linq попадают старые данные (до изменения)

    и так постоянно, пока iisreset не сделаешь

    linq как бы кэширует данные после 1 запроса

    сталкивались ли с такой проблемой?

  • slivka_83 14.06.2010

    2Andrey
    Не совсем понял? Что значит «AuthenticationType=Integrated» не катит?
    ведь в

    var crm = new DataContext(CrmConnection.Parse("Authentication Type=Integrated; Server=http://win-n22hj23d1b1/superfirma")); 
    

    не используется логин и пароль? 🙂

  • slivka_83 14.06.2010

    2Akka

    Ну, в линковской справке есть разделы по кэшированию (искать по слову cache) 🙂 сам не пользовался, но других идей нет 🙂

  • slivka_83 14.06.2010

    Добавил в код очистку кэша 🙂

  • Dmitry 14.06.2010

    Привет!

    а пробовал делать сложные join: например выбрать организацию по имени и по полному имени связанного контакта.

  • slivka_83 14.06.2010

    Привет 🙂 Нет, такие эксперименты с линком я не проводил 🙂

  • Aliwer 14.06.2010

    Скажите пожалуйста, мне нужно организовать запрос, который удовлетворяет каким-то условиям, но этот запрос не возможно выполнить с помощью «Расширенного поиска. Написал Fetch запрос все работает, но выводит на консоль. Вопрос: Как сделать так, чтобы этот Fetch запрос сохранился и чтоб можно было смотреть в списке «Сохраненные представления» ?

    Заранее спасибо !

*

code