0

I have a custom entity called Issue which has a grid linked to a entity Cost To Business, users can add records from Cost To Business to the issue record. I want a overall cost to business field to be auto populated from the items in the grid. I've used a plugin to do this for me. Within the plugin I have constructed the following FetchXML Query to retrieve my data

string fetchxml =
            @"<fetch distinct='true' mapping='logical' output-format='xml-platform' version='1.0'>
                    <entity name='new_issue'>
                        <attribute name='new_issueid'/>
                        <attribute name='new_name'/>
                        <attribute name='createdon'/>
                            <order descending='false' attribute='new_issueid'/>
                            <link-entity name='new_costtobusiness' alias='ab' to='new_issueid' from='new_issue_costid'>
                                <attribute name='new_costtobusiness'/>                                             
                            </link-entity>
                    </entity>
              </fetch>";

            EntityCollection result = service.RetrieveMultiple(new FetchExpression(fetchxml));
            {
                if (result != null && result.Entities.Count > 0)
                {
                    List<string> _product = new List<string>();

                    foreach (Entity _entity in result.Entities)//loop through every record
                        {
                            costToBusiness = ((AliasedValue)_entity.Attributes["ab.new_costtobusiness"]).Value.ToString(); 

                        }
                    throw new InvalidPluginExecutionException(costToBusiness);
                }

            }

However, when I'm calling the Invalid Plugin Execution Exception to view what the query is returning the variable "costToBusiness" is only holding "Microsoft.Xrm.Sdk.Money" and not the actual value which is in the record.

Does anyone know what I've done wrong?

Sjharrison
  • 729
  • 3
  • 16
  • 39

1 Answers1

1

I'm pretty sure you need a Value.Value.

((AliasedValue)_entity.Attributes["ab.new_costtobusiness"])  // Returns an AliasedValue
.Value // Returns a Money but is actually an Object
.ToString() // Calls the default ToString of Money which is just spit out the type name.

// This should be correct:
((Money)((AliasedValue)_entity.Attributes["ab.new_costtobusiness"]).Value).Value.ToString()

I'm assuming this is heavily edited code, since you loop through all, but only spit out the last.

Working with Aliased Values kind of sucks so I use these extensions methods internally:

/// <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);

    foreach (var entry in entity.Attributes)
    {
        if (entity.IsSpecifiedAliasedValue(entry, aliasedEntityName, attributeName))
        {
            var aliased = entry.Value as AliasedValue;
            if (aliased == null) { throw new InvalidCastException(); }

            try
            {
                // If the primary key of an entity is returned, it is returned as a Guid.  If it is a FK, it is returned as an Entity Reference
                // Handle that here
                if (typeof (T) == typeof (EntityReference) && aliased.Value is Guid)
                {
                    return (T)(object) new EntityReference(aliased.EntityLogicalName, (Guid) aliased.Value);
                }

                if(typeof (T) == typeof (Guid) && aliased.Value is EntityReference)
                {
                    return (T)(object) ((EntityReference)aliased.Value).Id;    
                }

                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,
                            aliased.Value.GetType().Name, typeof(T).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>
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 IsSpecifiedAliasedValue(this Entity entity, KeyValuePair<string,object> entry, string aliasedEntityName, string attributeName)
{
    AliasedValue aliased = entry.Value as AliasedValue;
    if (aliased == null)
    {
        return false;
    }

    // There are two ways to define an Aliased name that need to be handled
    //   1. At the Entity level in Query Expression or Fetch Xml.  This makes the key in the format AliasedEntityName.AttributeName
    //   2. At the attribute level in FetchXml Group.   This makes the key the Attribute Name.  The aliased Entity Name should always be null in this case

    bool value = false;
    if (aliasedEntityName == null)
    {
        // No aliased entity name specified.  If attribute name matches, assume it's correct, or, 
        //     if the key is the attribute name.  This covers the 2nd possibility above
        value = aliased.AttributeLogicalName == attributeName || entry.Key == attributeName;
    }
    else if (aliased.AttributeLogicalName == attributeName)
    {
        // The Aliased Entity Name has been defined.  Check to see if the attribute name join is valid
        value = entry.Key == aliasedEntityName + "." + attributeName;
    }
    return value;
}

This would change your code to this:

_entity.GetAliasedValue<Money>("new_costtobusiness").ToString();
Daryl
  • 18,592
  • 9
  • 78
  • 145
  • `AliasedValue` returns type of `object` so that should be cast to `decimal`, since it is money. It may not matter for calling ToString, since `object` implements that method, but it will be important to do the computations. So would edit it to red: `((decimal)((AliasedValue)_entity.Attributes["ab.new_costtobusiness"]).Value)).ToString();` – Nicknow Jul 29 '14 at 03:30
  • A Double value resulted in the second value creating a error. With regards to @Nicknow suggestion it resulted in my Business Process Error returning the message "Exception of type 'Microsoft.Xrm.Sdk.InvalidPluginExecutionExeption' was thrown – Sjharrison Jul 29 '14 at 11:12
  • @user3793518 Sorry, missed the second cast of the `Value` property of the AliasedValue to Money – Daryl Jul 29 '14 at 11:31
  • @Nicknow Money doesn't define an explicit cast to Decimal, so I don't see how your suggestion will work. You did help me see I was missing the Money cast though! – Daryl Jul 29 '14 at 11:35
  • Hi @Daryl just literally copied and pasted the code into my plugin as it's away over my head, only error message I have in Visual Studio is "no definition or extension method for IsSpecifiedAliasedValue Any suggestions? – Sjharrison Jul 29 '14 at 12:53
  • @user3793518 IsSpecifiedAliasedValue is the last of the three functions I put in the answer. Did you copy all of the classes? The standard Extension class rules apply, all three will need to be in a `public static class` in a namespace that you have a `using` statement for in your plugin. – Daryl Jul 29 '14 at 13:25
  • Hi, apologies but this is all new to me, I have now created a new class library in my plugin, I have namespace CostToBusiness, Public Static Class Aliased and copied and pasted the 3 functions into the public static class, I have included Using into the form also, all is okay on visual studio now, however i'm still getting "Exception of type 'Microsoft.Xrm.Sdk.InvalidPluginExecutionExeption' was thrown' on CRM and not the actual amount I need so I can continue – Sjharrison Jul 29 '14 at 14:08
  • @user3793518 - Your plugin code throws an Exception, Did you forget to remove it before updating the plugin? – Daryl Jul 29 '14 at 14:28
  • I've kept it in there on purpose at the moment to see what the string costToBusiness is returning so that I can continue, once it is returning what I want I will remove it – Sjharrison Jul 29 '14 at 14:41
  • @user3793518 What is it returning? – Daryl Jul 29 '14 at 15:14
  • @Daryl Exception of type 'Microsoft.Xrm.Sdk.InvalidPluginExecutionExeption' was thrown' – Sjharrison Jul 29 '14 at 15:20
  • @user3793518 You should be able to view the Exception Details and see what the message of the Exception was. – Daryl Jul 29 '14 at 15:36
  • I can download the log file but the same message appears within the log, nothing that I can see of use – Sjharrison Jul 29 '14 at 15:38