
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:
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 | usingMicrosoft.Xrm.Sdk;usingMicrosoft.Xrm.Sdk.Extensions;usingMicrosoft.Xrm.Sdk.Query;usingSystem;usingSystem.Linq;namespaceBlogPackage{ publicclassPreCreateParent : PluginBase { publicPreCreateParent() : base(typeof(PreCreateParent)) { } protectedoverridevoidExecuteDataversePlugin(ILocalPluginContext localPluginContext) { vartarget = localPluginContext.PluginExecutionContext.InputParameterOrDefault<Entity>("Target"); target["tmy_number"] = target.GetAttributeValue<string>("tmy_number") + " PreCreateParent"; vartotalAmount = GetChilds(localPluginContext.PluginUserService, target.Id) .Sum(e => e.GetAttributeValue<int>("tmy_qty") * e.GetAttributeValue<Money>("tmy_price")?.Value ?? 0m); target["tmy_total"] = newMoney(totalAmount); localPluginContext.Trace($"PreCreateParent {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}. Parents Text: {target.GetAttributeValue<string>("tmy_parentstext")}. Total {totalAmount}."); } privateEntity[] GetChilds(IOrganizationService service, Guid id) { varquery = newQueryExpression("tmy_child") { ColumnSet = newColumnSet("tmy_qty", "tmy_price"), NoLock = true }; query.Criteria.AddCondition("tmy_parentid", ConditionOperator.Equal, id); varresult = service.RetrieveMultiple(query); returnresult.Entities.ToArray(); } } publicclassPreCreateMultipleParent : PluginBase { publicPreCreateMultipleParent() : base(typeof(PreCreateMultipleParent)) { } protectedoverridevoidExecuteDataversePlugin(ILocalPluginContext localPluginContext) { vartargets = localPluginContext.PluginExecutionContext.InputParameterOrDefault<EntityCollection>("Targets"); varvalid = targets != null&& targets.Entities.Any(); if(!valid) return; localPluginContext.Trace($"Start PreCreateMultipleParent {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}.."); foreach(vartarget intargets.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}.."); } } publicclassPreCreateChild : PluginBase { publicPreCreateChild() : base(typeof(PreCreateChild)) { } protectedoverridevoidExecuteDataversePlugin(ILocalPluginContext localPluginContext) { vartarget = 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}"); } } publicclassPreCreateMultipleChild : PluginBase { publicPreCreateMultipleChild() : base(typeof(PreCreateMultipleChild)) { } protectedoverridevoidExecuteDataversePlugin(ILocalPluginContext localPluginContext) { vartargets = localPluginContext.PluginExecutionContext.InputParameterOrDefault<EntityCollection>("Targets"); varvalid = targets != null&& targets.Entities.Any(); if(!valid) return; localPluginContext.Trace($"Start PreCreateMultipleChild {DateTime.Now:MM/dd/yyyy hh:mm:ss.fff tt}.."); foreach(vartarget intargets.Entities) { target["tmy_number"] = target.GetAttributeValue<string>("tmy_number") + " PreCreateMultipleChild"; } varparentInput = localPluginContext.PluginExecutionContext.ParentContext.InputParameterOrDefault<Entity>("Target"); foreach(varattr inparentInput.Attributes) { localPluginContext.Trace($"{attr.Key} - {attr.Value}"); } vardata = 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(varparentGroup indata) { if(parentGroup.Key == null) continue; vartotal = parentGroup.Sum(e => e.Qty * e.Price); varparent = newEntity("tmy_parent", parentGroup.Key.GetValueOrDefault()) { ["tmy_total"] = newMoney(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:
- MultipleCreate vs Create. Which one will trigger first?
- 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).
- Parent vs Child, which one will be created first
Once the above plugins are deployed to my environment. Then I created the necessary steps:

Plugin steps registered
Advertisement
Again, I’m using exe to trigger the creation of the entity:
| 123456789101112131415161718192021222324252627282930313233343536 | varcontact = newContact{ FirstName = "Temmy", LastName = "Raharjo"};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
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
Last, which one runs first between Parent vs Child:

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
