Разработка
25
Окт
0

Маппинг обновления

Маппинг в CRM это такая функция, которая при создании дочерней записи (1:N) через форму родительской переносит значения определенных полей с родительской в дочернюю запись. И работает она вроде как надо, но чего-то не хватает… а именно – того же маппинга при обновлении родительских записей. Ведь это очень удобно – один раз настроил и всегда пользуешься 🙂 Поэтому запилим плагин, который выполняет маппинг при обновлении родительской записи:

  • Создайте плагин с таким кодом:
    using System;
    using System.Collections.Generic;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Query;
    using Microsoft.Xrm.Sdk.Messages;
    using Microsoft.Xrm.Sdk.Metadata;
    
    using System.ServiceModel;
    
    namespace Mapping
    {
        public class Mapping: IPlugin
        {
            private List<string> relationShips;
            public Mapping(string unsecure, string secure)
            {
                // Разбиваем названия связей переданных в параметрах плагина
                relationShips = unsecure == null ? new List<string>() : new List<string>(
                    unsecure.Split(new char[] { ',', ';', ' ', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
                );
            }
    
            public void Execute(IServiceProvider serviceProvider)
            {
                IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
    
                // Вытаскиваем из таргета обновленные поля
                Entity target = (Entity)context.InputParameters["Target"];
                AttributeCollection tardetAttributes = (AttributeCollection)target.Attributes;
    
                // Полчаем метаданные всех переданных связей 
                List<OneToManyRelationshipMetadata> relationShipsMetadata = GetRelationshipDetails(context, service, relationShips);
    
                // Для каждой связи...
                foreach (OneToManyRelationshipMetadata relationShipMetadata in relationShipsMetadata)
                {
                    // Вытаскиваем настройки маппинга
                    List<InternalAttributeMap> mappingAttributes = GetChildAttributeMappings(context, service, relationShipMetadata.ReferencingEntity);
    
                    // Из списка маппинга получаем только те атрибуты, по которым произошли изменения
                    mappingAttributes = RemoveUnupdated(mappingAttributes, tardetAttributes, relationShipMetadata.ReferencedAttribute);
    
                    // Обновлем каждую дочернюю запись
                    UpdateChildEntities(context, service, mappingAttributes, target, relationShipMetadata.ReferencingEntity, relationShipMetadata.ReferencingAttribute);
                }
            }
    
            private static List<OneToManyRelationshipMetadata> GetRelationshipDetails(IPluginExecutionContext context, IOrganizationService service, List<string> relationshipNames)
            {
                RetrieveEntityRequest retrieveEntityRequest = new RetrieveEntityRequest()
                {
                    EntityFilters = EntityFilters.Relationships,
                    RetrieveAsIfPublished = true,
                    LogicalName = context.PrimaryEntityName
                };
    
                RetrieveEntityResponse retrieveEntityResp = (RetrieveEntityResponse)service.Execute(retrieveEntityRequest);
                List<OneToManyRelationshipMetadata> returnList = new List<OneToManyRelationshipMetadata>();
    
                foreach (OneToManyRelationshipMetadata relationshipMetadata in retrieveEntityResp.EntityMetadata.OneToManyRelationships)
                    foreach (string relName in relationshipNames)
                        if (relationshipMetadata.SchemaName.Equals(relName))
                            returnList.Add(relationshipMetadata);
    
                return returnList;
            }
    
            private static List<InternalAttributeMap> GetChildAttributeMappings(IPluginExecutionContext context, IOrganizationService service, string relatedentity)
            {
                QueryExpression relationshipMappings = new QueryExpression("attributemap");
                relationshipMappings.ColumnSet.AddColumns("sourceattributename", "targetattributename");
                relationshipMappings.Criteria.AddCondition("parentattributemapid", ConditionOperator.Null);
    
                LinkEntity entityMap = new LinkEntity("attributemap", "entitymap", "entitymapid", "entitymapid", JoinOperator.Inner);
                entityMap.LinkCriteria.AddCondition("sourceentityname", ConditionOperator.Equal, context.PrimaryEntityName);
                entityMap.LinkCriteria.AddCondition("targetentityname", ConditionOperator.Equal, relatedentity);
                relationshipMappings.LinkEntities.Add(entityMap);
    
                EntityCollection attributeMaps = service.RetrieveMultiple(relationshipMappings);
                List<InternalAttributeMap> attributeMappings = new List<InternalAttributeMap>();
                foreach (Entity attributeMap in attributeMaps.Entities)
                {
                    attributeMappings.Add(new InternalAttributeMap(attributeMap["sourceattributename"].ToString(), attributeMap["targetattributename"].ToString()));
                }
    
                return attributeMappings;
            }
    
            private static List<InternalAttributeMap> RemoveUnupdated(List<InternalAttributeMap> mappingAttributes, AttributeCollection tardetAttributes, string ReferencedAttribute)
            {
                List<InternalAttributeMap> clearAttributes = new List<InternalAttributeMap>();
    
                foreach (InternalAttributeMap attribute in mappingAttributes)
                    if (attribute.SourceAttributeName != ReferencedAttribute &&
                        tardetAttributes.Contains(attribute.SourceAttributeName))
                            clearAttributes.Add(attribute);
    
                return clearAttributes;
            }
    
            private static void UpdateChildEntities(IPluginExecutionContext context, IOrganizationService service, List<InternalAttributeMap> mappingAttributes, Entity target, string entity, string relationshipattribute)
            {
                // Формируем список полей, которые нужно вернуть из дочерних записей
                ColumnSet columnSetAttributes = new ColumnSet();
                foreach (InternalAttributeMap attribute in mappingAttributes)
                    columnSetAttributes.AddColumn(attribute.TargetAttributeName);
    
                // Запрашиваем дочерние записи
                QueryExpression childEntities = new QueryExpression(entity);
                childEntities.Criteria.AddCondition(relationshipattribute, ConditionOperator.Equal, context.PrimaryEntityId);
                childEntities.ColumnSet = columnSetAttributes;
                EntityCollection childEntitiesCollection = service.RetrieveMultiple(childEntities);
    
                // Обновляем каждую дочернюю запись
                foreach (Entity childEntity in childEntitiesCollection.Entities)
                {
                    foreach (InternalAttributeMap attribute in mappingAttributes)
                    {
                        childEntity[attribute.TargetAttributeName] = target.Attributes[attribute.SourceAttributeName];
                    }
                    service.Update(childEntity);
                }
            }
    
            // Вспомогательный класс
            internal class InternalAttributeMap
            {
                public InternalAttributeMap(string sourceattributename, string targetattributename)
                {
                    this.SourceAttributeName = sourceattributename;
                    this.TargetAttributeName = targetattributename;
                }
    
                public string SourceAttributeName { get; set; }
                public string TargetAttributeName { get; set; }
            }
        }
    }
    

    Здесь происходит следующее:

    • На входе мы парсим переданные во входном параметре имена связей 1:N;
    • И таргета обновления получаем все атрибуты, которые обновил пользователь;
    • Получаем метаданные всех связей 1:N;
    • Для каждой связи вытаскиваем атрибуты, настроенные для маппинга;
    • Из атрибутов маппинга отсеиваем только те, которые переданы в обновлении. Также исключаем атрибут, в котором хранится GUID родительской записи;
    • Вытаскиваем дочерние записи и обновляем их соответствующие поля данными из таргета.
  • Зарегистрируйте плагин на обновление нужного родительского объекта (в данном случае это Возможная сделка) и передайте на входе имя связи 1:N, для которой нужно осуществлять маппинг полей при обновлении.




З.Ы. Реализация довольно простая, но имеется пару недостатков, которые нужно доработать:

  • Если на входе будет передано две связи 1:N к одному и тому же объекту, и в них будут одинаковые поля, то одна и та же запись будет обновлена соответствующее количество раз;
  • Не все стандартные объекты CRM можно обновлять в неактивном Состоянии, поэтому нужно для них проводить дополнительную проверку.
Комментарии (0)

*

code