Friday, September 2, 2016

Allow Native Mapping When Referencing a Parent Entity

Normal Out of Box (OOB) CRM Relationship mapping can be extremely beneficial, but it has a very big “gotcha”.  They only work when creating a child entity from the parent form.  So if the user is on the Parent Entity Form, and adding a new entity via a Grid, then the mapping will automatically be populated for the user.  But what happens if you have an entity that is serving as a M:M entity (think Lead Products).   Generally, a user would create a Lead Product from the Lead Form.  This means if any mappings are setup from the Product to the Lead Product, they will not be applied.

There is no way for this to get applied natively, since the mapping takes place before the Lead Product is loaded, but the user doesn’t select a Product until after the Lead Product is loaded.  There is however, a simple partial work around.  Create a plugin that performs the mapping using what is defined in the OOB Field Mapping for the Relationship.  For extensibility, define the parent field(s) in the plugin step.  Below is a working example of such a plugin (utilizing the DLaB.Xrm.2016 NuGet Package).
public override void RegisterEvents()
{
    RegisteredEvents.AddRange(new RegisteredEventBuilder(PipelineStage.PreOperation, MessageType.Create).Build());
}
 
protected override void ExecuteInternal(ExtendedPluginContext context)
{
    if (UnsecureConfig == null)
    {
        context.Trace("No Fields listed in the Unsecure Configuration.  Nothing from which to initialize the entity!");
        return;
    }
    var target = context.GetTarget<Entity>();
    foreach (var field in UnsecureConfig.Split(new [] {",","|",Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries))
    {
        InitializeFromField(context, target, field);
    }
}
 
/// <summary>
/// Loads the configured mappings for the entity from the given field.  
/// Only attributes that do not exist in the target are set, and only if the field contains an EntityReference in the target
/// </summary>
/// <param name="context">The context.</param>
/// <param name="target">The target.</param>
/// <param name="field">The field.</param>
private void InitializeFromField(ExtendedPluginContext context, Entity target, string field)
{
    context.TraceFormat("Initializing from field {0}", field);
    var parent = target.GetAttributeValue<EntityReference>(field);
    if (parent == null)
    {
        context.TraceFormat("No Parent found for field {0}", field);
        return;
    }
    var mappedEntity = context.SystemOrganizationService.InitializeFrom(parent, target.LogicalName, TargetFieldType.ValidForCreate);
    foreach (var att in mappedEntity.Attributes.Where(a => !target.ContainsAllNonNull(a.Key)))
    {
        context.TraceFormat("Adding attribute {0}:{1}", att.Key, att.Value);
        target.Attributes.Add(att);
    }
}

The Details

  • The Plugin must be registered for each entity to be initialized
  • The Unsecure Configuration Settings must contain a comma (or pipe or newline) delimited list of parent lookup attributes to initialize the entity from. i.e. “ProductId”
  • The parent field lookup field must be populated on the initial save.
  • Only un-populated fields will be mapped.  If a user has defined a value already, it will not be overridden.
  • An end user will not see the mapped values on the form until after the initial save refresh occurs.
That’s it.  A few lines of code, and now your Entity Relations Field Mappings can be used in many more places!

2 comments:

Alagunellaikumar said...

Hi,
Could you please tell me how to write Native pipeline?
Also Could you please give me more detail about the below line,
There is no way for this to get applied natively, since the mapping takes place before the Lead Product is loaded, but the user doesn’t select a Product until after the Lead Product is loaded.

Daryl said...

Not sure what you mean by "write Native pipeline"

As far as your second question, the OOB Mapping functionality can't possibly work when selecting a parent entity from a child form, since the mapping takes place before the load of the form.

This post describes how you can create a plugin that will run on pre-create of the entity, and then manually performs the mapping. The difference to the user, is that they won't see the mapped values until after they first click save on the form.

Does that help?