Microsoft.Xrm.Client Part 1: CrmOrganizationServiceContext and when should I use it?

The Dynamics CRM SDK documentation is amongst the best I've seen for any business application. It provides numerous code examples and walk through ranging from the basics all the way through to advanced topics.

One of the areas that perhaps needs a little more clarification is the 'Developer Extensions' topic. This post is part 1 of a series on this SDK 'blind spot' so you can get the most out of this value part of the SDK.

The Microsoft.Xrm.Client namespace comes from the assembly of the same name 'microsoft.xrm.client.dll'. It is not available to plugin or workflow code and is designed specifically for use in Windows .NET clients or ASP.NET clients that communicate with Dynamics CRM.

It provides the following key features; each will be a topic of separate blog post.

  1. CrmSvcUtil & OrganizationServiceContext enhancements such as lazy loading
  2. Simplified Connection Management with Connection Dialog UI
  3. Client Side caching extensions
  4. Utility Extension functions for common tasks to speed up client development
  5. Organization Service Message utility functions to make it easy to call common messages such as BulkDelete, Add Member to Team etc.
  6. Objects to support the Microsoft.Xrm.Portal extensions

One area that I do get asked about quite frequently is the difference between the CrmOrganizationServiceContext and your common-all-garden OrganizationServiceContext. This question is the subject of this post.

Both of these classes implement the IOrganziationServiceContext interface and so once created they can be used interchangeably, but before you do this it is important to understand what is going on and why.

To use the CrmOrganizationServiceContext correctly, your journey should start with the Developer Extensions to the CrmSvcUtil. Rather than using the standard CrmSvcUtil flavour, you need to modify your command to include the following:

CrmSvcUtil.exe /codeCustomization:"Microsoft.Xrm.Client.CodeGeneration.CodeCustomization,Microsoft.Xrm.Client.CodeGeneration"  /url:http://<server>/<org>/XRMServices/2011/Organization.svc /out:"Xrm.cs" /namespace:Xrm /serviceContextName:XrmServiceContext

This will give you a very familiar looking early bound code file but with some critical differences:

  1. The Entity classes now inhertit from Microsoft.Xrm.Client.CrmEntity and not Microsoft.Xrm.Sdk.Entity
  2. The generated Service Context will inhertit from Microsoft.Xrm.Client.CrmOrganizationServiceContext and not Microsoft.Xrm.Sdk.Client.OrganizationServiceContext

The XrmServiceContext has some new constructors that allow you to create the OrganizationService from the simplified connection API (Part 2 of this series), but apart from that there aren't really any differences. The magic is what is going on in the new base classes that allow Lazy Loading of the Relationship attributes.

In order to access a relationship property using the standard OrganizationServiceContext you would need to use:

ctx.LoadProperty(ParentAccount, (a) => a.contact_customer_accounts);

Note: This is using the LINQ expression to specify the relationship – which is much easier than the 'traditional approach' of specifying the relationship name as string!

..but using the CrmOrganizationServiceContext, you can now simply query the relationship and the values will be automatically 'lazy loaded' for you. It is called 'lazy' because it will only load when it is first called:

var relatedContacts = (from c in acc.contact_customer_accounts select c);

This allows you to simply query the object graph as though it was all in memory on the client already without worrying if it has been already loaded. With this automatic expand behaviour comes with some important points to watch for:

  1. Every time you perform a LINQ query against a lazy loaded property, the CrmOrganizationService will first query the relationship metadata to and then it will return all related entities using a Retrieve request with the RelatedEntitiesQuery populated. It makes sense to use this technique only when you need to load all related entities as part of a client side caching strategy (see Part 3).
  2. Any 'where' filters will be applied on the client side once all the related entities are retrieved. If you need to perform server side filtering, it would best to use a standard OrganizationServiceContext.CreateQuery<T>
  3. Joins will be applied re-cursively by repeatedly querying each relationship – so again if you need complex joins and don't want all the entities on the client use CreateQuery<T> and a server side query.
  4. Any LINQ projections you use in the query will not be used since all attributes are returned for the related entities. The idea again is that a caching strategy is used so that any filters can be applied on the client side without re-requesting the data from the server.

The LINQ query above will result in the following requests being made:

First the relationship query:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <request i:type="a:RetrieveRelationshipRequest" xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">
        <a:Parameters xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
          <a:KeyValuePairOfstringanyType>
            <b:key>MetadataId</b:key>
            <b:value i:type="c:guid" xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/">00000000-0000-0000-0000-000000000000</b:value>
          </a:KeyValuePairOfstringanyType>
          <a:KeyValuePairOfstringanyType>
            <b:key>RetrieveAsIfPublished</b:key>
            <b:value i:type="c:boolean" xmlns:c="http://www.w3.org/2001/XMLSchema">false</b:value>
          </a:KeyValuePairOfstringanyType>
          <a:KeyValuePairOfstringanyType>
            <b:key>Name</b:key>
            <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema">contact_customer_accounts</b:value>
          </a:KeyValuePairOfstringanyType>
        </a:Parameters>
        <a:RequestId i:nil="true" />
        <a:RequestName>RetrieveRelationship</a:RequestName>
      </request>
    </Execute>
  </s:Body>
</s:Envelope>

And then the actual query:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <request i:type="a:RetrieveRequest" xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">
        <a:Parameters xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
          <a:KeyValuePairOfstringanyType>
            <b:key>Target</b:key>
            <b:value i:type="a:EntityReference">
              <a:Id>9345b249-17ee-e211-9d20-000c299ffe7d</a:Id>
              <a:LogicalName>account</a:LogicalName>
              <a:Name i:nil="true" />
            </b:value>
          </a:KeyValuePairOfstringanyType>
          <a:KeyValuePairOfstringanyType>
            <b:key>ColumnSet</b:key>
            <b:value i:type="a:ColumnSet">
              <a:AllColumns>false</a:AllColumns>
              <a:Columns xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
            </b:value>
          </a:KeyValuePairOfstringanyType>
          <a:KeyValuePairOfstringanyType>
            <b:key>RelatedEntitiesQuery</b:key>
            <b:value i:type="a:RelationshipQueryCollection">
              <a:KeyValuePairOfRelationshipQueryBaseX_PsK4FkN>
                <b:key>
                  <a:PrimaryEntityRole i:nil="true" />
                  <a:SchemaName>contact_customer_accounts</a:SchemaName>
                </b:key>
                <b:value i:type="a:QueryExpression">
                  <a:ColumnSet>
                    <a:AllColumns>true</a:AllColumns>
                    <a:Columns xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
                  </a:ColumnSet>
                  <a:Criteria>
                    <a:Conditions />
                    <a:FilterOperator>And</a:FilterOperator>
                    <a:Filters />
                  </a:Criteria>
                  <a:Distinct>false</a:Distinct>
                  <a:EntityName>contact</a:EntityName>
                  <a:LinkEntities />
                  <a:Orders />
                  <a:PageInfo>
                    <a:Count>0</a:Count>
                    <a:PageNumber>0</a:PageNumber>
                    <a:PagingCookie i:nil="true" />
                    <a:ReturnTotalRecordCount>false</a:ReturnTotalRecordCount>
                  </a:PageInfo>
                  <a:NoLock>false</a:NoLock>
                </b:value>
              </a:KeyValuePairOfRelationshipQueryBaseX_PsK4FkN>
            </b:value>
          </a:KeyValuePairOfstringanyType>
        </a:Parameters>
        <a:RequestId i:nil="true" />
        <a:RequestName>Retrieve</a:RequestName>
      </request>
    </Execute>
  </s:Body>
</s:Envelope>

This functionality is specific built for building Client applications that connect to Dynamics CRM, to make displaying CRM data to the user easier. It is not intended for use in Plugins or Workflow Activities.

Next up is Part 2 on the simplified connection management extensions.

@ScottDurow

Pingbacks and trackbacks (3)+

Comments are closed