Search code examples
c#visual-studiomicrosoft-dynamicskeynotfoundexception

MS Dynamics 365 C# audit history KeyNotFoundException


I am trying to pull the audit history of a specific account as demonstrated by an example here:

enter image description here

I specifically need to pull all the results for the Changed Field called Credit Limit.

In order to do this, I have the following code in C#:

using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Tooling.Connector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PowerApps.Samples
{
    public partial class SampleProgram
    {
        [STAThread] // Added to support UX
        static void Main(string[] args)
        {
            CrmServiceClient service = null;
            service = SampleHelpers.Connect("Connect");
            if (!service.IsReady)
                Console.WriteLine("No Connection was Made.");
            Console.WriteLine("Connected");

            //Create a new RetrieveAttributeChangeHistoryRequest

            RetrieveAttributeChangeHistoryRequest req = new RetrieveAttributeChangeHistoryRequest();

            //Set the target Entity

            req.Target = new EntityReference("new_case", new Guid("468f8db5-4f98-eb11-57ee-0006ffc2587a"));

            //Set the attribute you want to retrieve specifically

            req.AttributeLogicalName = "credit_limit";

            //Execute the request against the OrgService

            RetrieveAttributeChangeHistoryResponse resp = (RetrieveAttributeChangeHistoryResponse)service.Execute(req);

            AuditDetailCollection details = resp.AuditDetailCollection;

            Console.WriteLine("Before the loop");
            //Iterate through the AuditDetails

            foreach (var detail in details.AuditDetails)

            {
                Console.WriteLine("Inside the loop");
                //Important: the AuditDetailCollection.AuditDetails doesn’t always contain the type of AttributeAuditDetail, so make sure it is of correct type before casting

                if (detail.GetType() == typeof(AttributeAuditDetail))
                {
                    AttributeAuditDetail attributeDetail = (AttributeAuditDetail)detail;

                    String oldValue = "(no value)", newValue = "(no value)";

                    if (attributeDetail.OldValue.Contains("credit_limit"))
                        Console.WriteLine("old value: "+oldValue);

                    oldValue = attributeDetail.OldValue["credit_limit"].ToString();

                    if (attributeDetail.NewValue.Contains("credit_limit"))
                        Console.WriteLine("new value: "+newValue);

                    newValue = attributeDetail.NewValue["credit_limit"].ToString();

                    //TODO: Use the old value and new value in the Business Logic

                }
            }
            Console.WriteLine("After the loop");
            Console.ReadLine();
        }
    }
}

What happens is that I get the following result in the command line:

Connected
Before the loop
Inside the loop
old value: (no value)
new value: (no value)
Inside the loop

And then it breaks on exception, telling me in Visual Studio that

System.Collections.Generic.KeyNotFoundExemption: The given key was not present in the dictionary.

Can someone clarify what it is that I am doing wrong and what I can do to fix it?


Solution

  • The problem is you are checking if "new value" and "old value" contain your attribute but then trying to use it even if it is false.

    I made a slight change to your code and it works as expected.

    //Create a new RetrieveAttributeChangeHistoryRequest
    
    RetrieveAttributeChangeHistoryRequest req = new RetrieveAttributeChangeHistoryRequest();
    
    //Set the target Entity
    
    req.Target = new EntityReference("new_case", new Guid("468f8db5-4f98-eb11-57ee-0006ffc2587a"));
    
    //Set the attribute you want to retrieve specifically
    
    req.AttributeLogicalName = "credit_limit";
    
    //Execute the request against the OrgService
    
    RetrieveAttributeChangeHistoryResponse resp = (RetrieveAttributeChangeHistoryResponse)crmService.Execute(req);
    
    AuditDetailCollection details = resp.AuditDetailCollection;
    
    Console.WriteLine("Before the loop");
    //Iterate through the AuditDetails
    
    foreach (var detail in details.AuditDetails)
    
    {
        Console.WriteLine("Inside the loop");
        //Important: the AuditDetailCollection.AuditDetails doesn’t always contain the type of AttributeAuditDetail, so make sure it is of correct type before casting
    
        if (detail.GetType() == typeof(AttributeAuditDetail))
        {
            AttributeAuditDetail attributeDetail = (AttributeAuditDetail)detail;
    
            String oldValue = "(no value)", newValue = "(no value)";
    
            if (attributeDetail.OldValue.Contains("credit_limit"))
                oldValue = attributeDetail.OldValue["credit_limit"].ToString();
    
            Console.WriteLine("old value: " + oldValue);
    
            if (attributeDetail.NewValue.Contains("credit_limit"))
                newValue = attributeDetail.NewValue["credit_limit"].ToString();
    
            Console.WriteLine("new value: " + newValue);
            
            //TODO: Use the old value and new value in the Business Logic
    
        }
    }
    
    Console.WriteLine("After the loop");
    Console.ReadLine();