DATAVERSE: HOW THE RELATEDENTITIES PLUGIN WORKS

Last week we learned how the RelatedEntity’s performance compares to normal creation. We will create plugins for today’s blog post and inspect how it works. For the side topic, we will also cover CreateMultiple and see if we can make the customizations better in terms of performance. Let’s go!

Here are the plugins that I’ll cover here:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Extensions;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
namespace BlogPackage
{
    public class PreCreateParent : PluginBase
    {
        public PreCreateParent() : base(typeof(PreCreateParent))
        {
        }
        protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
        {
            var target = localPluginContext.PluginExecutionContext.InputParameterOrDefault<Entity>("Target");
            target["tmy_number"] = target.GetAttributeValue<string>("tmy_number") + " PreCreateParent";
            var totalAmount = GetChilds(localPluginContext.PluginUserService, target.Id)
                .Sum(e => e.GetAttributeValue<int>("tmy_qty") * e.GetAttributeValue<Money>("tmy_price")?.Value ?? 0m);
            target["tmy_total"] = new Money(totalAmount);
            localPluginContext.Trace($"PreCreateParent {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}. Parents Text: {target.GetAttributeValue<string>("tmy_parentstext")}. Total {totalAmount}.");
        }
        private Entity[] GetChilds(IOrganizationService service, Guid id)
        {
            var query = new QueryExpression("tmy_child")
            {
                ColumnSet = new ColumnSet("tmy_qty", "tmy_price"),
                NoLock = true
            };
            query.Criteria.AddCondition("tmy_parentid", ConditionOperator.Equal, id);
            var result = service.RetrieveMultiple(query);
            return result.Entities.ToArray();
        }
    }
    public class PreCreateMultipleParent : PluginBase
    {
        public PreCreateMultipleParent() : base(typeof(PreCreateMultipleParent))
        {
        }
        protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
        {
            var targets = localPluginContext.PluginExecutionContext.InputParameterOrDefault<EntityCollection>("Targets");
            var valid = targets != null && targets.Entities.Any();
            if (!valid) return;
            localPluginContext.Trace($"Start PreCreateMultipleParent {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}..");
            foreach (var target in targets.Entities)
            {
                target["tmy_number"] = target.GetAttributeValue<string>("tmy_number") + " PreCreateMultipleParent";
                target["tmy_parentstext"] = target.GetAttributeValue<string>("tmy_number");
            }
            localPluginContext.Trace($"End PreCreateMultipleParent {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}..");
        }
    }
    public class PreCreateChild : PluginBase
    {
        public PreCreateChild() : base(typeof(PreCreateChild))
        {
        }
        protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
        {
            var target = localPluginContext.PluginExecutionContext.InputParameterOrDefault<Entity>("Target");
            target["tmy_number"] = target.GetAttributeValue<string>("tmy_number") + " PreCreateChild";
            localPluginContext.Trace($"PreCreateChild {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}. Total: {target.GetAttributeValue<Money>("tmy_total")?.Value}");
        }
    }
    public class PreCreateMultipleChild : PluginBase
    {
        public PreCreateMultipleChild() : base(typeof(PreCreateMultipleChild))
        {
        }
        protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
        {
            var targets = localPluginContext.PluginExecutionContext.InputParameterOrDefault<EntityCollection>("Targets");
            var valid = targets != null && targets.Entities.Any();
            if (!valid) return;
            localPluginContext.Trace($"Start PreCreateMultipleChild {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}..");
            foreach (var target in targets.Entities)
            {
                target["tmy_number"] = target.GetAttributeValue<string>("tmy_number") + " PreCreateMultipleChild";
            }
            var parentInput = localPluginContext.PluginExecutionContext.ParentContext.InputParameterOrDefault<Entity>("Target");
            foreach (var attr in parentInput.Attributes)
            {
                localPluginContext.Trace($"{attr.Key} - {attr.Value}");
            }
            var data = targets.Entities.Select(e => new
            {
                ParentId = e.GetAttributeValue<EntityReference>("tmy_parentid")?.Id,
                Qty = e.GetAttributeValue<int>("tmy_qty"),
                Price = e.GetAttributeValue<Money>("tmy_price")?.Value ?? 0m
            }).GroupBy(e => e.ParentId).ToArray();
            localPluginContext.Trace($"Test {targets.Entities.Count}.. {data.Length}.. {data.FirstOrDefault()?.Key}..");
            foreach (var parentGroup in data)
            {
                if (parentGroup.Key == null) continue;
                var total = parentGroup.Sum(e => e.Qty * e.Price);
                var parent = new Entity("tmy_parent", parentGroup.Key.GetValueOrDefault())
                {
                    ["tmy_total"] = new Money(total)
                };
                localPluginContext.PluginUserService.Update(parent);
                localPluginContext.Trace($"Parent {parentGroup.Key} SUM {total}..");
            }
            localPluginContext.Trace($"End PreCreateMultipleChild {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}..");
        }
    }
}

If you are wondering what I’m doing on the above plugins, basically what I want to proof are:

  1. MultipleCreate vs Create. Which one will trigger first?
  2. If MultipleCreate can be used with the RelatedEntities. So we can have options to make the performance better (instead of relying on logic on Create, we can have options to call the logic once in CreateMultiple).
  3. Parent vs Child, which one will be created first

Once the above plugins are deployed to my environment. Then I created the necessary steps:

Registered plugin and plugin steps

Plugin steps registered

Advertisement

Again, I’m using exe to trigger the creation of the entity:

123456789101112131415161718192021222324252627282930313233343536varcontact = newContact{    FirstName = "Powerapps",    LastName = "Ninja"};varrelationshipParent_Child = newRelationship(nameof(tmy_Parent.tmy_child_ParentId_tmy_parent));varlistParent = newList<tmy_Parent>();for(inti = 0; i < 2; i++){    varlistChild = newList<tmy_Child>();    varparent = newtmy_Parent    {        tmy_number = $"Parent {i}"    };    for(inty = 0; y < 5; y++)    {        varchild = newtmy_Child        {            tmy_number = $"Child-{i}-{y}",            tmy_Qty = 2,            tmy_Price = newMoney(10)        };        listChild.Add(child);    }    parent.RelatedEntities[relationshipParent_Child] = newEntityCollection(listChild.ToArray())    {        EntityName = tmy_Child.EntityLogicalName    };    listParent.Add(parent);}varrelationshipContact_Parent = newRelationship(nameof(Contact.tmy_parent_ContactId_contact));contact.RelatedEntities[relationshipContact_Parent] = newEntityCollection(listParent.ToArray()){    EntityName = tmy_Parent.EntityLogicalName};service.Create(contact);

Once the records are created, here is the result:

Trace Log that shows that PreCreateChild running first compared to PreCreateMultiple

Trace Log that shows that PreCreateChild running first compared to PreCreateMultiple

PreCreate operation will be run first, and only after that the PreMultipleCreate (please note that Microsoft will gradually force us to use the MultipleCreate instead of Create).

From the record itself also you can see that the “Number” is set as “PreCreateChild” first only “PostCreateChild“:

Result from Grid

Advertisement

On PreCreateMultipleChild we actually have logic to SUM all the children Qty * Total in hopes that the RelatedEntities method will perform the creation as a collection. But, unfortunately, the system will create the rows one by one (same behavior with service.Create(entity)). Hence, if you observe in the Plugin Execution Logs, it will still generate 1 by 1 of each instance of PreCreate and PreCreateMultiple:

Plugin Trace Logs

Another finding is also the PreCreateMultiple is not detecting the ParentId ref:

Parent ID is not detected in the Child CreateMultiple

Parent ID is not detected in the Child CreateMultiple

Last, which one runs first between Parent vs Child:

Parent Entity will be created first, only the Child Entity

Parent Entity will be created first, only the Child Entity

The below results are still in line with my logic. The system will still need to create the Parent first, only the Child.

DATAVERSE: HOW THE RELATEDENTITIES PLUGIN WORKS

https://www.youtube.com/@powerappsninja

Leave a Comment

Your email address will not be published. Required fields are marked *