Thierry
Thierry

Reputation: 6458

Build a dictionary of objects and append text to one of its properties using Linq

This a continuation from my original post append string in List contained in a dictionary using LINQ and while it was answered correctly, I actually need slightly different requirement.

In short, I had a dictionary with Key of type string and a Value of type string. The Value had to be set to List which had to be converted to a comma separated string.

That was fine until I realized I really needed the full object to be stored in my dictionary rather than the string which contained the comma separated values but one of the property of my object still needs to have the List to be converted to a comma separated string.

This is based on the original answer provide in my previous post:

Dictionary<string, string> myResult = Mapping.Select(m => m)
   .Select((c, i) => new { Key = c.FieldName, Value = c.Value })
   .GroupBy(o => o.Key, o => o.Value)
   .ToDictionary(grp => grp.Key, grp => 
    string.Join(Environment.NewLine, grp.Where(x => !string.IsNullOrEmpty(x))));

As you can see, setting the Key and Value based on the object that's stored in Mappping List.

The object is contained in my Mapping is defined as follows:

public class Field
{
   public string FieldName  { get; set; }
   public string FieldType  { get; set; }
   public string FieldValue { get; set; }
}

It's not always easy to express what you want but I'll do my best to somehow make it clear!

What I really need is to end up with a dictionary where the key is set to the FieldName and the value is set to the Field object but the FieldValue of the object needs to have the same logic that's applied in the above code i.e. FieldValue gets appended to itself if multiple identical FieldName have been defined in the Mapping list.

It may be easier to understand by looking at some data. Here is what the Mapping list would look like if hardcoded:

Mapping.Add(new Field{ FieldName="Name", FieldValue="Thierry", FieldType="1" })
Mapping.Add(new Field{ FieldName="Address", FieldValue="The Plaza", FieldType="2" })
Mapping.Add(new Field{ FieldName="Address", FieldValue="My Road", FieldType="2" })
Mapping.Add(new Field{ FieldName="Address", FieldValue="My Road Again", FieldType="2" })
Mapping.Add(new Field{ FieldName="City", FieldValue="New York", FieldType="1" })
Mapping.Add(new Field{ FieldName="Country", FieldValue="USA", FieldType="1" })

The dictionary created by the code above generates the following:

MyResult: Key="Name", Value="Thierry"
MyResult: Key="Address", Value="ThePlaza\r\nMy Road\r\nMy Road Again"
MyResult: Key="City", Value="New York"
MyResult: Key="Country", Value="USA"

What I want is

MyResult: Key="Name", Field {FieldName="Name", FieldType="1", FieldValue="Thierry"}
MyResult: Key="Address", Field {FieldName="Address", FieldType="2", FieldValue="ThePlaza\r\nMy Road\r\nMy Road Again"}
MyResult: Key="City", Field {FieldName="City", FieldType="1", FieldValue="New York"}
MyResult: Key="Country", Field {FieldName="Country", FieldType="1", FieldValue="USA"}

As you can see, my dictionary has its key set to FieldName, and the value is set to the Field object but there is only a single entry for the Address (instead of 3) and the values have been concatenated!!

I've tried the following but a) it's not working and b) I'm still struggling with the logic that can be applied to Linq so once again I'm relying on of you smart linq guys to help me out!

Dictionary<string, Field> uniqueFieldList1 = 
Mapping.Select(m => m)
       .Select((c, i) => new
       {
         Key = c.FieldName,
         Value = new Field()
            {
              FieldName = c.FieldName,
              FieldType = c.FieldType,
              FieldValue = 
              Mapping.Select(m => m)
                     .Select((g, t) => new { Key = g.FieldName, Value = g.FieldValue })
                     .GroupBy(o => o.Key, o => o.Value)
                     .ToDictionary(grp => grp.Key, grp => string.Join(Environment.NewLine, grp.Where(x => !string.IsNullOrEmpty(x))))[c.FieldName]
            }
        })
        .GroupBy(o => o.Key, o => o.Value)
        .ToDictionary(grp => grp.Key, grp => grp.Key);

My last line of code is giving an error but I'm assuming it's really related to the last 2

.GroupBy(o => o.Key, o => o.Value)
.ToDictionary(grp => grp.Key, grp => grp.Key);

Also can you tell me if the way I went on setting the FieldValue is correct and is this the most optimized way to achieve this?

I hope the above makes sense.

Thanks.

T

Upvotes: 2

Views: 375

Answers (2)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236248

Group your fields by name, then create new Field from each group with aggregated field value. And creating dictionary is simple:

Mapping.GroupBy(f => f.FieldName)
   .Select(g => new Field { 
       FieldName = g.Key,
       FieldType = g.First().FieldType // Note, that I get type from first field
       FieldValue = String.Join(Environment.NewLine, g.Select(f => f.FieldValue))
   })
   .ToDictionary(f => f.FieldName);

I didn't add filter Where(f => !String.IsNullOrEmpty(f.FieldValue)) when joined values, because there is no empty values in your sample data.

BTW if you are aggregating values, then it's likely you need grouping by both other properties - name and type. Because if you'll have two fields with same name, but different type, that will not be reflected in results. Also what type should have filed in dictionary in this case?

Upvotes: 2

pescolino
pescolino

Reputation: 3123

Can the FieldType values be different for the same FieldName value? If not you can use lazyberezovskys' solution.

Otherwise you will need to group by both FieldName and FieldType. You can (for example) create Field instances for the key with FieldValue set to null:

Mapping.GroupBy(m => new Field
                     {
                         FieldName = m.FieldName,
                         FieldType = m.FieldType
                     })
       .ToDictionary(grp => grp.Key,
                     grp => string.Join(Environment.NewLine,
                                        grp.Select(f => f.FieldValue)));

The result is of type Dictionary<Field, string> here where the key is a Field representing the FieldName and FieldType only and the value is just the combined string. Of course you can also create Field instances for the values repeating FieldName and FieldValue from the group key.

To use Field as the key of a dictionary you should have proper GetHashCode and Equals implementations for the Field class.

If you do not need the lookup capabilities of a dictionary you can also use this:

Mapping.GroupBy(m => new
                     {
                         m.FieldName,
                         m.FieldType
                     })
       .Select(grp => new Field
                      {
                          FieldName = grp.Key.FieldName,
                          FieldType = grp.Key.FieldType,
                          FieldValue = string.Join(Environment.NewLine,
                                                   grp.Select(f => f.FieldValue))
                      });

This will return an IEnumerable<Field> with distinct FieldName and FieldType values.

Upvotes: 1

Related Questions