Posted on 9. September 2013

Do you understand MergeOptions?

If you use LINQ queries with the OrganizationServiceContext then understanding MergeOptions is vital. At the end of this post I describe the most common 'gotcha' that comes from not fully understanding this setting.

The OrganizationServiceContext implements a version of the 'Unit of Work' pattern (http://martinfowler.com/eaaCatalog/unitOfWork.html ) that allows us to make multiple changes on the client and then submit with a single call to 'SaveChanges'. The MergeOption property alters the way that the OrganizationServiceContext handles the automatic tracking of objects when returned from queries. It is important to understand what's going on since by default LINQ queries may not return you the most recent version of the records from the server, but rather a 'stale' versions that is currently being tracked.

What is this 'Merge' they speak of?!

The SDK entry on MergeOptions talks about 'Client side changes being lost' during merges.

The term 'merge' is nothing to do with merging of contacts/leads/accounts – but describes what happens when the server is re-queried within an existing context and results from a previous query are returned rather than new copies of each record. It is a record ID based combination, not an attribute merge – so a record is either re-used from the current context, or a new instance is returned that represents the version on the server.

In order to describe the different options, consider the following code:

// Query 1
var contacts = (from c in context.ContactSet
                select new Contact
                {
                    ContactId = c.ContactId,
                    FirstName = c.FirstName,
                    LastName = c.LastName,
                    Address1_City = c.Address1_City

                }).Take(1).ToArray();

// Update 1
Contact contact1 = contacts[0];
contact1.Address1_City = DateTime.Now.ToLongTimeString();
context.UpdateObject(contact1);

// Query 2
var contacts2 = (from c in context.ContactSet
                 select c
          ).Take(2).ToArray();

// Update 2
var contact2 = contacts2[0];
contact2.Address1_City = DateTime.Now.ToLongTimeString();
context.UpdateObject(contact2);

// Save Changes
context.SaveChanges();

MergeOption.NoTracking

Perhaps the best place to start is the behaviour with no tracking at all.

  • Query 1 – Will return all matching contacts but not add them to the tracking list
  • Update 2 – Will throw and exception because the contact is not being tracked. You would need to use context.Attach(contact) to allow this update to happen
  • Query 2 – This query will pull down new copies of all contacts from the server include a new version of contact 1
  • Update 2 – We now have two version of the same contact with different city attribute value. The UpdateObject will fail without Attach first being called. If you attempt to attach contact2 after attaching contact1 you will receive the error 'The context is already tracking a different 'contact' entity with the same identity' because contact1 is already tracked and has the same ID.

MergeOption.AppendOnly (Default Setting)

When using the OrganizationServiceContext, by default it will track all objects that are returned from LINQ queries. This means that the second query will return the instance of the contacts that have already been returned from query 1. Critically this means that any changes made on the server between query 1 and query 2 (or any additional attributes queried using projection) will not be returned.

  • Query 1 – Will return all matching contacts and add them to the tracking list
  • Update 2 – Will succeed because the contact is being tracked
  • Query 2 – Will return the same instances that are already being tracked. The only records that will be returned from the server will be those that are not already being tracked. This is the meaning of 'AppendOnly'. The query still returns the data from the server, but the OrganizationServiceContext redirects the results to the instances already in the tracking list meaning that any changes made on the server since Query 1 will not be reflected in the results.
  • Update 2 – Will succeed since contact1 and contact2 are the same object. Calling UpdateObject on the same instance more than once is acceptable.

MergeOption.PreserveChanges

PreserveChanges is essentially the same as AppendOnly except:

  • Query 2 – Will return the same instances that are already being tracked provided they have an EntityState not equal to Unchanged. This means that contact2 will be the same instance as contac1 because it has been updated, but other instances in the contacts1 and contacts2 results will be new instances.

The result of this is that queries will not pick up the most recent changes on the server if a tracked version of that record has been edited in the current context.

MergeOption.OverwriteChanges

With a MergeOption of OverwriteChanges, the query behaviour will effectively be as per NoTracking however the tracking behaviour is like AppendOnly and PreserverChanges:

  • Query 1 – Will return all matching contacts and add each on to the tracking list (as per AppendOnly and PreserveChanges)
  • Update 2 – Will succeed because the contact is being tracked (as per AppendOnly and PreserveChanges)
  • Query 2 – This query will pull down new copies of all contacts from the server include a new version of contact 1 (as per NoTracking). Previously tracked contact1 will no longer be tracked, but the new version (contact2) will be.
  • Update 2 – Will succeed and the values on contact1 will be lost.

The MergeOption has a subtle but important effect on the OrganizationServiceContext, and without truly understanding each setting you might see unexpected results if you stick with the default 'AppendOnly'. For instance, you might update the value on the server between queries, but because a record is already tracked, re-querying will not bring down the latest values. Remember that all of this behaviour only is true for the same context – so if you are creating a new context then any previously tracked/modified records will no longer be tracked.

LINQ Projection 'Gotcha'

The most common issue I see from not fully understanding MergeOptions (and yes I made this mistake too! Smile) is the use of the default AppendOnly setting in conjunction with LINQ projection. In our code example Query 1 returns a projected version of the contact that only contains 4 attributes. When we re-query in Query 2 we might expect to see all attribute values but because we are already tracking the contacts our query will only return the previously queried 4 attributes! This can hide data from your code and cause some very unexpected results!

In these circumstances, unless you really need tracking and fully understand MergeOptions, I recommend changing the MergeOptions to 'NoTracking'.

@ScottDurow

Posted on 12. January 2013

getServerUrl is Deprecated. Use getClientUrl instead.

Although at the time of writing UR12 is not yet released - I was checking though the changes in the latest SDK documentation.

In addition to the Ribbon Workbench being listed (yay!) I noticed the following statement about the getServerUrl function

"Deprecated. Use getClientUrl instead. This method is deprecated as of Microsoft Dynamics CRM 2011 Update Rollup 12 and the Microsoft Dynamics CRM December 2012 Service Update."

 

Out with the old

 

Using getServerUrl on it's own always had the potential to cause 'Access Denied' messages if the browser domain Url was different to the server Url stored in the CRM database. This was to do with cross domain access restrictions in IE. A work around was to use a method similar to Daniel Cai gives in his post on the subject - http://danielcai.blogspot.co.uk/2012/02/get-right-server-url-in-your-crm-client.html

In with the new

The SDK described the new getClientUrl as:

"Returns the base URL that was used to access the application."

The function nolonger blindly return the server Url in the database - but looks at the url that was used to access CRM so that cross domain access issues will be a thing of the past!

Read more in the SDK:
http://msdn.microsoft.com/en-us/library/d7d0b052-abca-4f81-9b86-0b9dc5e62a66#BKMK_getClientUrl

@ScottDurow

 

Posted on 15. December 2010

Things Dynamics CRM 4.0 Developers must know about CRM 2011 #1:

Nulls are handled differently by the SDK Web services:

Using the CRM 4.0 SDK webservice and pipeline context, you could always exclude an attribute from an update by setting it to null. This has now changed:

CRM 4.0:

entity.attributename = null;

CRM 2011:

entity.Remove("attributename");

 

Data loss when using the RESTfull endpoint

When using the CRM 4.0 SDK  Webservices, you could choose to only return certain attribute values and then when updating only update those values provided whilst leaving the rest the same as they were on the server. This is very desirable to facilitate concurrency (we don't want to send any update to an attribute if we are not actually updating it) and to minimise the traffic between the client and server (don't send/return every attribute with each call).

In CRM2011 however using the OData endpoint this is no longer possible. All values must be retrieved and re-set when updating.

For example, we can use projection onto an Entity to manipulate the $select on the OrganisationData.svc:

 var queryVar = from c in _context.ContactSet
                  where c.FullName.Contains(criteria)
                  select new Contact {
                    FullName = c.FullName,
                    Telephone1 = c.Telephone1,
                    EMailAddress1 = c.EMailAddress1,
                    FirstName = c.FirstName,
                    LastName = c.LastName,
                    Address1_Line1 = c.Address1_Line1,
                    Address1_City = c.Address1_City,
                    Address1_StateOrProvince = c.Address1_StateOrProvince,
                    Address1_PostalCode = c.Address1_PostalCode
                };

This code uses the following GET:

http://{server}:5555/{orgname}/xrmservices/2011/organizationdata.svc/ContactSet()?$filter=FullName%20eq%20'{crtieria}'&$select=FullName,Telephone1,EMailAddress1,etc...

This will only pull down the attributes we need to display.

All good so far.

However, when using the DataContext to update the record on the server using:

_context.BeginSaveChanges(OnSaveContactsComplete, TheMainViewModel.Contacts);
...

We find that all the data that we didn't include in the $select is now nulled out.

This is a common problem with ADO.Net data services and is indeed detailed as "Data loss might occur in the data service when you save updates that were made to projected types." in http://msdn.microsoft.com/en-us/library/ee473425.aspx

This seems like a big flaw to the OData endpoint - in that it forces us to retrieve all values if we want to make any updates at all via the Client OData API. This might be a good reason to avoid the RESTful endpoint unless you know up front that you will never be updating data. The overhead having to retrieving *all* values before performing an update is not something that you want.

 

There is always an exception...

Since JScript RESTful operations use the JSON notation to send data to the server rather than the OData framework, we can perform partial updates as shown by this JScript in the SDK example 'JScriptRestDataOperations:

function updateAccountRecord(Id) {

 var updateAccountReq = new XMLHttpRequest();
 var changes = new Object();
 changes.Name = "Updated Sample";
 changes.Telephone1 = "555-0123";
 changes.AccountNumber = "ABCDEFGHIJ";
 changes.EMailAddress1 = "someone1@example.com";

 updateAccountReq.open("POST", ODataPath + "/AccountSet(guid'" + Id + "')", true);
 updateAccountReq.setRequestHeader("Accept", "application/json");
 updateAccountReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
 updateAccountReq.setRequestHeader("X-HTTP-Method", "MERGE");
 updateAccountReq.onreadystatechange = function () {
  updateAccountReqCallBack(this, Id);
 };
 updateAccountReq.send(JSON.stringify(changes));
}

This code will only update the Name, Telephone1, AccountNumber and EmailAddress1 attributes, and leave the rest unchanged. Note that the attribute names are Case Sensitive.