April 4, 2017

GetAttributeValue<object>, why not?

Today I wrote a small piece of code in order to replicate the exact same data from a CRM instance to another keeping the same ID, it was an exercise because I had to copy only a couple of entities, otherwise I could use one of the several tools made for this kind of operation (like KingswaySoft).

While I was writing the code I arrived to the point where I need to map the fields, so I started to write the usual GetAttributeValue<string>, GetAttributeValue<EntityReference>, ...
And I asked myself:
"source and target attributes are always of the same type, what if I use object for the Generic?"
"something like newEntity["name"] = oldEntity.GetAttributeValue<object>("name"); works?"

With a bit of surprise I found that actually works, so in the end I wrote this piece of code.
Some notes:
  • It uses CrmServiceClient, because there are two instances (source and target) one of them should contains the option RequireNewInstance = true;
  • The UpsertRequest works also when the entity has the ID defined, and not only when the entity is defined using an alternate key syntax
  • As suggested by Tinus Smith in his comment, if the purpose is to do an exact copy, it's not necessary to create the entityTarget and copy its values with GetAttributeValue<object>, instead the entitySource can be used directly inside the Target property of the UpdateRequest:
    UpsertResponse response = (UpsertResponse)target.Execute(new UpsertRequest { Target = entitySource });
MigrateEntity(source, target, "new_entity", new List { "new_name", "new_date", "new_lookupid" });

private static void MigrateEntity(CrmServiceClient source, CrmServiceClient target,
                                  string entityName, List<string> columns)
{
    QueryExpression querySource = new QueryExpression(entityName);
    querySource.ColumnSet = new ColumnSet(columns.ToArray());
    EntityCollection collSource = source.RetrieveMultiple(querySource);
    foreach (Entity entitySource in collSource.Entities)
    {
        try
        {
            Entity entityTarget = new Entity(entityName);
            entityTarget.Id = entitySource.Id;
            foreach (string column in columns)
            {
                entityTarget[column] = entitySource.GetAttributeValue<object>(column);
            }
            UpsertResponse response = (UpsertResponse)target.Execute(new UpsertRequest { Target = entityTarget });
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

6 comments:

  1. For someone that is such an advocate for late bound, how have you not known this? You could even write an extension method on Entity that removes the Generic Type all together and just calls the object version of the GetAttributeValue under the covers.

    ReplyDelete
    Replies
    1. I like late bound but I also like to know the types of the fields I'm working on :)

      Delete
    2. Guido, that comment either got Daryl laughing so loud, or crying in despair. I couldn't guess which though...
      But I agree with you ;)

      Delete
  2. Perhaps this is a silly question, but why not use:

    UpsertResponse response = (UpsertResponse)target.Execute(new UpsertRequest { Target = entitySource });

    Thereby eliminating the need to copy Entity objects?

    ReplyDelete
    Replies
    1. Hi Tinus, you are absolutely right, when you need to copy the same values we do with the syntax you wrote (of course a specific list of columns needs to be defined always with this code). I will update my code. Thanks!

      Delete