An Update to the F# Microsoft Dynamics CRM Type Provider Sample

[ The CRM type provider sample is available as the FSharpx.TypeProviders.Xrm NuGet package, pat of the FSharpx community library. The namespace has changed from "Samples" to "FSharpx" The updates below are in the process of being transferred into FSharpx package ]

Part 1 - The Microsoft Dynamics CRM Type Provider Sample
Part 2 - The Microsoft Dynamics CRM Type Provider Sample - Static Parameters
Part 3 - The Microsoft Dynamics CRM Type Provider Sample - Updated Functionality

An update of the Microsoft Dynamics CRM type provider sample is now available. The new features from this update are demonstrated below.

CRUD Operations

You are now able to create entities and set their attributes using the properties provided by the type provider. The OrganizationService instance is exposed so that you can pass entities to the relevant create / update / delete methods. You can create entities either by calling the Create method on the relevant entity set, or you can call the constructor of the provided entity type from within the XrmService type.

let account = XRM.XrmService.account()
let account2 = dc.accountSet.Create()

account.name <- "John"
account2.name <- "Juan"

account.accountid <- dc.OrganizationService.Create account
account2.accountid <- dc.OrganizationService.Create account2

account.name <- "Jean"
dc.OrganizationService.Update account

dc.OrganizationService.Delete(account.LogicalName,account.accountid)
dc.OrganizationService.Delete(account2.LogicalName,account2.accountid)

Explicit Joins

You are now able to use the standard join syntax in the query expressions. This syntax is not supported when combined with the select many syntax, you must choose one or the other for the query.

let (contactName,accountName,leadName) =
     query{ for a in dc.accountSet do
            join c in dc.contactSet on (a.primarycontactid.Id = c.contactid)
            join l in dc.leadSet on (a.originatingleadid.Id = l.leadid)
            select (c.fullname, a.name, l.fullname )
            exactlyOne }

There is no support for outer joins using this syntax – the recommended approach is to use the select many syntax directly over the relationships where you can use the outer join operator (!!) if required.

Dot Individuals

All entity sets on the data context now have a new property named Individuals. This will return a type that contains a sample of the entities from the CRM system surfaced directly as properties. The name for each entity property is derived from the Primary Attribute as designated in the CRM metadata. You can adjust how many entities to load using the static parameter IndividualsAmount, the default is 1000.

let ben = dc.accountSet.Individuals.``Ben Smith``

This feature can be extremely useful in data scripting scenarios or where a lot of static data exists in the CRM system, allowing for clearer and easier access to common entities.

Option Sets

Enumerations are now generated for CRM Option Sets on entities. These can be used directly in queries and CRUD operations.

query { for a in dc.contactSet do
        where (a.accountrolecode = XRM.XrmService.contact_accountrolecode.``Decision Maker`` )
        select a }

If you have nullable types switched on, you will not be able to use the F# nullable operators with the provided enumerations due to a restriction in the provider mechanism. Instead, you can use a standard operator on the Value property. The type provider will transform this into a safe expression containing a null check.

where (c.accountrolecode.Value = XRM.XrmService.contact_accountrolecode.``Decision Maker``)

Formatted Values

The contents of the underlying Microsoft.Xrm.Sdk.Entity.FormattedValues dictionary is now projected directly into a new type, accessed via the Formatted property on the provided entity type. This type contains a string property for each attribute on the entity whose type can possibly have a formatted value. For example; Money, Dates, Integers and Option Sets are the most common. You can use the formatted values directly in the projection expression of a query.

query { for c in dc.contactSet do
        select (c.address1_city,c.Formatted.accountrolecode) }

You can also use the original underlying formatted values in a dynamic fashion by indexing the dictionary like normal

query { for c in dc.contactSet do
        select (c.address1_city,c.FormattedValues.["accountrolecode"]) }

If you use the dynamic approach you should assert the existence of the key first – the provided version handles this automatically and returns a blank string if the value does not exist.

query { for c in dc.contactSet do
        select
          (c.address1_city,
           if c.FormattedValues.ContainsKey "accountrolecode" then
             c.FormattedValues.["accountrolecode"]
           else
             "") }

Enumerating Relationships

Given an instance of an entity, you are now able to treat its relationships directly as sequences. The type provider will construct a query that uses the current entity ID as the primary or foreign key of the relationship and retrieves the results as normal.

let lead = dc.leadSet.Individuals.``Diogo Andrade``
let accounts = lead.``1:N account_originating_lead`` |> Seq.toList

In this case the relationship represents children. This code will always work because the query is using the primary key of lead to find matching children, and the primary key for an entity is always present no matter how you retrieve it. When enumerating a parent relationship, the related foreign key attribute must be present on the entity; otherwise the provider will not have enough information to retrieve the parent(s). For example, the following query works fine because no specific attributes were selected. The query returns all the entity's attributes including the foreign key we are interested in.

let lead = dc.accountSet.Individuals.``Contoso Ltd``.``N:1 account_originating_lead`` |> Seq.exactlyOne

The next query will not work, because the initial projection statement specifies the attribute name, thus the provider only returns that single attribute for reasons of efficiency.

let (name,acc) = query { for acc in dc.accountSet do
                         select (acc.name,acc)} |> Seq.head
let lead = acc.``N:1 account_originating_lead`` |> Seq.exactlyOne

For the subsequent query to work, the foreign key in question would need to be selected as well

let (name,leadId,acc) = query { for acc in dc.accountSet do
                                select (acc.name,acc.originatingleadid,acc)} |> Seq.head
let lead = acc.``N:1 account_originating_lead`` |> Seq.exactlyOne

It is also possible to write new query expressions directly over the relationships of entity instances, assuming the above criteria is met.

let cont = dc.contactSet.Individuals.``Brian Smith``
let (a,o,l) = query { for acc in cont.``1:N account_primary_contact``do
                      for lead in acc.``N:1 account_originating_lead`` do
                      for owner in acc.``N:1 owner_accounts`` do
                      where (l.firstname = "Diogo")
                      select (acc,owner,lead)
                      exactlyOne }  

Data Binding

Provided entities can now be bound to any .NET data binding system that makes use of INotifyPropertyChanged and the TypeDescriptor. There are two different data binding modes you can select from, accessed via the static parameter DataBindingMode.

NormalValues is the default mode. All entity attributes will appear as get/set properties to the binding mechanism, directly passing the underlying type of each attribute value. This means that you can use this mode to perform full create / update behaviour, however, whilst most basic types will work just fine, a lot of the attributes will have types such as OptionSet and EntityReference from the XRM SDK. You will need to customise the UI for these types if you want to properly read and modify them - the nature of this work depends on the binding component in question.

FormattedValues is the other available binding mode. Entity attributes will appear as a mix of read/write properties for those that do not have formatted values associated with them, and read-only string properties for other attributes that do have formatted values. This means that types such as OptionSet will show the actual string representation of the option set value; dates appear in localized time, and so forth. Because the formatted values are provided by the server and are always strings, providing the ability to modify them would not impact the underlying entity data – therefore this mode is designed more for explorative code or an application which only needs to show data rather than modify it with a binding mechanism.

The following code shows a complete F# interactive sample script that binds all the account entities to a DataGridView on a Windows Form.

#r @"microsoft.xrm.sdk.dll"
#r @"Samples.XrmProvider.dll"
#r "System.Runtime.Serialization"
#r "System.Windows.Forms"

open Samples.XrmProvider
open Samples.XrmProvider.Runtime.Common
open System.Windows.Forms
open System.ComponentModel

type XRM = XrmDataProvider<"https://server/org/XRMServices/2011/Organization.svc",false,
                           DataBindingMode=DataBindingMode.FormattedValues,
                           Username="usernamne",Password="password",Domain="domain">

let dc = XRM.GetDataContext()

let data = BindingList(Seq.toArray dc.accountSet)
let form = new Form(Text="CRM Accounts")
let dg = new DataGridView(Dock = DockStyle.Fill,DataSource=data)
form.Controls.Add dg
form.Show()