Добро пожаловать в мой блог! Изначально он не задумывался как блог CRM разработчика, но жизнь сама внесла нужные коррективы. Тут я публикою все свои наблюдения относительно обозначенных в заголовке систем. Если Вы найдете в нем что-то интересное для Вас, как для заказчика, то буду рад сотрудничать с Вами! В моей компетенции 100% задач по MS CRM 3.0/4.0/2011:
MVP 2010, 2011
- Консалтинг
- Проектирование
- Разработка
- Обучение
MVP 2010, 2011
Кастомные служебные задания для CRM
Запись от Артем Enot Грунин размещена 17.07.2012 в 17:11
Обновил(-а) Артем Enot Грунин 17.07.2012 в 23:27
Обновил(-а) Артем Enot Грунин 17.07.2012 в 23:27
Начиная с версии 4.0 в CRM есть подсистема Служебных заданий - System Job. В SDK служебные задания представлены объектом AsyncOperation и, как можно понять из названия, за их выполнение отвечает Асинхронный сервис системы.
Служебные задания - это запускаемые асинхронно плагины, рабочие процессы и различные служебные системные задания, такие как обновления индексов базы данных. В более ранних версиях системы эти задания выполнялись SQL Agent.
И хотя со Служебными заданиями можно взаимодействовать через SDK, с ними нельзя сделать одно самое главное - создать свой тип Служебного задания. Это прискорбно, так как достаточно часто возникает задача выполнять какие-либо операции на периодической основе. Хороший пример - это системные задания автоматического сведения Целей и закрытия истекших сервисных Контрактов. Даже набившая оскомину проблема "Поздравлялки с днем рождения" могла бы получить изысканное решение, будь у нас возможность выполнять некие действия на периодической основе.
Вариантов решения множество:
Для реализации этой задачи я выбрал следующий подход:
В теории все просто, но на практике возникают сложности. Первая проблема, которую нам придется преодолеть - это врожденное неумение CRL сборок для SQL работать с веб-сервисами. Дело в том, что текущие версии SQL поддерживают работу только с определенным, очень ограниченным набором сборок из состава .NET: Supported .NET Framework Libraries. Все прочие (и их зависимости) придется регистрировать в базе отдельно. Но, обо всем по порядку!
1. Поддержка CLR.
Необходимо включить поддержку CLR типов для базы в которой мы планируем разворачивать нашу процедуру. Для этого необходимо выполнить следующий скрипт:
Пожалуйста, убедитесь что вы понимаете что делаете, прежде чем выполнять данный скрипт! Рекомендую ознакомиться с официальным источником: Common Language Runtime (CLR) Integration.
2. Поддержка WCF
Следующим шагом, необходимо зарегистрировать сборки (и их зависимости), которые нам потребуются для взаимодействия с веб-сервисами Windows Communication Foundation. Для этого выполним следующий скрипт:
Так как наши сборки "UNSAFE" мы вынуждены сделать нашу базу Trustworthy в первой инструкции.
3. Проект CLR процедуры для SQL Server.
Visual Studio имеет готовые шаблоны проектов CLR типов для SQL Server. Воспользуемся этим:
Далее необходимо указать к какой базе будет подключено решение. Нужно указать ту базу, для которой вы выполняли инструкции выше:
Следующий шаг - создание самой процедуры. Для этого выбираем пункт "Создать новый элемент" (Add new item) из контекстного меню проекта:
Далее добавим в созданный файл следующий код:
Так же следует добавить Service Reference для CRM. В моем примере она называется CrmService. Не забудьте указать корректный адрес веб-сервиса в переменной serviceUrl! Я не сторонник хардкодинга подобных вещей - данный код - это лишь пример. В своем решении, вы можете реализовать получение адреса из таблицы настроек или параметра процедуры - на свой вкус.
4. Развертывание и тестирование созданного CLR типа.
Visual Studio умеет автоматически развертывать сборки и их содержимое на SQL Server. К сожалению, при этом студия зависает на неопределенное время. Возможно это специфика моего развертывания - не знаю. В любом случае, если вы столкнулись с такой проблемой вы можете воспользоваться следующим скриптом для выполнения той же самой операции:
Напоминаю, что в скрипте необходимо указать корректный путь к сборке решения и корректные имена типов.
Теперь остается убедиться что все работает. Для этого вы можете воспользоваться отладчиком VS или просто запустить процедуру:
В данном примере, запрос выбирает идентификатор тестовой записи объекта Клиент и вызывает предварительно настроенный тестовый процесс. Процесс должен быть доступен для запуска в ручную!
В реализации приведенной выше, я разворачивал сборки непосредственно в базе CRM, что, конечно же, не поддерживается. В реальном проекте следует использовать для этих целей отдельную базу. Второй момент: учетная запись от имени которой выполняется код - это учетная запись SQL сервера. Если база данных CRM обслуживается той же инстанцией SQL, то ее не нужно (даже запрещено) добавлять как пользовательскую учетную запись CRM. Все действия вашей сборки будут выполняться от привилегированной учетной записи SYSTEM:
4. Настройка SQL Job
Последний шаг - это настройка самого задания SQL Agent Job. Это наиболее простая операция из всех здесь приведенных. Нужно лишь указать операцию которую необходимо выполнить:
и расписание запуска:
Разумеется в настройках необходимо задать какую-то разумную выборку и правильный процесс-обработчик. Процесс должен:
5. Грязная магия
А теперь немного о грустном... При вызове вашей процедуры вы с большой долей вероятности получите ошибку:
Что это и почему возникает написано тут: http://support.microsoft.com/kb/949080/en-us и тут: Supported .NET Framework Libraries. Последнюю ссылку я уже приводил в этой статье.
Суть проблемы в следующем: при инстанцировании сборки, SQL Server по какой-то неведомой мне причине проверяет, нет ли более новой версии в GAC. Если более новая версия найдена, то запуск валится с ошибкой. Чтобы все работало корректно, необходимо обновить сборки в базе более свежими версиями из GAC.
Почему это могло случиться? На ваш сервер установились обновления .NET Framework. Надо было думать прежде чем ставить что попало на производственный сервер! Если вы думаете, что для обновления сборок в базе достаточно повторно запустить скрипт регистрации, то нет. Почему-то обновления не затрагивают сборки в каталоге инсталляции .NET Framework! Свежие версии содержаться только в GAC. Выцарапать их оттуда можно, например, командой меню RUN:
Теперь осталось только найти и положить в какой-либо каталог 17 сборок и повторно их зарегистрировать. Прошлые версии предварительно нужно удалить. Будьте бдительны! Некоторые сборки имеют кросс-зависимости, так что их нужно удалять в одной транзакции (возможно только через SQL, через интерфейс не получится). Возможно более простым и правильным будет обновить зарегистрированные сборки при помощи инструкции ALTER.
Вторая проблема настолько загадочна, что я даже не стану вдаваться в суть проблемы. Приеду только ссылку на ее решение: What is Microsoft.VisualStudio.Diagnostics.ServiceModelSink.dll? Если упомянутая библиотека вылазит в сообщении об ошибке - вам сюда.
Итоги.
Задача решаема, но о цене судите сами. Я бы не назвал это "Enterprise Ready" решением, как любит говорить мой коллега Андрей Слепицкий, но это как минимум "быстрое решение".
Перед проведением своих зловещих экспериментов рекомендую ознакомиться с первоисточником: Common Language Runtime (CLR) Integration Programming Concepts. Так же хорошее практическое руководство есть тут: Invoking a WCF Service from a CLR Trigger.
Служебные задания - это запускаемые асинхронно плагины, рабочие процессы и различные служебные системные задания, такие как обновления индексов базы данных. В более ранних версиях системы эти задания выполнялись SQL Agent.
И хотя со Служебными заданиями можно взаимодействовать через SDK, с ними нельзя сделать одно самое главное - создать свой тип Служебного задания. Это прискорбно, так как достаточно часто возникает задача выполнять какие-либо операции на периодической основе. Хороший пример - это системные задания автоматического сведения Целей и закрытия истекших сервисных Контрактов. Даже набившая оскомину проблема "Поздравлялки с днем рождения" могла бы получить изысканное решение, будь у нас возможность выполнять некие действия на периодической основе.
Вариантов решения множество:
- Самопал. Самостоятельная реализация "долгоиграющего" приложения.
- Винсервис. Стандартный сервис Windows.
- Консоль + планировщик. Настройка планировщика Windows для вызова некоторого приложения по расписанию
- Прочие менее популярные подходы.
- Корректная обработка выключения и перезагрузки сервера
- Корректная работа на ферме серверов
- Балансировка нагрузки при работе в ферме
Для реализации этой задачи я выбрал следующий подход:
- Создается стандартный рабочий процесс с запуском вручную. Этот процесс будет выполнять необходимые нам действия - обновлять запись, слать письма и пр. для каждой записи в выборке.
- Создается хранимая CLR процедура. Процедура в качестве входных параметров принимает строку SQL запроса и идентификатор рабочего процесса. Она выполняет запрос и для каждой найденной записи осуществляет запуск указанного бизнес процесса.
- Создается задание планировщика SQL, который будет выполнять запуск процедуры.
В теории все просто, но на практике возникают сложности. Первая проблема, которую нам придется преодолеть - это врожденное неумение CRL сборок для SQL работать с веб-сервисами. Дело в том, что текущие версии SQL поддерживают работу только с определенным, очень ограниченным набором сборок из состава .NET: Supported .NET Framework Libraries. Все прочие (и их зависимости) придется регистрировать в базе отдельно. Но, обо всем по порядку!
1. Поддержка CLR.
Необходимо включить поддержку CLR типов для базы в которой мы планируем разворачивать нашу процедуру. Для этого необходимо выполнить следующий скрипт:
X++:
sp_configure 'show advanced options', 1; GO RECONFIGURE; GO sp_configure 'clr enabled', 1; GO RECONFIGURE; GO
2. Поддержка WCF
Следующим шагом, необходимо зарегистрировать сборки (и их зависимости), которые нам потребуются для взаимодействия с веб-сервисами Windows Communication Foundation. Для этого выполним следующий скрипт:
X++:
ALTER DATABASE [FixRM_MSCRM] SET Trustworthy ON GO CREATE ASSEMBLY [System.Web] from 'C:\Windows\Microsoft.NET\Framework64\v2.0.50727\System.Web.dll' with permission_set = UNSAFE GO CREATE ASSEMBLY SMDiagnostics from 'C:\Windows\Microsoft.NET\Framework64\v3.0\Windows Communication Foundation\SMDiagnostics.dll' with permission_set = UNSAFE GO CREATE ASSEMBLY [System.Runtime.Serialization] from 'C:\Windows\Microsoft.NET\Framework64\v3.0\Windows Communication Foundation\System.Runtime.Serialization.dll' with permission_set = UNSAFE GO CREATE ASSEMBLY [System.IdentityModel] from 'C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.IdentityModel.dll' with permission_set = UNSAFE GO CREATE ASSEMBLY [System.IdentityModel.Selectors] from 'C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.IdentityModel.Selectors.dll' with permission_set = UNSAFE GO CREATE ASSEMBLY [System.Messaging] from 'C:\Windows\Microsoft.NET\Framework64\v2.0.50727\System.Messaging.dll' with permission_set = UNSAFE GO CREATE ASSEMBLY [Microsoft.Transactions.Bridge] from 'C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\Microsoft.Transactions.Bridge.dll' with permission_set = UNSAFE GO CREATE ASSEMBLY [System.ServiceModel] from 'C:\Windows\Microsoft.NET\Framework64\v3.0\Windows Communication Foundation\System.ServiceModel.dll' with permission_set = UNSAFE GO
3. Проект CLR процедуры для SQL Server.
Visual Studio имеет готовые шаблоны проектов CLR типов для SQL Server. Воспользуемся этим:
Далее необходимо указать к какой базе будет подключено решение. Нужно указать ту базу, для которой вы выполняли инструкции выше:
Следующий шаг - создание самой процедуры. Для этого выбираем пункт "Создать новый элемент" (Add new item) из контекстного меню проекта:
Далее добавим в созданный файл следующий код:
X++:
using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using SqlServerCrmClrIntegration.CrmService; using System.Collections.Generic; using System.ServiceModel.Channels; using System.ServiceModel.Security.Tokens; using System.ServiceModel; public partial class StoredProcedures { [Microsoft.SqlServer.Server.SqlProcedure] public static void ExecuteWorkflow(SqlString sql, SqlGuid workflowId) { using (SqlConnection connection = new SqlConnection("context connection=true")) { connection.Open(); SqlCommand command = new SqlCommand(sql.Value, connection); SqlDataReader reader = command.ExecuteReader(); using (reader) { String serviceUrl = "http://localhost/FixRM/XRMServices/2011/Organization.svc"; SymmetricSecurityBindingElement security = new SymmetricSecurityBindingElement(new SspiSecurityTokenParameters()); HttpTransportBindingElement http = new HttpTransportBindingElement(); CustomBinding binding = new CustomBinding(); binding.Elements.Add(security); binding.Elements.Add(http); OrganizationServiceClient client = new OrganizationServiceClient(binding, new EndpointAddress(serviceUrl)); while (reader.Read()) { OrganizationRequest executeWorkflow = new OrganizationRequest(); executeWorkflow.RequestName = "ExecuteWorkflow"; executeWorkflow.Parameters = new ParameterCollection(); executeWorkflow.Parameters.Add(new KeyValuePair<string, object>("WorkflowId", workflowId.Value)); executeWorkflow.Parameters.Add(new KeyValuePair<string, object>("EntityId", reader.GetSqlGuid(0).Value)); client.Execute(executeWorkflow); } client.Close(); } } } };
4. Развертывание и тестирование созданного CLR типа.
Visual Studio умеет автоматически развертывать сборки и их содержимое на SQL Server. К сожалению, при этом студия зависает на неопределенное время. Возможно это специфика моего развертывания - не знаю. В любом случае, если вы столкнулись с такой проблемой вы можете воспользоваться следующим скриптом для выполнения той же самой операции:
X++:
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'ExecuteWorkflow') DROP PROCEDURE ExecuteWorkflow IF EXISTS (SELECT * FROM sys.assemblies asms WHERE asms.name = N'SqlServerCrmClrIntegration' and is_user_defined = 1) DROP ASSEMBLY [SqlServerCrmClrIntegration] CREATE ASSEMBLY [SqlServerCrmClrIntegration] from 'С:\Путь к каталогу проекта\SqlServerCrmClrIntegration\bin\Debug\SqlServerCrmClrIntegration.dll' WITH PERMISSION_SET = UNSAFE GO CREATE PROCEDURE ExecuteWorkflow (@SQL NVARCHAR(MAX), @WORKFLOW UNIQUEIDENTIFIER) AS EXTERNAL NAME SqlServerCrmClrIntegration.StoredProcedures.ExecuteWorkflow; GO
Теперь остается убедиться что все работает. Для этого вы можете воспользоваться отладчиком VS или просто запустить процедуру:
X++:
DECLARE @SQL AS NVARCHAR(MAX) = 'SELECT account.accountid FROM FilteredAccount account WHERE account.accountid = ''1BDFF7A2-D599-E111-A0F2-0800271D883E''' DECLARE @WORKFLOW AS UNIQUEIDENTIFIER = '53304737-2B84-4C09-8969-351218500BD1' EXECUTE ExecuteWorkflow @SQL, @WORKFLOW GO
В реализации приведенной выше, я разворачивал сборки непосредственно в базе CRM, что, конечно же, не поддерживается. В реальном проекте следует использовать для этих целей отдельную базу. Второй момент: учетная запись от имени которой выполняется код - это учетная запись SQL сервера. Если база данных CRM обслуживается той же инстанцией SQL, то ее не нужно (даже запрещено) добавлять как пользовательскую учетную запись CRM. Все действия вашей сборки будут выполняться от привилегированной учетной записи SYSTEM:
4. Настройка SQL Job
Последний шаг - это настройка самого задания SQL Agent Job. Это наиболее простая операция из всех здесь приведенных. Нужно лишь указать операцию которую необходимо выполнить:
и расписание запуска:
Разумеется в настройках необходимо задать какую-то разумную выборку и правильный процесс-обработчик. Процесс должен:
- Подходить типу записи, которую возвращает запрос
- Быть настроен на запуск вручную или как дочерний процесс
- Быть настроен на автоматическое удаление журнала (иначе вы быстро забьете логи)
5. Грязная магия
А теперь немного о грустном... При вызове вашей процедуры вы с большой долей вероятности получите ошибку:
X++:
Server: Msg 6522, Level 16, State 2, Line 1 A .NET Framework error occurred during execution of user defined routine or aggregate XYZ: System.IO.FileLoadException: Could not load file or assembly 'System.ServiceModel, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. Assembly in host store has a different signature than assembly in GAC. (Exception from HRESULT: 0x80131050) System.IO.FileLoadException:
Суть проблемы в следующем: при инстанцировании сборки, SQL Server по какой-то неведомой мне причине проверяет, нет ли более новой версии в GAC. Если более новая версия найдена, то запуск валится с ошибкой. Чтобы все работало корректно, необходимо обновить сборки в базе более свежими версиями из GAC.
Почему это могло случиться? На ваш сервер установились обновления .NET Framework. Надо было думать прежде чем ставить что попало на производственный сервер! Если вы думаете, что для обновления сборок в базе достаточно повторно запустить скрипт регистрации, то нет. Почему-то обновления не затрагивают сборки в каталоге инсталляции .NET Framework! Свежие версии содержаться только в GAC. Выцарапать их оттуда можно, например, командой меню RUN:
X++:
C:\Windows\assembly\gac_msil
Вторая проблема настолько загадочна, что я даже не стану вдаваться в суть проблемы. Приеду только ссылку на ее решение: What is Microsoft.VisualStudio.Diagnostics.ServiceModelSink.dll? Если упомянутая библиотека вылазит в сообщении об ошибке - вам сюда.
Итоги.
Задача решаема, но о цене судите сами. Я бы не назвал это "Enterprise Ready" решением, как любит говорить мой коллега Андрей Слепицкий, но это как минимум "быстрое решение".
Перед проведением своих зловещих экспериментов рекомендую ознакомиться с первоисточником: Common Language Runtime (CLR) Integration Programming Concepts. Так же хорошее практическое руководство есть тут: Invoking a WCF Service from a CLR Trigger.
Всего комментариев 1
Комментарии
-
Запись от Артем Enot Грунин размещена 14.03.2014 в 10:55