Pogo69's Blog

May 20, 2011

CRM 2011 LINQ – Entity Instance Tracking

Filed under: Cutting Code — pogo69 [Pat Janes] @ 10:11

An interesting query came up in the CRM Development Forum on MSDN recently:

http://social.msdn.microsoft.com/Forums/en-US/crmdevelopment/thread/83d49d47-1fc0-4181-99c6-229b658c99f3

which I will repeat here for context:

I currently have a plugin that triggers anytime a user creates, updates, or deletes a record out of a custom entity named Conflict of Interest. The plugin will update a flag on the contact record that lets a user know if there is an active conflict of interest. When I try to run the linq update I get an error “The context is not currently tracking the ‘contact’ entity“.

This sets up the IOrganizationService:

IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

// This function creates the linq query to update the contact record
public void SetActor_ConflictOfInterest(IOrganizationService service, Guid ActorGUID)
    {
      Utilities util = new Utilities();
      Guid BillingHeader = Guid.Empty;
      var xrm = new XrmServiceContext(service);

      var Actor = new Xrm.Contact
      {
        ContactId = ActorGUID,
        new_ConflictofInterest = true,
      };
      xrm.UpdateObject(Actor);
      xrm.SaveChanges();
    }

I’ve tried updating the record with a sql query as well, but CRM is locking the contact record for some reason. Any ideas on the linq error and why CRM is locking the contact record?

I’d never seen the error before, so I set off to see if I could replicate it.  It was not difficult:

var xrm = new Xrm2011.XrmServiceContext("Xrm");

Guid ActorGUID = (from c in xrm.ContactSet where c.FullName == "aa test 2" select c.Id).FirstOrDefault();

xrm.ClearChanges();

Xrm2011.Contact Actor = new Xrm2011.Contact
{
	Id = ActorGUID,
	DoNotEMail = true
};

xrm.UpdateObject(Actor);
xrm.SaveChanges();</pre>
<div>
<pre>

And bingo!!  As soon as I execute the .UpdateObject() method, I receive the error:

The context is not currently tracking the ‘contact’ entity.

According to my  reading through the SDK, we’re being told that the XRM LINQ Context is not currently keeping track of the changes in our ‘contact’ entity instance (identified by the GUID that we have assigned to our new Xrm2011.Contact object).

Ordinarily when interacting with the CRM via the XRM context, we follow a pattern:

  1. Retrieve entity instance(s)
  2. Update entity instance(s)
  3. Save

OrganizationServiceContext.Attach()

But in this case we’re trying to update an entity instance by creating its representation from scratch; so the context knows nothing about it.  The solution is the context.Attach() method:

Guid ActorGUID = (from c in xrm.ContactSet where c.FullName == "aa test 2" select c.Id).FirstOrDefault();

Xrm2011.Contact Actor = new Xrm2011.Contact
{
	Id = ActorGUID,
	DoNotEMail = true
};

xrm.Attach(Actor);
xrm.UpdateObject(Actor);
xrm.SaveChanges();

which attaches our ‘contact’ entity instance to the context, so that it can track the changes.  So we run the code and we’re greeted with this:

The context is already tracking a different ‘contact’ entity with the same identity.

What’s going on?  First, we’re not tracking the ‘contact’ and now we are?  But not?

What happens is this:

  1. Our initial LINQ query retrieves the GUID identifier for the ‘contact’ entity instance we’re interested in.  This tracks the ‘contact’ in context so that the context can keep up to date with any changes.  However, because we are only extracting the GUID, we’re throwing away our reference to the ‘contact’ entity instance that it being tracked.
  2. We construct our ‘contact’ entity instance and set whichever attributes are of relevance.
  3. We attempt to attach our ‘contact’ entity instance to the context.  However, this entity refers to the same entity instance (that is, identified by the same GUID) that was already added to context by our query in (1).  Hence, the error.

So, what do we do now?

There are two solutions in this case:

  1. Keep hold of the original entity instance reference; return the instance and not just the GUID.
  2. Remove the original entity instance reference from context.  We can do this with either the .Detach() method or .ClearChanges() method.

OrganizationServiceContext.Detach()

This method will remove an entity instance from the context and stop tracking changes.  To use this method, you must have a reference to the entity instance.  So this would require a combination of options (1) and (2) above:
  1. Hold onto the original entity instance reference
  2. .Detach() it
  3. Create your new entity instance reference and .Attach()
  4. Update
  5. Save

OrganizationServiceContext.ClearChanges()

This method will remove ALL entity instances from context and stop tracking changes for ALL entity instances.  This will allow us to .Attach and .Update without holding onto the original entity instance:

Guid ActorGUID = (from c in xrm.ContactSet where c.FullName == "aa test 2" select c.Id).FirstOrDefault();

xrm.ClearChanges();

Xrm2011.Contact Actor = new Xrm2011.Contact
{
	Id = ActorGUID,
	DoNotEMail = true
};

xrm.Attach(Actor);
xrm.UpdateObject(Actor);
xrm.SaveChanges();

Conclusion

Which combination of tracking methods you use in your application will depend entirely upon what you need to accomplish, but armed with this knowledge at least you’ll know which tools you have at your disposal.

Advertisements

10 Comments »

  1. Good post…It helped me alot 🙂

    Comment by Rajeev Pentyala — January 3, 2012 @ 17:17

  2. I had this exact issue… but your solution didn’t fix it.

    Exception:
    An error occured while processing this request.
    at Microsoft.Xrm.Sdk.Client.OrganizationServiceContext.SaveChanges(SaveChangesOptions options)
    at CrmPlugins.Opportunity.UpdateContactFromDetails(IOrganizationService service, Guid id, String strLogFile)
    An unexpected error occurred.

    Still getting this. Any other ideas?

    Comment by Mark McGookin — March 7, 2012 @ 01:04

    • Hi Mark,

      Apologies for the late reply.

      If you are still experiencing the issue:

      1. Enable tracing – it will give you more in-depth error information
      2. Interrogate resultant trace log files
      3. Debug as required

      There is insufficient information in the snippet you provided to elicit the root cause of the error.

      Comment by pogo69 — March 12, 2012 @ 12:12

  3. Try stick to this approach:
    1) select just the attributes you want to change from context, along with id (trick here, see below)
    2) update that attributes
    3) updateobject()
    4) savechanges()

    at step 1), create the object you’re changing with just the attributes you have selected populated.
    ie.
    var obj = (from o in ctx.object where (…) select new object() { attr1=o.attr1, attr2=o.attr2, id=o.id}).FirstOrDefault();
    obj.attr1 = attr1valueyouwant
    obj.attr2 = attr2valueyouwant
    ctx.updateobject(obj)
    ctx.SaveChanges();

    This way, you don’t have to detach the original read object and then create another object, attach it, then update it.

    Comment by Kenneth Leong — April 11, 2012 @ 01:25

    • Thanks Kenneth.

      I have been using much the same mechanisms in my recent work with the early-bound objects and *ServiceContext.

      This technique allows you to update attributes not originally retrieved; however, once the entity instance is being tracked, subsequent requests for attributes not originally retrieved return null, as they are being cached. Usage of the early-bound mechanisms is much more complex than it appears on the surface; and really does require planning ahead.

      I really should re-write and/or update this posting based on some other information gleaned since originally writing it, but it’s been tough enough lately for me to motivate myself into writing new posts. To be continued; but for now, hopefully people will read the comments as well as the post!

      Comment by pogo69 — April 11, 2012 @ 07:55

  4. can I get the name of attributes of an entity and assign them to array?
    ej: “array[] a = new Xrm2011.Contact.atribute,,,,,,” … something can be done so dynamically???
    thanks.

    Comment by diego — June 23, 2012 @ 00:46

    • Hi Diego,

      Yes, you can programmatically retrieve such a list with a metadata query:

      RetrieveEntityRequest req = new RetrieveEntityRequest
      {
      EntityFilters = Microsoft.Xrm.Sdk.Metadata.EntityFilters.Attributes,
      LogicalName = Contact.EntityLogicalName
      };

      RetrieveEntityResponse resp = (RetrieveEntityResponse)xrm.Execute(req);

      EntityMetadata metadata = resp.EntityMetadata;

      string[] attributeNames = metadata.Attributes.Aggregate(new string[0], (attr, item) =>
      {
      return attr.Concat(new string[] { item.LogicalName }).ToArray();
      });

      Or, you could use reflection to extract the Attribute Names from the definitions of the Early bound classes – I would recommend the metadata query as reflection is a potentially computationally expensive mechanism.

      –pat

      Comment by pogo69 — June 26, 2012 @ 10:38

  5. Hi pogo69,

    Thanks for the article, it’s very well explained and saved my time a lot.

    Cheers,

    Comment by pasargard — February 28, 2013 @ 15:35

  6. Still a valid article, helped me understand and fix a problem – thanks! Like you say there’s more going on with the developer extensions than at first meets the eye.

    Comment by philkermeen — March 19, 2015 @ 20:04


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: