Reputation: 67
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!!
Upvotes: 5
Views: 1958
Reputation: 726479
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();
Upvotes: 3
Reputation: 21245
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
Name
of a Small
will exactly match the corresponding Large
property.Name
of a Small
is not unique and order of iteration matters.Name
of a Small
does not existing in the Large
then it is not mapped.Pros
Large
do not affect the mapping logic.Cons
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();
Upvotes: 2
Reputation: 4981
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:
Upvotes: 0
Reputation: 1308
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();
Upvotes: 0
Reputation: 218798
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.
Upvotes: 2