Sunday, 10 May 2015

Tips: CRM C# Create Validated Parent and Child Records at Once as one Big Compound using ‘Related Entities’

Business Requirement

Often, we have requirement to create records which are having Parent and Child Relationship.

For example, Transaction and Transaction Detail (custom entities), Quote and Quote Product, Account/Contact and Activities (Task, Letter, Phone Call, etc).

Commonly, we will create 2 requests, one for Parent (create) and another one to create Child record.

Have you imagined that during the creation of the Parent and Child, the Parent is successfully created, but the Child is not. A good action on this is doing the rollback, otherwise, you will be confused, where is the detail of this, imagine, A Quote without Product, a Header without Detail, in fact Detail is very important in your Business Requirement.

Often we are faced with situation that details are even more important than the header, because header is just summary or just a ‘virtual place’, contrived to group it and store the relationship, while per detail line item is the most important data.

To know the purpose of my post, let’s start this scenario.

Scenario

I need to create an Account with 5 Letters.

My Point is, I receive 5 letters from the same company (Dynamics Bank), but in my database, there is no that Account data, so based the received 5 letters, I want to create the Account.

As we know, Account is the Parent of the Activities and I would like to create the Dynamics Bank before linked it to the letters.

So, I use this code

Code #1

I use the common code..
(I use Late Bound, you can change to Early Bound as well)

private void CreateAccountandLetters(IOrganizationService _service)
{
    try
    {   Guid guidCreatedAccount = Guid.Empty;
        //Account
        Entity accountToBeCreated = new Entity("account");
        accountToBeCreated["name"] = "Dynamics Bank";
        guidCreatedAccount = _service.Create(accountToBeCreated);

        //Define Letters (5 letters)
        /Make sure that will link to accountId
        if(guidCreatedAccount != Guid.Empty)
        for (int i = 1; i <= 5; i++)
        {
            Entity eLetter = new Entity("letter");
            eLetter["subject"] = string.Format("Company Letter {0}", i.ToString());
            //make it linked to the newly created Account..
            eLetter["regardingobjectid"] = new EntityReference("account", guidCreatedAccount);
            //create the Letter with Account related
            _service.Create(eLetter);
        }                            
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

My method here is I create the parent, then inside the for looping, I create child one by one.

Everything is okay, since I can see the Account and 5 Letters.

image

Because that is very simple one, now let’s add one more attribute to the Letter, duration for example.

Code #2

I will add new attribute and I purposely put a mistake to make sure that one of the child will not be created.

private void CreateAccountandLetters(IOrganizationService _service)
{
    try
    {
        Guid guidCreatedAccount = Guid.Empty;

        //Account
        Entity accountToBeCreated = new Entity("account");
        accountToBeCreated["name"] = "Dynamics Bank";
        guidCreatedAccount = _service.Create(accountToBeCreated);

        //Define Letters (5 letters)
        //Make sure that will link to accountId
        if(guidCreatedAccount != Guid.Empty)
        for (int i = 1; i <= 5; i++)
        {
            Entity eLetter = new Entity("letter");
            eLetter["subject"] = string.Format("Company Letter {0}", i.ToString());
            //make it linked to the newly created Account..
            eLetter["regardingobjectid"] = new EntityReference("account", guidCreatedAccount);                

            //put a mistake here
            if (i == 5)
            {
                eLetter["scheduledstart"] = "today";
                //it should accepts datetime only, since it is a datetime field
            }

            //create the Letter with Account related
            _service.Create(eLetter);
        }                          
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

Please notice the eLetter["scheduledstart"] = "today";

As we can see, I receive an expected error.

image

Because the attribute value is expecting Datetime type, not a string.

Now, let’s we see the result..

image

Well, you can see that 1 Account: Dynamics Bank is still created and other 4 letter records are also created.

In fact, this is not what we want! This is not a perfect solution.

We want to create 1 Account with 5 Letters with 100% fully successfully or not at all!

Since we use the method by creating the record, 1 by one, we will have this problem.

*If you notice we have called at least 6 Create request for this, and if one the children is failed, we can still see the others.

Now, what is the solution?

After though reading, I found this article..

https://msdn.microsoft.com/en-us/library/gg309282.aspx

It was using the CompoundRequest method, which was deprecated, replaced by standard create or update with related entities property.

Code #Related Entities

You can do a rollback by creating additional custom code to delete the Account + 4 Letter records in the end.

But, actually, we have better solution, that is using the Compound Request method and utilizing the Relationship object.

Here is the code:

private void CreateRecordsWithRelatedEntities(IOrganizationService _service)
{
    try
    {
        //Define the account for which we will add letters                
        //Account
        Entity accountToBeCreated = new Entity("account");
        accountToBeCreated["name"] = "Dynamics Bank";

        //This acts as a container for each letter we create. Note that we haven't
        //define the relationship between the letter and account yet.
        EntityCollection relatedLettersToBeCreated = new EntityCollection();
        for (int i = 1; i <= 5; i++)
        {
            Entity eLetter = new Entity("letter");
            eLetter["subject"] = string.Format("Company Letter {0}", i.ToString());

            //put a mistake here
            if (i == 5)
            {
                eLetter["scheduledstart"] = "today";
                //it should accepts datetime only, since it is a datetime field
            }

            //bind to the EntityCollection of the related records
            relatedLettersToBeCreated.Entities.Add(eLetter);
        }

        //Creates the reference between which relationship between Letter and
        //Account we would like to use.
        Relationship letterRelationship = new Relationship("Account_Letters");

        //Adds the letters to the account under the specified relationship
        accountToBeCreated.RelatedEntities.Add(letterRelationship, relatedLettersToBeCreated);

        //Passes the Account (which contains the letters)
        _accountId = _service.Create(accountToBeCreated);
        //and guess, you only need 1 request for all records!!
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

And as you can see, we got the expected error (same error, actually)

image

And here is the result

image

*There is no Account created, nor any Letter records as well..

Because, in fact, they are 1 big compound, once one is failed, it will totally make others failed as well.

Now, let’s make no mistake and let those records created successfully, i just remove the lines making it failed.

image


Result:

image

Now, those 6 records are 100% created.

This method is very useful if you are required to focus to the big compound and 100% completeness is your absolute goal.

Imagine that you will have a trouble and headached if you need to create multiple records and make sure they are created in the same time, while in fact, one mistake makes one record not created..

Then, you need to do a rollback!! How many new lines of code you need to do checking for rollback? While you can make them as the big one rock that is not separable.

Here is the complete code:

private void CreateRecordsWithRelatedEntities(IOrganizationService _service)
{
    try
    {
        //Define the account for which we will add letters                
        //Account
        Entity accountToBeCreated = new Entity("account");
        accountToBeCreated["name"] = "Dynamics Bank";

        //This acts as a container for each letter we create. Note that we haven't
        //define the relationship between the letter and account yet.
        EntityCollection relatedLettersToBeCreated = new EntityCollection();
        for (int i = 1; i <= 5; i++)
        {
            Entity eLetter = new Entity("letter");
            eLetter["subject"] = string.Format("Company Letter {0}", i.ToString());

            //bind to the EntityCollection of the related records
            relatedLettersToBeCreated.Entities.Add(eLetter);
        }

        //Creates the reference between which relationship between Letter and
        //Account we would like to use.
        Relationship letterRelationship = new Relationship("Account_Letters");

        //Adds the letters to the account under the specified relationship
        accountToBeCreated.RelatedEntities.Add(letterRelationship, relatedLettersToBeCreated);

        //Passes the Account (which contains the letters)
        _accountId = _service.Create(accountToBeCreated);
        //and guess, you only need 1 request for all records!!
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

The most important part to identify a compound is this:

//Adds the letters to the account under the specified relationship
 accountToBeCreated.RelatedEntities.Add(letterRelationship, relatedLettersToBeCreated);

While you need to defined the Relationship Name and the Entity Collection of the related entities.

With this method, you can validate the united records as one big compound, either 100% created or 0% created..

For me, I have requirement to make sure that between Header and Details are always in sync, so yes, I need the 100% not a partial compound.

So, I found this method is very useful and this method might not be popularized explicitly. Thus, I want to introduce and share it out, to make this method more useful.

Hope this helps you!

Thanks.

6 comments:

  1. Does using compound also work in the situation when doing a service update to an existing parent record and create NEW linked entity reference? Does it update the parent record and create new related records?

    ReplyDelete
    Replies
    1. Hm, I dont think it will work for 2 different messages: Update & Create
      Coz it will in the end call only 1 request, see my code:
      //Passes the Account (which contains the letters)
      _accountId = _service.Create(accountToBeCreated);
      //and guess, you only need 1 request for all records!!

      So, your case would be
      //Passes the Account (which contains the letters)
      _accountId = _service.Update(accountToBeCreated); //HERE IS YOUR UPDATE
      //and guess, you only need 1 request for all records!!

      In fact, the children are not yet created, but you are triggering Update.
      Well, I havent ried, have you tried it Whitey?

      Delete
  2. Nice article,
    i want to ask you, is this method work to link parent and child coming from the same entity.

    Example: I want to create parent entity from "new_product" entity. In new product there is "new_code" field. If the record has code alfabeth, then its parent. But if code is numerik then its child. Is it possible to use your method for this case ?

    ReplyDelete
    Replies
    1. Hi Fikri,

      My prediction, that should work as long they are parent-child.

      Delete
  3. hi aileen. i understand this is an old post. but i'm wondering if i can do this OOB in a composite form.
    say we have entity A and entity B and they have N:N relationship.
    and i have a composite form in which entity A is dominant and i have a subgrid which contains many entity B.

    the question is :
    can i create entity A using this composite form and create related entity B at the same time?

    many thanks,.

    ReplyDelete
    Replies
    1. Hi Phil, sorry just saw your comment. What do you mean by composite form? is it the CRM Form (UI) or you mean something else? thanks :)

      Delete

My Name is..