Иногда возникает необходимость выполнить объединение совпадающих записей в одну. Это можно сделать выполнив слияние сущностей, с дальнейшим обновлением ссылок слитой сущности на основную сущность.
Предположим, что у нас есть сущность «Физическое лицо» или «Клиент» или «Контакт» (contact), с дополнительным внешним идентификатором (new_id) и связанной сущностью «Документ клиента» (new_client_document). Нам необходимо создать плагин, который будет при создании новой записи Документа клиента искать клиентов с совпадающими данными документа и пустым дополнительным идентификатором, и объединять их в одну запись клиента.
Пример
Клиент №1. Иванов Иван, Документ 1234 123456, идентификатор отсутствует.
Клиент №2. Петров Петр, Документ 1234 123456, идентификатор отсутствует.
Создаем нового клиента:
Клиент №3. Сидоров Сидор, Документ 1234 123456, идентификатор 123.
В результате в системе должна остаться одна активная запись Клиента №3, на которую ссылаются все связанные записи от Клиента №1 и Клиента №2.
Код
/// <summary> /// Плагин для автоматического поиска "Физических лиц" с совпадающими данными и их слияния. /// </summary> /// <remarks> /// Данный плагин регистрируется на сообщение Create, сущности "Документ клиента" (new_client_document), /// состояние post-operation в асинхронном режиме. /// </remarks> public class Plugin : IPlugin { public void Execute(IServiceProvider serviceProvider) { // Получить контекст выполнения и сервис от поставщика услуг. IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); var service = serviceFactory.CreateOrganizationService(context.UserId); // Коллекция InputParameters содержит все данные, переданные в запросе. if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity) { // Получить целевой объект из входных параметров. var clientDocument = (Entity)context.InputParameters["Target"]; // Ищем контакты с совпадающими данными документа. var contacts = GetContacts(service, clientDocument); if (contacts.Length == 0) { return; } // Связанные со сливаемым клиентом записи. var entityNames = GetOneToManyRelationshipEntityAttributePair(service); // Сливаем совпадающие контакты и обновляем ссылки. foreach (var contact in contacts) { var client = clientDocument.GetAttributeValue<EntityReference>("new_clientid"); MergeContact(service, client, contact); foreach (var entity in entityNames) { UpdateLookup(service, entity.Item1, entity.Item2, contact.Id, client.Id); } } } } /// <summary> /// Выполняет получение массива "Физических лиц", удовлетворяющих условиям. /// </summary> /// <remarks> /// Совпадает серия и номер "Документа клиента" "Физического лица" с обрабатываемым "Документом клиента". /// Отсутствует идентификатор у "Физического лица. /// </remarks> /// <param name="service">Провайдер предоставляющий доступ к данным и метаданным организации.</param> /// <param name="clientDocument">Документ клиента.</param> /// <returns>Массив "Физических лиц".</returns> private EntityCollection GetContacts(IOrganizationService service, Entity clientDocument) { var series = clientDocument.GetAttributeValue<string>("new_series"); var number = clientDocument.GetAttributeValue<string>("new_number"); var query = new QueryExpression() { EntityName = "contact", ColumnSet = new ColumnSet(false), Distinct = true, LinkEntities = { new LinkEntity() { LinkFromEntityName = "contact", LinkFromAttributeName = "contactid", LinkToEntityName = "new_client_document", LinkToAttributeName = "new_clientid", Columns = new ColumnSet(false), LinkCriteria = new FilterExpression { FilterOperator = LogicalOperator.And, Conditions = { new ConditionExpression("new_series", ConditionOperator.Equal, series), new ConditionExpression("new_number", ConditionOperator.Equal, number) } } }, }, Criteria = { Conditions = { new ConditionExpression("new_id", ConditionOperator.Null) } } }; var contacts = service.RetrieveMultiple(query); return contacts; } /// <summary> /// Получить список имен зависимых сущностей и атрибутов. /// </summary> /// <param name="service">Провайдер предоставляющий доступ к данным и метаданным организации.</param> /// <returns>Список пар "Имя сущности" - "Атрибут сущности".</returns> private List<(string, string)> GetOneToManyRelationshipEntityAttributePair(IOrganizationService service) { // Не все сущности поддерживают возможность выполнения поисковых запросов. // Обычно это внутренние служебные сущности CRM. // Поэтому мы пропускаем эти сущности. var excludeEntitys = new string[] { "postrole", "postregarding", "mailboxtrackingfolder", "customeraddress" }; var contactMetadata = RetrieveEntityMetadata(service, "contact"); var result = contactMetadata.OneToManyRelationships .Select(c => new ValueTuple<string, string> { Item1 = c.ReferencingEntity, Item2 = c.ReferencingAttribute }) .Where(c => !excludeEntitys.Contains(c.Item1)).ToList(); return result; } /// <summary> /// Получает метаданные заданной сущности. /// </summary> /// <param name="service">Провайдер предоставляющий доступ к данным и метаданным организации.</param> /// <param name="logicalName">Логическое имя сущности.</param> /// <returns>Метаданные сущности.</returns> public static EntityMetadata RetrieveEntityMetadata(IOrganizationService service, string logicalName) { var request = new RetrieveEntityRequest { LogicalName = logicalName, EntityFilters = EntityFilters.All, RetrieveAsIfPublished = false }; var result = (RetrieveEntityResponse)service.Execute(request); return result.EntityMetadata; } }
Подробнее про регистрацию плагинов можно прочитать в статье Создание Plug-in для Microsoft Dynamic 365