C# LINQ Help - Query over a Dictionary<string, string>

I have a List<Audit> which contains a Dictionary<string, string> and I need to be able to search the Dictionary values and return the Audit Object where the value matches.

I have the following code that loops over every Audit object and and returns the index when it finds a match. This doesn't seem very efficient to me and was looking for a cleaner way to write this logic using LINQ. Any ideas would be appreciated.

                for (int i = 0; i < audit.Count - 1; i++)
                {
                    if (audit[i].FindEntryId("12346"))
                    {
                        Console.WriteLine("Found Id: {0}", audit[i].Fields["Id"].ToString());
                    }
                }

Open in new window


public class AuditParse
{
        private List<Audit> audit = new List<Audit>();

        private class Audit
        {
            public Dictionary<string, string> Fields = new Dictionary<string, string>();

            public void Field(string fieldName, string fieldValue)
            {
                Fields[fieldName] = fieldValue;
            }

            public bool FindRecordId(string id)
            {
                return this.Fields.ContainsValue(id);
            }
            
            //Deep copy the Fields dictionary
            public Audit Clone()
            { 
                Audit cloned = new Audit();
                cloned.Fields = new Dictionary<string, string>(this.Fields);

                //cloned.Fields = this.Fields.ToDictionary(i => i.Key, i => i.Value);
                return cloned;
            }
        }
}

Open in new window

nightshadzAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

nightshadzAuthor Commented:
I think this will work. Can anyone see any flaws?

var a = audit.Select(r => r.Fields).Where(d => d.ContainsValue("123456"));
käµfm³d 👽Commented:
This doesn't seem very efficient to me...
It might be more code, but on a technical level it's more efficient than LINQ. LINQ is just simpler code (usually).

That won't work because you've projected out the Fields property. You won't get back the actual Audit objects. You should only need the Where:

var query = audit.Where(a => a.Fields.ContainsValue("123456"));

Open in new window


Keep in mind that the above isn't actually executed until you foreach over "query" (in some manner). A LINQ query's body is merely a setup, not an execution. Some external force like foreach or ToList is what causes it to actually be executed.
Bob LearnedCommented:
What is the "key" for the dictionary?  I would think that if you need the values out of the dictionary by "ID", that would be a simple lookup.
it_saigeDeveloperCommented:
Along with what Kaufmed has already said, you stipulate:
return the Audit Object where the value matches
Yet you will end up with an IEnumerable<Audit>.  If you want a single object, are expecting a single object or want the first object, then you would need to use the appropriate extension method to cast the IEnumerable<Audit> to an Audit item:

Single Extension -
// Returns a single audit record from an IEnumerable, will throw an exception if 0 audits; or if more than 1 audit; is found 
var a = audit.Single(d => d.ContainsValue("123456"));

Open in new window


SingleOrDefault Extension -
// Returns a single audit record from an IEnumerable, will return a null if 0 audits; or if more than 1 audit; is found
var a = audit.SingleOrDefault(d => d.ContainsValue("123456"));

Open in new window


First Extension -
// Returns the first audit record from an IEnumerable, will throw an exception if 0 audits are found 
var a = audit.First(d => d.ContainsValue("123456"));

Open in new window


FirstOrDefault Extension -
// Returns the first audit record from an IEnumerable, will return a null if 0 audits are found
var a = audit.FirstOrDefault(d => d.ContainsValue("123456"));

Open in new window


Consider the following example -
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace EE_Q28686794
{
	class Program
	{
		static List<Audit> audits = new List<Audit>();
		static void Main(string[] args)
		{
			audits.Add(new Audit() { Name = "Paul", Items = new Dictionary<string,object>() { {"Color", Color.Brown}, { "Shoes", ShoeBrand.Nike } } });
			audits.Add(new Audit() { Name = "Jim", Items = new Dictionary<string, object>() { { "Color", Color.Blue }, { "Shoes", ShoeBrand.Adidas } } });
			audits.Add(new Audit() { Name = "Bill", Items = new Dictionary<string, object>() { { "Color", Color.Beige }, { "Shoes", ShoeBrand.Converse } } });
			audits.Add(new Audit() { Name = "Peter", Items = new Dictionary<string, object>() { { "Color", Color.White }, { "Shoes", ShoeBrand.Converse } } });
			audits.Add(new Audit() { Name = "Jason", Items = new Dictionary<string, object>() { { "Color", Color.Green }, { "Shoes", ShoeBrand.Adidas } } });
			audits.Add(new Audit() { Name = "Bob", Items = new Dictionary<string, object>() { { "Color", Color.Turquoise }, { "Shoes", ShoeBrand.Nike } } });

			Console.WriteLine("Proper usage of single");
			var single = audits.Single(audit => audit.Items.ContainsValue(Color.Turquoise));
			Console.WriteLine(single);
			Console.WriteLine();

			Console.WriteLine("Exception causing usage of single");
			try
			{
				var singleEx = audits.Single(audit => audit.Items.ContainsValue(ShoeBrand.Nike));
				Console.WriteLine(singleEx);
			}
			catch (Exception ex)
			{
				Console.WriteLine("Trying to get a single {0} from audits resulted in an exception.  {1}", ShoeBrand.Nike, ex.Message);
			}
			Console.WriteLine();

			Console.WriteLine("Proper usage of first");
			var first = audits.First(audit => audit.Items.ContainsValue(ShoeBrand.Adidas));
			Console.WriteLine(first);
			Console.WriteLine();

			Console.WriteLine("Exception causing usage of first");
			try
			{
				var firstEx = audits.First(audit => audit.Items.ContainsValue(Color.Violet));
				Console.WriteLine(firstEx);
			}
			catch (Exception ex)
			{
				Console.WriteLine("Trying to get the first {0} from audits resulted in an exception.  {1}", Color.Violet, ex.Message);
			}
			Console.WriteLine();

			Console.ReadLine();
		}
	}

	class Audit
	{
		public string Name { get; set; }
		public Dictionary<string, object> Items { get; set; }

		public override string ToString()
		{
			string result = string.Empty;
			result = string.Format("{0}'s items are: ", Name);
			foreach (var item in Items.Values)
			{
				if (item is Color)
					result += string.Format("{0}", item);
				else if (item is ShoeBrand)
					result += string.Format(" and {0}", item);
			}
			return result;
		}
	}

	enum ShoeBrand
	{
		None = 0,
		Nike = 1,
		Adidas = 2,
		Converse = 3
	}
}

Open in new window

Which produces the following output -Capture.JPGIt should be noted that Single(), SingleOrDefault(), First() and FirstOrDefault() retrieve the object immediately as they represent the completion of an enumerative call.

More on these methods can be found here: LINQ Single vs SingleOrDefault vs First vs FirstOrDefault

-saige-

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
nightshadzAuthor Commented:
Thanks everyone! Lots of excellent tips here. Just playing around with these suggestions has made me learn a lot!

//One way to search (or = old record id)
                var a = audit.Single(s => s.Fields.ContainsValue(correction.Attribute("or").Value));

//Second way to search (or = old record id)
                for (int i = 0; i < audit.Count-1; i++)
                {
                    if (audit[i].Fields.ContainsValue(correction.Attribute("or").Value))
                    {
                        Console.WriteLine("Found Id: {0}", audit[i].Fields["Id"].ToString());

                        break;
                    }
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
C#

From novice to tech pro — start learning today.