Dynamics CRM allows to create fields with the following data types:
Except for "Single Line of Text" and "Multiple Lines of Text" (both use string), each one uses a different underline data type in .NET, some of these data types are nullable, some are not.
What means nullable and why we need to care about this?
Let's start with an example: In our CRM we have two records, the first record has all the fields filled with a value, in the second one all the fields are empty. When we use GetAttributeValue and there is a value, the method (fairly) returns the value. But what happens with our empty record? The response is "depends".
GetAttributeValue uses Generics, so we can choose to get a nullable type or not:
bool boolean = entity.GetAttributeValue<bool>("new_boolean"); bool? booleanNullable = entity.GetAttributeValue<bool?>("new_boolean");In this case, if the value is null (for a Boolean/Two Options field means that no value is set) the first variable will contains false, the second will contains null.
The next table is a summary:
CRM Type | .NET Type | can hold null? | default value |
---|---|---|---|
Single Line of Text | string | Yes | |
Option Set | OptionSetValue | Yes | |
Two Options | bool | No | false |
Image | byte[] | Yes | |
Whole Number | int | No | 0 |
Floating Point Number | double | No | 0.0 |
Decimal Number | decimal | No | 0 |
Currency | Money | Yes | |
Multiple Lines of Text | string | Yes | |
Date and Time | DateTime | No | DateTime.MinValue |
Lookup | EntityReference | Yes |
EntityReference lookupRef = entity.GetAttributeValue<EntityReference>("new_lookupid"); if (lookupRef == null) { // no value set } else { // we have a value }For the types that can't hold null we need to ask ourselves: "The default value is enough for the requirement?"
If we are in a loop and we need to do a sum of an int field, the default value (0) is ok, so we can just do
int totalSum = 0; foreach (Entity row in RowCollection.Entities) { int number = entity.GetAttributeValue<int>("new_wholenumber"); totalSum+=number; }but if we are doing a multiplication we need to skip the null values, so we use int?
int totalMulty = 0; foreach (Entity row in RowCollection.Entities) { int? number = entity.GetAttributeValue<int?>("new_wholenumber"); if (number != null) { totalMulty*=number; } }otherwise with a null value our totalMulty variable will be 0.
DateTime is a particular case, it can't hold null but the default value (MinValue = 01/01/0001) can't be a valid CRM value (as happens with bool and numeric fields) so we can do the following check:
DateTime dateTime = entity.GetAttributeValue<DateTime>("new_datetime"); if (dateTime == DateTime.MinValue) { // no value set } else { // we have a value }Practically when we don't use the nullable form, Dynamics CRM is doing the following:
double floating = entity.GetAttributeValue<double>("new_floating"); // equals to double floating = entity.GetAttributeValue<double?>("new_floating").GetValueOrDefault();The combination of the nullable form and the GetValueOrDefault can be useful in some scenarios. Let's say that we need to do a data migration to an external system, but if the source decimal Quantity is null, the target decimal Quantity must be -1.
decimal quantity = entity.GetAttributeValue<decimal?>("new_quantity").GetValueOrDefault(-1);In this way we deal automatically the null values and they are ready for the target system.
Now you are a Dynamics CRM True Survivor!
Great post more people should definitely use it, I tried to persuade people last year
ReplyDeletehttps://crmbusiness.wordpress.com/2014/10/16/crm-2013-using-entity-getattributevalue-instead-of-entity-contains/
Developers should be careful when using this code because it will return default values, you must be expecting default values in the code because working out a value using zero could give different results than if your code previously stopped when it was null
You are right Ben, I also wrote this post to keep in mind the possible scenarios, sometime I still use Contains because I don't remember right away the default values.
DeleteI created similar extension methods for working with AliasedValues as well: http://dotnetdust.blogspot.com/2013/03/simplifying-retrieval-of-aliased-values.html
ReplyDeleteAwesome, also I didn't know you had a blog, double awesome.
DeleteGetAttributeValue is really just a wrapper method that first calls Contains, and if that returns true, uses the indexer to get the value and casts the value to the correct type. Otherwise, it returns default(T). You can just open up the Microsoft.Xrm.Sdk.dll using Telerik JustDecompile and look at the code... :)
ReplyDeleteWell, Well once again down the rabbit hole and I find you Guido with the answer I was looking for. Next Vendor interview will include a question "When is Null not a value?" We had issue with creating work around and now I can put something more solid in place.
ReplyDeleteI enjoy seeing What Daryl and Ben did as well. The different perspectives combine to make a clear picture for me. Have a great day!