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

DynamicEntity vs. BusinessEntity

В прошлом посте (Введение в разработку плагинов для Microsoft CRM 4.0) была описна базовая процедура создания плагина. Помимо прочего было сказано, что для разработки плагинов для MS CRM Вы должны добавить в проект в Visual Studio обращение к API веб-служб CRM (всего их три: CrmService, MetadataService и DiscoveryService). Но там был описан только один способ добавления web reference в проект. Всего же их можно добавить двумя (в контексте MS CRM) разными способами:

Web Reference на WSDL

Для того чтобы задействовать этот способ можно пойти двумя путями:

Динамический

  • В Solution Explorer щелкните правой кнопкой мыши по Reference — Add Service Reference;
  • Кнопка Advanced — Add Web Reference…;
  • В поле URL введите путь к необходимой Вам службе:
    • http://<servername:port>/mscrmservices/2007/crmservice.asmx (с именем CrmSdk);
    • http://<servername:port>/mscrmservices/2007/MetadataService.asmx (с именем MetadataServiceSdk);
    • http://<servername:port>/mscrmservices/2007/AD/CrmDiscoveryService.asmx (с именем CrmSdk.Discovery).
  • Жмите Add Reference.



Статический

  • Откройте CRM и перейдите Параметры — Настройка — Загрузка файлов описания веб-служб;
  • Щелкните на значке CrmService.asmx чтобы загрузить файл. Файл откроется в окне браузера. Сохраните страницу на диск в виде XML-файла (т.е. измените при сохранении расширение файла либо на .xml, либо на .wsdl);
  • В Solution Explorer щелкните правой кнопкой мыши по Reference — Add Service Reference;
  • Кнопка Advanced — Add Service Reference;
  • В поле Service Reference Setting укажите путь к скаченному файлу WSDL и введите имя CrmSdk;
  • Жмите Add Reference.



Reference на SDK-сборки

  • Установите последнюю версию MS CRM SDK;
  • В Solution Explorer щелкните правой кнопкой мыши по Reference — Add Reference;
    Перейдите на вкладку Browse;
  • В папке, куда Вы установли SDK откройте подпапку Bin. Выделите Microsoft.Crm.Sdk.dll и Microsoft.Crm.SdkTypeProxy.dll и OK.


Преимущества и недостатки

CRM SDK это веб-служба, которая генерирует динамический WSDL основанный на Вашей текущей схеме CRM. Например, если Вы создаете кастомные объекты и или добавляете кастомные атрибуты к объектам, то WSDL отразит эти настройки. Что дает Вам возможность тут же использовать их при разработке. Но динамический WSDL является удобным только на первый взгляд – у него есть один большой недостаток. После внесения настроек в CRM и их публикации, любые web reference, которые были созданы с использованием старой версии схемы не будет работать (если только доступ к данным не сделан с использованием dynamic entity). Вся проблема в том – что свойства изменяются, когда меняется схема и попытка задать свойства с помощью старой схемы – потерпит неудачу.

Веб-служба – это «контракт», и изменение контракта не должно быть односторонним – необходимо, чтобы все стороны контракта согласились на изменения. Но динамический подход WSDL противоречит этому требованию, позволяя администратору CRM в одностороннем порядке изменять «контракт» веб-службы, просто внося изменения в объекты CRM.

Вообщем, использование статических классов вместо динамических объектов представляет существенный риск стабильности для всех приложений, подключающихся к CRM.
Поэтому у CRM SDK также есть другой режим работы, который называется Dynamic Entity. Использование динамического класса лишено вышеописанного недостатка, поскольку этот «контракт» не меняется при внесении изменений в настройки CRM.

Dynamic Entity – объект, предназначенный для работы с любым объектом CRM, как входящими в стандартную комплектацию CRM, так и полностью кастомными. Это предпочтительный способ использования данных CRM из внешних приложений. Использование Dynamic Entity гарантирует что Ваши приложения будут работать с данными CRM, не сваливаясь из-за изменений сервиса каждый раз, когда происходит публикация настроек CRM.

Есть и другие преимущества использования динамических объектов, например, универсальность:

  • Проще узнать, как использовать динамический класс вместо изучения несметного числа других прямых классов, которые сгенерированы через dynamic web reference;
  • Такой код намного легче повторно использовать;
  • Наконец, не придется непрерывно обновлять web reference и перекомпилировать и повторно разворачивать обновленные приложения каждый раз при изменении схемы CRM.

Другим последствием добавления web reference то, что у Вас теперь есть URL к Вашему CRM, вставленному непосредственно в Ваш код. Соответственно развернуть на продакшен серверы Вы его не сможете, т.к. web reference ссылаются на сервер разработки. У Вас есть два выбора: Вы можете изменить код прежде, чем Вы его развернете (плохая идея), или Вы можете добавить переменную в свой файл конфигурации, а затем заменить url в коде во время выполнения.

В свою очередь, у DLL сборк CRM SDK (microsoft.crm.sdk.dll и microsoft.crm.sdktypeproxy.dll) есть все, что необходимо для полноценного использования CRM и их при использовании нет необходимости добавлять web reference в Ваш проект.

Сводная табличка…

Преимущества Недостатки
Ссылки на WSDL
  • Простота кода при использовании Business Entity;
  • Необходимость обновлять ссылку при изменениях в WSDL (т.е. при изменении объектов и их публикации);
  • Необходим доступ к серверу, на котором осуществляется развертывание (при статическом способе этот недостаток несколько нивелируется).
SDK-сборки
  • Возможность удаленной и «независимой» разработки.
  • Стандартные классы могут работать только с объектами и атрибутами входящими в начальный комплект поставки CRM.
  • Относительно более «громоздкий» код при использовании Dynamic Entity.

Таким образом, при невозможности добавить в проект ссылку на CRM веб-сервис необходимо использовать сборки SDK. Но при их использовании классы Business Entity доступны только для стандартных объектов и стандартных атрибутов (поставляемых «в коробке»). Если же необходимо задействовать кастомные объекты и атрибуты, то следует обратиться к т.н. динамическим объектам (DynamicEntity).

А теперь несколько прмеров…

Но для начала создадим сам проект в Visual Studio. Для этого (в кратце повторю шаги описанные в статье ):

  • Создайте новый C# проект Claass Library;
  • Подпишите его Цифоровой подписью;
  • Установите CRM SDK и добавьте ссылки на:
    • Microsoft.Crm.Sdk.dll
    • Microsoft.Crm.SdkTypeProxy.dll

    а также на:

    • System.Web.Services
  • Добавьте следующие пространства имен в код:
    • using System.Net;
    • using Microsoft.Crm.Sdk;
    • using Microsoft.Crm.SdkTypeProxy;
    • using Microsoft.Crm.Sdk.Query;

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



Ну, а дальше нужно просто использовать ниже приведенный код (подправив его в некоторых местах)…

Создание записи

DynamicEntity

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 ClassLibrary1
{
    public class CreateDynamic : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            // Конфигурируем CrmService
            CrmAuthenticationToken myToken = new CrmAuthenticationToken();
            myToken.OrganizationName = "superfirma";
            /*
			Тип аутентификации
			0 – Active Directory
			1 – MS CRM Dynamics Live
			2 – IFD
			*/
            myToken.AuthenticationType = 0;
            CrmService myService = new CrmService();
            /*
            Передаем учетные данные Active Directory, чтобы соединится с API веб-служб MS CRM
            В данном случаи пользователя вошедшего в систему
            */
            myService.Credentials = System.Net.CredentialCache.DefaultCredentials;
            myService.Url = "http://mmcrm.ru/MSCrmServices/2007/CrmService.asmx";
            myService.CrmAuthenticationTokenValue = myToken;


            // Создаем экземпляр класса DynamicEntity 
            DynamicEntity myDE = new DynamicEntity();
            // Опеределяем объек с которым будем работать
            myDE.Name = "contact";

            // Создаем экземпляр класса StringProperty чтобы задать значение для двух атрибутов: firstname и lastname
            StringProperty myFirstName = new StringProperty();
            myFirstName.Name = "firstname";
            myFirstName.Value = "Mickey";
            StringProperty myLastName = new StringProperty();
            myLastName.Name = "lastname";
            myLastName.Value = "Mouse";

            // Помещяем все атрибуты и их значения в экземпляр класса PropertyCollection
            PropertyCollection properties = new PropertyCollection();
            properties.Add(myFirstName);
            properties.Add(myLastName);

            // Добавляем в атрибуты в объект DynamicEntity 
            myDE.Properties = properties;

            Guid contactGuid = new Guid();

            try
            {
                // Отправляем запрос в web-сервис для cоздания новой записи объекта Контакт
                contactGuid = myService.Create(myDE);
            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                // Обработка ошибок
                throw new InvalidPluginExecutionException("An error occurred in the AccountCreateHandler plug-in.", ex);
            }
        }
    }
}

Тестируем:

  • Соберите плагин;
  • Зарегистрируйте с помощью Plug-in Registration Tool на любое событие;
  • Вызовите это событие в CRM и смотрите на созданный Контакт 🙂



BusinessEntity

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 ClassLibrary1
{
    public class CreateBusiness : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            /*
			Конфигурируем CrmService с помощью контекста плагина. Передаем булев параметр: 
			true – олицетворение (т.е. выполнение кода под определенным пользователем CRM) происходит от имени пользователя указанного в поле impersonatinguserid при регистрации плагина. Если это поле не заполнит,то пдлагин будет выполнятся от имени вызывающего юзвера
			false – плагин будет выполнятся под системной учетной записью (как правило это SYSTEM)
			*/
            ICrmService crmService = context.CreateCrmService(true);

            // Создаем экземпляр объекта Account
            account acct = new account();

            // Задаем параметры создаваемой записи Бизнес-партнера
            acct.name = "Fourth Coffee";
            acct.address1_city = "Redmond";

            Guid contactGuid = new Guid();

            try
            {
                // Отправляем запрос в web-сервис для cоздания новой записи объекта Бизнес-партнера
                contactGuid = crmService.Create(acct);
            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                // Обработка ошибок
                throw new InvalidPluginExecutionException("An error occurred in the AccountCreateHandler plug-in.", ex);
            }
        }
    }
}

В этом примере есть одно важное отличие (помимо самого способа создания записи) — это то, как создается подключение к CRM сервису. В данном случаи используется контекст плагина, что значительно упрощает код.


Обновление записи

DynamicEntity

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

namespace ClassLibrary1
{
    public class UpdateDynamic : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            // Конфигурируем CrmService
            CrmAuthenticationToken myToken = new CrmAuthenticationToken();
            myToken.OrganizationName = "superfirma";
            myToken.AuthenticationType = 0;
            CrmService myService = new CrmService();
            /*
            Передаем учетные данные Active Directory конкретного юзвера (в таком порядке: имя, пароль, домен), чтобы соединится с API веб-служб MS CRM
            Юзер подключающийся к API CRM должен входить в группу PrivUserGroup
            */
            myService.Credentials = new NetworkCredential("Administrator", "1qaz@WSX", "d2008");
            myService.Url = "http://mmcrm.ru/MSCrmServices/2007/CrmService.asmx";
            myService.CrmAuthenticationTokenValue = myToken;

            // Создаем экземпляр динамческого объекта и указываем его имя
            DynamicEntity myDEUpdate = new DynamicEntity();
            myDEUpdate.Name = "contact";

            // Создаем KeyProperty для хранения GUID’а обновляемой записи
            KeyProperty myContactGuid = new KeyProperty();
            myContactGuid.Name = "contactid";

            // Указываем GUID обновляемой записи
            Key myContactKey = new Key();
            myContactKey.Value = new Guid("AC06CE34-721D-DF11-A324-000C29F6B309");
            myContactGuid.Value = myContactKey;
            myDEUpdate.Properties.Add(myContactGuid);

            // Создаем StringProperty с новым обновленным значением
            StringProperty myFirstNameU = new StringProperty();
            myFirstNameU.Name = "jobtitle";
            myFirstNameU.Value = "Nishu";
            myDEUpdate.Properties.Add(myFirstNameU);

            try
            {
                // Обновляем запись
                myService.Update(myDEUpdate);
            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                // Обработка ошибок
                throw new InvalidPluginExecutionException("An error occurred in the AccountCreateHandler plug-in.", ex);
            }
        }
    }
}

В данном случаи код тоже не совсем обычный — мы подключаемся к CRM сервису под конкретной учеткой, которую указали в коде (вместе с паролем и именем домена)!


BusinessEntity

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 ClassLibrary1
{
    public class UpdateBusiness : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            /*
            Конфигурируем CrmService с помощью контекста плагина. Передаем булев параметр: 
            true – олицетворение (т.е. выполнение кода под определенным пользователем CRM) происходит от имени пользователя указанного в поле impersonatinguserid при регистрации плагина. Если это поле не заполнит,то пдлагин будет выполнятся от имени вызывающего юзвера
            false – плагин будет выполнятся под системной учетной записью (как правило это SYSTEM)
            */
            ICrmService crmService = context.CreateCrmService(true);

            // Создаем экземпляр объекта Contact
            contact contact = new contact();

            // Задаем значение поля адрес
            contact.address1_line1 = "34 Market St.";

            // Указываем GUID изменяемой записи
            contact.contactid = new Key();
            contact.contactid.Value = new Guid("AC06CE34-721D-DF11-A324-000C29F6B309");

            try
            {
                // Отправляем запрос в web-сервис для обновления записи
                crmService.Update(contact);
            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                // Обработка ошибок
                throw new InvalidPluginExecutionException("An error occurred in the AccountCreateHandler plug-in.", ex);
            }
        }
    }
}


Возвращение записи

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 ClassLibrary1
{
    public class RetriveBusiness : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            // Конфигурируем CrmService
            CrmAuthenticationToken myToken = new CrmAuthenticationToken();
            myToken.OrganizationName = "superfirma";
            myToken.AuthenticationType = 0;
            CrmService myService = new CrmService();
            myService.Credentials = System.Net.CredentialCache.DefaultCredentials;
            myService.Url = "http://mmcrm.ru/MSCrmServices/2007/CrmService.asmx";
            myService.CrmAuthenticationTokenValue = myToken;

            // Задаем поля, которые требуется вернуть
            string[] attributes = new string[] { "firstname", "lastname" };
            ColumnSet cols = new ColumnSet(attributes);

            // Указываем GUID возвращаемой записи
            Guid contactGuid = new Guid("86D7F121-6E1D-DF11-A324-000C29F6B309");

            try
            {
                // Отправляем запрос в web-сервис
                contact contact = (contact)myService.Retrieve("contact", contactGuid, cols);
                // Выводим значение поля firstname в сообщении
                throw new InvalidPluginExecutionException(contact.firstname);
            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                // Обработка ошибок
                throw new InvalidPluginExecutionException("An error occurred in the AccountCreateHandler plug-in.", ex);
            }
        }
    }

    public class RetriveDiynamic : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            // Конфигурируем CrmService
			ICrmService crmService = context.CreateCrmService(true);

            // Указываем имя кобъекто, который требуется вернуть и его GUID
            TargetRetrieveDynamic target = new TargetRetrieveDynamic();
            RetrieveRequest retrieveRequest = new RetrieveRequest();
            RetrieveResponse retrieveResponse = null;
            DynamicEntity DynamicContact = null;
            target.EntityName = "contact";
            target.EntityId = new Guid("86D7F121-6E1D-DF11-A324-000C29F6B309");
            
            // Указываем параметры запроса
            retrieveRequest.ColumnSet = new AllColumns(); // Возвращаем все поля
            retrieveRequest.ReturnDynamicEntities = true;
            retrieveRequest.Target = target;
            
			// Собираем объект ответа
            retrieveResponse = (RetrieveResponse)crmService.Execute(retrieveRequest);

            try
            {
                // Отправляем запрос в web-сервис
                DynamicContact = (DynamicEntity)retrieveResponse.BusinessEntity;
                // Просматриваем все возвращенные поля и если среди них есть lastname, то выводим его значение в сообщении
				foreach (Property prop in DynamicContact.Properties)
                {
                    if (prop is StringProperty && prop.Name.ToLower() == "lastname")
                    {
                        StringProperty property = (StringProperty) prop;
                        throw new InvalidPluginExecutionException(property.Value);
                    }
                }

            }
            catch (System.Web.Services.Protocols.SoapException ex)
            {
                // Обработка ошибок
				throw new InvalidPluginExecutionException("An error occurred in the AccountCreateHandler plug-in.", ex);
            }
        }
    }
}

А в этом примере другая интересная ситуация — в одной сборке объетенины два плагина (разделение происходит путем создания нескольких классов с окончанием IPlugin) 🙂 но события на них в Plug-in Registration Tool регистрируются отдельно! Вобщем получается некая экономия пространства 🙂



Комментарии (4)
  • a33ik 20.02.2010

    Вцелом — неплохо написано, но есть несколько ньюансов.
    Надеюсь на позитивную реакцию на критику:
    1. Непонятно почему так называется пост. DynamicEntity — это класс наследованный от BusinessEntity. Источник — http://msdn.microsoft.com/en-us/library/bb956153.aspx.
    2. Service Reference — это когда таргетовым фреймворком для проекта является 3.0, 3.5, 4.0. Для 2.0 — Web Reference. У читателей может возникнуть вопрос.
    3. Устанавливать значения свойств DynamicEntity проще и читабельнее можно так:
    entity[] = .
    4. По поводу примера возвращения записи проще и читабельнее использовать такой код:
    if (entity.Properties.Contains() &&
    entity[] is string)
    throw new InvalidPlugineException((string)entity[]);

  • a33ik 20.02.2010

    Полыл коментарий…
    3. Следует читать так:
    entity[«Field Name»] = Value;
    4. Следует читать так:
    if (entity.Properties.Contains(«Field Name») &&
    entity[«Field Name»] is string)
    throw new InvalidPluginException((string)entity[«Field Name»]);

    5. >>разделение происходит путем создания нескольких классов с окончанием IPlugin
    Может всё таки стоит написать, что IPLugin — это интерфейс, а не окончание? 😉

  • Огурец 20.02.2010

    Зачем добавлять сслыки на MetadataService и DiscoveryService, если в примерах они не используются? И вообще для чего нужен DiscoveryService?

  • slivka_83 20.02.2010

    -> Огурец
    А где я добавлял их в примерах?
    Если не ошибаюсь DiscoveryService нужен чтобы получить системных данные об организации(ях)… но подробное изучение данной службы еще впереди 🙂

*

code