-
Notifications
You must be signed in to change notification settings - Fork 1
Fetching data with FluentCRM
First a quick discussion of what is involved in fetching data from CRM, and more importantly making use of that data once it has been fetched from the server.
If you are reading this, you should be aware that there are two options when writing code for the CRM .Net SDK - early and Late binding. While the two approaches are different, they have their pros and cons, and present similar issues.
Lets look at a late binding example of fetching an attribute from an account - we are trying to find which of several phone numbers might be available (because your users are never consistent about which phone number field they put data into, are they?)
var telno = string.Empty;
var cols = new ColumnSet("telephone1", "telephone2", "mobilephone"); // 1)
var result = orgService.Retrieve("account", account1.Id, cols);
if (result != null) // 2)
{
if (result.Contains("telephone1")) // 3)
{
telno = result.GetAttributeValue<string>("telephone1"); // 4)
}
else if (result.Contains("telephone2"))
{
telno = result.GetAttributeValue<string>("telephone");
}
else if (result.Contains("mobilephone"))
{
telno = result.GetAttributeValue<string>("mobilephone");
}
}
A few points about this: -
-
We probably want a using() around this - but we have enought clutter already for one chunk of code. :-\
-
Right at the start we have to decide which columns we want to retrieve, and we have to make sure we include all the attributes we might want to use later.
If we make any mistakes then we either end up throwing an exception, or not doing what we expect. -
Check that we actually have returned something.
-
Check to see if the result set contains the attribute we are looking for, and that it is not null.
Better make sure that there are no typos in the attribute name, and that we are being consistent with our attribute names.
Of course we could use a string constant - more clutter! -
Extract the value we are looking for. Again, better make sure that we are being consistent with our attribute names.
Now we are in a position to actually do something with our telephone number.
Did you spot the "deliberate" mistake here? Look at where we extract "telephone2" value. We've made a typo in the attribute name. We won't even find out there's a problem until we run this against an account that has a telephone2 value, but not a telephone1.
The FluentCRM approach is to try to make sure we only specify the attribute name once, and to get rid of the clutter and boilerplate, (which is going to be pretty much the same every time). How do we do that? A bit of magic with Generics, and this gives us the Fluent CRM version: -
var telno = string.Empty;
var name = string.Empty;
FluentAccount.Account(account1.Id, context.GetOrganizationService()) // 1)
.UseAttribute((string n) => name = n, "name") // 2)
.UseAttribute((string t) => telno = t, "telephone1", "telephone2","mobilephone") // 3)
.Execute(); // 4)
Notice how little clutter there is here - it's easier to write, minimal scope for errors and is easier to comprehend (I hope!)
-
Start by selecting the account we want - in this case we do it by the Id, but we will look at options for selecting records by attribute values later. We specify the IOrganizationService here, but if all of your code uses the same orgservice, there is the option to just set this once, and then forget about it. Less clutter == good!
-
Now we want to make use of an attribute - so we specify the attribute logical name once and once only and then say what we want to do with it.
Most of the time we just want to extract the value and do something with it - usually feed it into some kind of DTO.
Note that if the attribute name doesn't exist or the type is not what we expect, we will get a runtime error.
If the attribute is set to null, or not present in the entity then the closure will not be called, and nothing will happen. Usually this is the right thing(tm) to happen. -
We can specify more than one attribute. What this means is that however many attributes we specify, we will call the closure once (and only once) with the first attribute we find that is not null.
In this case we are trying to find a phone number for this account, but the users have been a bit sloppy about which phone number they fill in (that never happens to you, does it?) -
Up to this point in the call chain nothing has actually happened - much like LINQ, we don't actually go out to the server until we call Execute(). This crystallizes everything we have set up so far, makes the call to the server to pull back the columns we need and then call the closures we have set up (provided that there is a value available to call the closure with)
The Execute() call is always the end of the chain. It does not return a value.
So why is this better? Why can't we just use LINQ and early binding. Wouldn't this be better? The issues with early binding are widely recognised - sometimes it can be the right solution, but it does couple your code closely to a particular CRM customization, and it will give you a very large early binding file to deal with (20MB and rising for an out of the box implementation). There are tools to only generate bindings for entities that you want, but these bindings will still be a considerable size, and the whole process adds more complexity to your solution.
And even with early binding, there is still clutter and lack of simplicity: -
var telno = string.Empty;
var name = string.Empty;
using (var ctx = new DynamicsLinq.OrgServiceContext(context.GetOrganizationService()))
{
var result = from a in ctx.AccountSet
where account1.Id == a.Id
select new {a.Name, a.Telephone1, a.Telephone2, a.Address1_Telephone1};
var account = result.FirstOrDefault();
if (account != null)
{
name = account.Name;
telno = account.Telephone1 ?? account.Telephone2 ?? account.Address1_Telephone1;
}
}
A bit better than late binding, but notice that you are still adding multiple references to the Attribute names - but at least early binding will stop us from misspellings and typos, so some improvement - but at a cost of all the overheads of EB. There's still a lot of clutter, testing for nulls and this isn't actually doing what you expect - it doesn't deal with empty strings and null values correctly.
FluentCRM - Microsoft.Xrm programming made easy