My practices on FetchXML (MS Dynamics CRM 4.0)
Since few months, I work on developing a WPF UI that directly connects to MS Dynamics CRM 4.0. In this post I will present you some of the practices I use in this project.
Restriction of the result
Reduce the attributes to return
Specify all desired attributes and not all-attributes element. all-attributes will return all fields of the desired entities:
<attribute name=”…” />
<attribute name=”…” />
instead of:
<all-attributes />
I really recommend you to manually set all the attributes you need. This will reduce the size of the response and in a same time, this reduce the SQL request generated by CRM.
Code Snippet
- <fetch distinct="true" mapping="logical">
- <entity name="contact">
- <attribute name="contactid" />
- <attribute name="gendercode" />
- <attribute name="firstname" />
- <attribute name="lastname" />
- </entity>
- </fetch>
Pagination with Paging cookie
Take few minutes to create your own paging mechanism.
Step 1 :
- Create a context object for the server calls:
Code Snippet
- /// <summary>
- /// Context object to keep between all crm calls
- /// </summary>
- public class PaginationContext
- {
- /// <summary>
- /// Id of the page
- /// </summary>
- public int Page { get; set; }
- /// <summary>
- /// Number of element to return
- /// </summary>
- public int ElementCount { get; set; }
- /// <summary>
- /// Paging cookie
- /// </summary>
- public string PagingCookie { get; set; }
- /// <summary>
- /// HAs more records to return
- /// </summary>
- public bool HasMoreRecords { get; set; }
- }
- Encapsulate the query encapsulation and parse the return of the FetchXML query:
Code Snippet
- /// <summary>
- /// Generic mechanism to paginate
- /// </summary>
- /// <param name="query"></param>
- /// <param name="context"></param>
- /// <param name="paginationcontext"></param>
- /// <returns></returns>
- public static XElement PaginateResults(this StringBuilder query,
- CrmDataContext context,
- PaginationContext paginationcontext)
- {
- XElement result = null;
- XDocument fetchQueryDoc = XDocument.Parse(query.ToString());
- XElement rootQueryElement = fetchQueryDoc.Element("fetch");
- // Initialize the number od records to return
- XAttribute countAttribute = rootQueryElement.Attribute("count");
- if (rootQueryElement.Attribute("count") != null)
- countAttribute.Value = paginationcontext.ElementCount.ToString();
- else
- rootQueryElement.Add(new XAttribute("count", paginationcontext.ElementCount.ToString()));
- // Initialize the page id
- XAttribute pageAttribute = rootQueryElement.Attribute("page");
- if (rootQueryElement.Attribute("page") != null)
- pageAttribute.Value = paginationcontext.Page.ToString();
- else
- rootQueryElement.Add(new XAttribute("page", paginationcontext.Page.ToString()));
- // Initialize the paging cookie
- XAttribute pagingCookieAttribute = rootQueryElement.Attribute("paging-cookie");
- if (rootQueryElement.Attribute("paging-cookie") != null)
- pagingCookieAttribute.Value = paginationcontext.PagingCookie.ToString();
- else
- rootQueryElement.Add(new XAttribute("paging-cookie", paginationcontext.PagingCookie.ToString()));
- using (var service = context.CreateService())
- {
- string fetchResult = service.Fetch(fetchQueryDoc.ToString());
- XDocument resultDoc = XDocument.Parse(fetchResult);
- if (resultDoc != null)
- {
- XElement rootElement = resultDoc.Element("resultset");
- if (rootElement.Attribute("morerecords") != null && rootElement.Attribute("morerecords").Value == "1")
- paginationcontext.HasMoreRecords = true;
- else
- paginationcontext.HasMoreRecords = false;
- if (rootElement.Attribute("paging-cookie") != null && string.IsNullOrEmpty(rootElement.Attribute("paging-cookie").Value) == false)
- {
- string pagingCookie = rootElement.Attribute("paging-cookie").ToString().TrimStart("paging-cookie=\"".ToCharArray()).TrimEnd('"');
- }
- result = rootElement;
- }
- }
- return result;
- }
- Test the code:
Code Snippet
- [TestMethod]
- public void TestPagination()
- {
- CrmDataContext context;
- // ...
- // Code to connect to CRM
- // ...
- using (var service = context.CreateService())
- {
- FetchXMLHelper.PaginationContext paginationContext = new FetchXMLHelper.PaginationContext() { ElementCount = 50, HasMoreRecords = true };
- do
- {
- StringBuilder fetchXml = new StringBuilder();
- fetchXml.Append("<fetch distinct=\"false\" mapping=\"logical\" >");
- fetchXml.Append("<entity name=\"account\">");
- fetchXml.Append(" <all-attributes />");
- fetchXml.Append("</entity>");
- fetchXml.Append("</fetch>");
- XElement element = fetchXml.PaginateResults(context, paginationContext);
- if (element != null)
- {
- // ...
- // Code to deserialize the result of the fetch query
- // ...
- }
- } while (paginationContext.HasMoreRecords);
- }
- }
Joining entities
FetchXML offers the ability to return fields from parent entity, linq queries not.
Using aliases
Using aliases for you linked entities will facilitate the execution and your deserialization. If you do not set an alias, you will not be able to return fields with same names from different entities.
Code Snippet
- <fetch distinct="true" mapping="logical">
- <entity name="contact">
- <attribute name="contactid" />
- <attribute name="gendercode" />
- <attribute name="firstname" />
- <attribute name="lastname" />
- <attribute name="createdby" />
- <attribute name="createdon" />
- <attribute name="modifiedby" />
- <attribute name="modifiedon" />
- <filter type="and">
- ...
- </filter>
- <link-entity name="account" from="accountid" to="parentcustomerid"alias="accountAlias">
- <attribute name="accountid" />
- ...
- <attribute name="createdby" />
- <attribute name="createdon" />
- <attribute name="modifiedby" />
- <attribute name="modifiedon" />
- </link-entity>
- </entity>
- </fetch>
Here is the result of previous FetchXML queyry:
Code Snippet
- <resultset>
- <result>
- <contactid>...</contactid>
- <gendercode>...</gendercode>
- <firstname>...</firstname>
- <lastname>...</lastname>
- <createdby>...</createdby>
- <createdon>...</createdon>
- <modifiedby>...</modifiedby>
- <modifiedon>...</modifiedon>
- <accountAlias.accountid>...</accountAlias.accountid>
- ...
- <accountAlias.createdby>...</accountAlias.createdby>
- <accountAlias.createdon>...</accountAlias.createdon>
- <accountAlias.modifiedby>...</accountAlias.modifiedby>
- <accountAlias.modifiedon>...</accountAlias.modifiedon>
- </result>
- ...
- </resultset>
In the example, you can now returns createdby (createdon, modifiedby, modifiedon) on two different entities. After in your project, you can easily create you own deserialization mechanism having the alias as parameter.
Left Join
You cannot use left join using Linq. Fetch queries enable left join. This helps you a lot, if you need to return in each contact query specific fields of parent entity, this reduces the number of request to server.
Code Snippet
- <fetch distinct="true" mapping="logical">
- <entity name="contact">
- <attribute name="contactid" />
- <attribute name="gendercode" />
- <attribute name="firstname" />
- <attribute name="lastname" />
- <filter type="and">
- ...
- </filter>
- <link-entity name="account" from="accountid" to="parentcustomerid" alias="accountAlias" link-type="outer">
- <attribute name="accountid" />
- ...
- </link-entity>
- </entity>
- </fetch>
Quick generation of fetch XML query
Here is in 5 points a quick way to generate your FetchXML query:
- Use the advance find of MS CRM;
- Use the graphical user interface to define your query;
- Run the query (click on Find button) ;
- Press CTRL+N, this will create a new IE window but will keep the context;
- Replace the url in the address bar
- javascript:alert(resultRender.FetchXml.value);
This will open a pop-up with the expected FetchXml