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

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

Rate This
  • Comments 9

[ 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<"http://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()

Leave a Comment
  • Please add 7 and 5 and type the answer here:
  • Post
  • Now working as a charm after you guys have fixed the Sample naming issue

    Explicit Joins and Dot Individuals (Metadata/Configurations) will become so trivial

    Absolutely Love It !!!

  • *** Regarding the CredentialsFile. Could you post an example? Or perhaps update the parameter description:

    CredentialsFile:

    Path to a plain text file that includes username, password and optionally the domain. In the case of CRM Online these would be the relevant Windows Live username and password.

    Example (on-premise_cred.txt):

    username="user"

    password="password"

    domain="domain"

    Ex (online_cred.txt):

    username="user@live.com"

    password="password"

    *** In advance thank you

  • Hey!  I'm pleased you like the provider (although I'd be interested to know how using the explicit joins would be nicer/easier than the relationships directly?) .  The credentials file is actually simpler than your example, it is just a flat text file with the first line being the username, second the password and lastly the domain, if applicable. There are no parameter names or quotes.  For instance, a three line text file:

    username

    password

    domain

    You can see a real example of this if you look in the test project on codeplex.  I will update this post at some stage to make it clearer, thanks for the feedback :)

  • >> (although I'd be interested to know how using the explicit joins would be nicer/easier than the relationships directly?)

    Point taken and wrong use of parentheses :-)

    "Explicit Joins and Dot Individuals (Metadata/Configurations) will become so trivial"

    should have been:

    "Explicit Joins and Dot Individuals (Metadata/Configurations will become so trivial)"

    I work on a daily basis with CRM so I might have some comments and maybe features I really think would be needed in order to move this into production

    If you are interested in hearing my thoughts, pls send me an e-mail to rsm@delegate.dk (my own "hobby" project is to make ZachBray's / FunScript usable for CRM. We might be able to produce a Framework that would ensure that only F# code (both serverside and also clientside) is used when making Xrm projects :-)

  • Hi Ross

    Regarding CRUD Operations, you provide the following examples:

    let account = XRM.XrmService.account()

    let account2 = dc.accountSet.Create()

    account.name <- "John"

    account2.name <- "Juan"

    I think this would be unpractical if the reason to make the move to F# is to have a functional approach to CRM data

    I can see that following works:

    let a = frm.accountSet.Create(name = "Foo")

    but then I can perform the following operation:

    a.name <- "Bar"

    which removes inmutability and from a functional point of view, it's not desired (How can I ensure correctness

    when for example using concurrent processes using the same data?)

    The behaviour I would expect is the following:

    let a = frm.accountSet.Create(name = "Foo")

    let a' = { a with accountnumber = 42 } // The type 'XrmDataProvider<...>.XrmService.account' does not contain a field 'accountnumber'

    let a'' = { a' with name = "Bar" } // The type 'XrmDataProvider<...>.XrmService.account' does not contain a field 'name'

    Where I have to create a new record based on the previous and then providing the fields I would like to update

    Does this make any kind of sense?

    Hope you guys can help and thanks in advance

    p.s.: Once again, keep up the good work :-)

  • Hi Ramon

    I see your point regarding the immutability of the types, primarily I wanted the objects returned to be of the standard CRM SDK types so that they can subsequently be used in all other standard organization requests, or any existing libraries that use the SDK Entity type. (not to mention greatly simplifying the implementation, which is anything but simple!)

    The point of the type provider was not so much to offer a set of functional constructs for CRM, but more to enable ease of access to the data without having the large code generation step.

    Additionally, type providers are currently unable to produce F# record types or discriminated unions, so what you have suggested would currently not be possible.  It would however, be a nice optional layer over the top of the existing implementation at some time in the future.

    ps. I have finished my work at Microsoft on the CRM type provider now, so it is unlikely to get new features aside from anything the community add themselves.

    Ross

  • Does anyone know if it's possible to change the state of an entity using the equivalent of the SetStateRequest via the type provider. This is looking like it could be a great tool for ad hoc crm data manipulation and the ability to set record states would be really useful.

  • I've now answered that question myself by referencing the crm.sdk.proxy dll and creating an instance of a SetStateRequest which allows me to use the OrganizationService.Execute method to change the status on an entity.

    The problem I'm now facing is how I would set a null value on an update of a record, particularly for an attribute which uses an option set. I've tried setting the UseNullableValues argument to true on the XrmDataProvider but that then presents me with issues setting any Optionset values as Nullable Types are expected.

  • Sorry, answered my own question again with the SetAttribute method.

Page 1 of 1 (9 items)