5

I get passed a list of small objects:

var smalls = new List<Small>();
smalls.AddRange( new Small[] { new Small{Name = "Aa", Id = 1, Value = "v1"},
                               new Small{Name = "Bb", Id = 1, Value = "v2"},
                               new Small{Name = "Cc", Id = 1, Value = "v3"},
                               new Small{Name = "Dd", Id = 1, Value = "v4"},
                               new Small{Name = "Ee", Id = 1, Value = "v5"},
                               new Small{Name = "Ff", Id = 1, Value = "v6"},
                               new Small{Name = "Gg", Id = 1, Value = "v7"} } );

From the above list I would like to populate an object that looks like this:

var large = new Large
    {
        Id = 1,
        Aa = "v1",
        Bb = "v2",
        Cc = "v3",
        Dd = "v4",
        Ee = "v5",
        Ff = "v6",
        Gg = "v7"
    }

The current code relies on the order of the list to populate the Large object however this does not feel secure enough and am looking for a more reliable way to map the list into the object.

Current code:

Large large = new Large
{
    Id = smalls[0].Id,
    Aa = smalls[0].Value,
    Bb = smalls[1].Value,
    Cc = smalls[2].Value,
    Dd = smalls[3].Value,
    Ee = smalls[4].Value,
    Ff = smalls[5].Value,
    Gg = smalls[6].Value
}

So I am looking to eliminate the assumption that they are in the correct order and populate the new fields based off of the Name string in the Small object into the corresponding field in the Large object.

Thanks for any input!!

2
  • Are you only concerned about the order or is there a name integrity consideration? Commented May 1, 2017 at 16:48
  • Great question, I am only concerned about the order issue now. The data in the Name field of the Small objects will always match a corresponding field name in the Large object. Commented May 1, 2017 at 17:04

5 Answers 5

3

You can group values by Id, and make a few methods to extract values based on the Name field:

private static string GetValueByName(IDictionary<string,string> data, string name) {
    string res;
    return data.TryGetValue(name, out res) ? res : null;
}
private static Large MakeFromAttributes(IEnumerable<Small> data, int id) {
    var dictByName = data.ToDictionary(s => s.Name, s => s.Value);
    return new Large {
        Id = id
    ,   Aa = GetValueByName(dictByName, "Aa")
    ,   Bb = GetValueByName(dictByName, "Bb")
    ,   Cc = GetValueByName(dictByName, "Cc")
    ,   Dd = GetValueByName(dictByName, "Dd")
    ,   Ee = GetValueByName(dictByName, "Ee")
    ,   Ff = GetValueByName(dictByName, "Ff")
    ,   Gg = GetValueByName(dictByName, "Gg")
    };
}

With these helper methods you can construct a LINQ query as follows:

var largeList = smalls
    .GroupBy(s => s.Id)
    .Select(g => MakeFromAttributes(g, g.Key))
    .ToList();
Sign up to request clarification or add additional context in comments.

Comments

2

So I am looking to eliminate the assumption that they are in the correct order

Something like this perhaps?:

Aa = smalls.Single(s => s.Name == "Aa").Value

This at least still relies on the assumption that the record will be there at all, though it doesn't care about the order of the records. If you want to drop that assumption as well, you can add some error checking. Something like this perhaps:

Aa = smalls.Any(s => s.Name == "Aa") ? smalls.First(s => s.Name == "Aa") : string.Empty

It's not the most efficient thing in the world, but at least remains on one line as in your current usage. Separating into multiple lines will make it longer, but potentially more performant (if performance is even an issue... in the very small example provided it really isn't).

Those multiple lines could potentially then be re-factored into a custom extension method to put it back onto a single line? The sky's the limit, really.

Comments

2

A Dictionary(TKey, TValue) may be a more appropriate data structure for the use case since the Keys can be dynamic and any solution going from IEnumerable(Small) to Large will need to make assumptions on the composition of the collection or the aggregate object.

A simple dynamic solution is to use reflection, but this will have more overhead compared to the static lookup already proposed.

public static Large CreateLargeFromSmalls(int id, IEnumerable<Small> smalls)
{
    var largeType = typeof(Large);
    var large = new Large { Id = id };

    foreach (var small in smalls)
    {
        var prop = largeType.GetProperty(small.Name);
        if (prop != null)
        {
            prop.SetValue(large, small.Value);
        }
    }

    return large;
}

Assumptions

  • The Name of a Small will exactly match the corresponding Large property.
  • The Name of a Small is not unique and order of iteration matters.
  • If the property corresponding to the Name of a Small does not existing in the Large then it is not mapped.

Pros

  • Contract changes to Large do not affect the mapping logic.

Cons

  • Reflection overhead

Example:

var smalls = new List<Small>
{
    new Small{Name = "Aa", Id = 1, Value = "v1"},
    new Small{Name = "Bb", Id = 1, Value = "v2"},
    new Small{Name = "Cc", Id = 1, Value = "v3"},
    new Small{Name = "Dd", Id = 1, Value = "v4"},
    new Small{Name = "Ee", Id = 1, Value = "v5"},
    new Small{Name = "Ff", Id = 1, Value = "v6"},
    new Small{Name = "Gg", Id = 1, Value = "v7"}
};

var bigs =
    smalls
        .GroupBy(x => x.Id)
        .Select(g => CreateLargeFromSmalls(g.Key, g))
        .ToList();

1 Comment

I had the same idea!
0

You can group your list of Small's, then set Id as the Key of the group and other properties using reflection, will look like this

var large = smalls.GroupBy(small => small.Id)
    .Select(group =>
    {
        var result = new Large();
        result.Id = group.Key;
        var largeType = result.GetType();
        foreach (var small in group)
        {
                largeType.GetProperty(small.Name).SetValue(result, small.Value);
        }
        return result;
    }).First();

Comments

0

If I understood your question correctly, you can use reflection to set all properties in the Large class without having to worry about order of Small collection:

Large large = new Large();
foreach (var propertyInfo in large.GetType().GetProperties())
{
    var sm = smalls.FirstOrDefault(small => string.Equals(small.Name, propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase));
    if (sm != null)
        propertyInfo.SetValue(large, Convert.ChangeType(sm.Value, propertyInfo.PropertyType), null);
}

Important: Please note that for this solution to work, all properties of Large that need to be updated MUST be marked with getters and setters. eg. public string Aa { get; set; }

We first get all properties of Large by using large.GetType().GetProperties() which gets all properties. Then we compare the names with the .Name property in the Small class collection and if we find a match then we set the value of the property. You can read more about reflection here.

Screenshot of Large after trying it out:

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.