Friday, March 8, 2013

Simplifying Retrieval of Aliased Values in CRM 2011

Aliased values have always annoyed me when performing a QueryExpression, or FetchXML Query.  It requires a lot of boiler plate code each time you attempt to retrieve an aliased value (checking for it to exist, including the entity logical name, casting the aliased value’s value to the correct type, etc).  Due to being a lazy developer who believes if I do something twice, that’s once too many, I created some extension methods to simplify the process.  I’m posting them here today for the world to enjoy.
First the code:  There are 5 methods. 
  1. GetAliasedValue<T> – This is the main method.  It’s an extension method on Entity, so it can be used on any entity, early bound or late.  It accepts the an attributeName which is the name of the aliased attribute.  As long as you don’t have duplicate aliased names (i.e. linking/joining on two different entities, and returning the same attribute name from both),  you don’t have to preappend the the logical name of the entity of the aliased value.  It is also generic, so that it will cast the value of the aliased value to the generic type.
  2. SplitAliasedAttributeEntityName – This helper method splits the attribute name passed in, by a period to determine if an entity logical name has been passed in, or only an attribute name.
  3. IsAttributeAliasedValue – This method determines if a particular aliased value is the aliased value requested by the user
  4. GetAliasedValueOrDefault<T> – By default GetAliasedValue<> will throw an exception if the aliased value wasn’t found, along with a list of the attributes contained in the collection.  This checks to see if the aliased value exists, and gets the default value for the generic type if isn’t doesn’t exist.
  5. HasAliasedAttribute – Used by GetAliasedValueOrDefault<T>.  Also helpful if you want to do some other logic besides getting the default value if it isn’t found
         /// <summary>
         /// Returns the Aliased Value for a column specified in a Linked entity
         /// </summary>
         /// <typeparam name="T">The type of the aliased attribute form the linked entity</typeparam>
         /// <param name="entity"></param>
         /// <param name="attributeName">The aliased attribute from the linked entity.  Can be preappeneded with the
         /// linked entities logical name and a period. ie "Contact.LastName"</param>
         /// <returns></returns>
         public static T GetAliasedValue<T>(this Entity entity, string attributeName)
         {
             string aliasedEntityName = SplitAliasedAttributeEntityName(ref attributeName);
             AliasedValue aliased;
             foreach (var attribute in entity.Attributes.Values)
             {
                 aliased = attribute as AliasedValue;
                 if(entity.IsAttributeAliasedValue(attributeName, aliasedEntityName, aliased))
                 {
                     try
                     {
                         return (T)aliased.Value;
                     }
                     catch (InvalidCastException)
                     {
                         throw new InvalidCastException(
                             String.Format("Unable to cast attribute {0}.{1} from type {2} to type {3}",
                                     aliased.EntityLogicalName, aliased.AttributeLogicalName,
                                     typeof(T).Name, aliased.Value.GetType().Name));
                     }
                 }
             }
             throw new Exception("Aliased value with attribute " + attributeName +
                 " was not found!  Only these attributes were found: " + String.Join(", ", entity.Attributes.Keys));
         }


         /// <summary>
         /// Handles spliting the attributeName if it is formated as "EntityAliasedName.AttributeName",
         /// updating the attribute name and returning the aliased EntityName
         /// </summary>
         /// <param name="attributeName"></param>
         /// <param name="aliasedEntityName"></param>
         private static string SplitAliasedAttributeEntityName(ref string attributeName)
         {
             string aliasedEntityName = null;
             if (attributeName.Contains('.'))
             {
                 var split = attributeName.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                 if (split.Length != 2)
                 {
                     throw new Exception("Attribute Name was specified for an Alaised Value with " + split.Length +
                     " split parts, and two were expected.  Attribute Name = " + attributeName);
                 }
                 aliasedEntityName = split[0];
                 attributeName = split[1];
             }
             return aliasedEntityName;
         }


        private static bool IsAttributeAliasedValue(this Entity entity, string attributeName, string aliasedEntityName, AliasedValue aliased)
         {
             bool value =
            (aliased != null &&
                 (aliasedEntityName == null || aliasedEntityName == aliased.EntityLogicalName) &&
                 aliased.AttributeLogicalName == attributeName);

             /// I believe there is a bug in CRM 2011 when dealing with aggregate values of a linked entity in FetchXML.
             /// Even though it is marked with an alias, the AliasedValue in the Attribute collection will use the 
             /// actual CRM name, rather than the aliased one, even though the AttributeCollection's key will correctly
             /// use the aliased name.  So if the aliased Attribute Logical Name doesn't match the assumed attribute name
             /// value, check to see if the entity contains an AliasedValue with that key whose attribute logical name 
             /// doesn't match the key (the assumed bug), and mark it as being the aliased attribute
             if (!value && aliased != null && entity.Contains(attributeName))
             {
                 var aliasedByKey = entity[attributeName] as AliasedValue;
                 if (aliasedByKey != null && aliasedByKey.AttributeLogicalName != attributeName &&
                      Object.ReferenceEquals(aliased, aliasedByKey))
                 {
                     value = true;
                 }
             }
             return value;
         }


         /// <summary>
         /// Returns the Aliased Value for a column specified in a Linked entity, returning the default value for 
         /// the type if it wasn't found
         /// </summary>
         /// <typeparam name="T">The type of the aliased attribute form the linked entity</typeparam>
         /// <param name="entity"></param>
         /// <param name="attributeName">The aliased attribute from the linked entity.  Can be preappeneded with the
         /// linked entities logical name and a period. ie "Contact.LastName"</param>
         /// <returns></returns>
         public static T GetAliasedValueOrDefault<T>(this Entity entity, string attributeName)
         {
             T value;
             if (entity.HasAliasedAttribute(attributeName))
             {
                 value = entity.GetAliasedValue<T>(attributeName);
             }
             else
             {
                 value = default(T);
             }
             return value;
         }


         /// <summary>
         /// Returns the Aliased Value for a column specified in a Linked entity
         /// </summary>
         /// <typeparam name="T">The type of the aliased attribute form the linked entity</typeparam>
         /// <param name="entity"></param>
         /// <param name="attributeName">The aliased attribute from the linked entity.  Can be preappeneded with the
         /// linked entities logical name and a period. ie "Contact.LastName"</param>
         /// <returns></returns>
         public static bool HasAliasedAttribute(this Entity entity, string attributeName)
         {
             string aliasedEntityName = SplitAliasedAttributeEntityName(ref attributeName);
             return entity.Attributes.Values.Any(a =>
                 entity.IsAttributeAliasedValue(attributeName, aliasedEntityName, a as AliasedValue));
         }


Just add these methods to an Extension class and ease the burden aliased values incur:


        public Guid GetBusinessUnit(IOrganizationService service, Guid buildingId)
        {
             var qe = new QueryExpression("new_building");
             qe.ColumnSet.AddColumns("new_buildingid", "new_name");
             qe.Criteria.AddCondition("new_buildingid", ConditionOperator.Equal, buildingId);
             var location = qe.AddLink("new_location", "new_locationid", "new_locationid");
             location.Columns.AddColumn("new businessunitid");
             return service.RetrieveMultiple(qe).Entities.FirstOrDefault().GetAliasedValue<EntityReference>("new_businessunitid").Id;
        }

No comments: