Reputation: 9953
(Full code available at: https://dotnetfiddle.net/tdKNgH)
I have two lists that are associated by ParentName
and I would like to join them in a specific way.
class Parent
{
public string ParentName { get; set; }
public IEnumerable<string> ChildNames { get; set; }
}
class Child
{
public string ParentName { get; set; }
public string ChildName { get; set; }
}
var parents = new List<Parent>()
{
new Parent() {ParentName = "Lee"},
new Parent() {ParentName = "Bob"},
new Parent() {ParentName = "Tom"}
};
var children = new List<Child>()
{
new Child() {ParentName = "Lee", ChildName = "A"},
new Child() {ParentName = "Tom", ChildName = "B"},
new Child() {ParentName = "Tom", ChildName = "C"}
};
I'm using a foreach loop to join, and it works, but is there a more succinct way to do it?
foreach (var parent in parents)
{
var p = parent; // to avoid foreach closure side-effects
p.ChildNames = children.Where(c => c.ParentName == p.ParentName)
.Select(c => c.ChildName);
}
Here's what the resulting parents list would look like:
Parent Children
------ --------
Lee A
Bob (empty)
Tom B,C
Upvotes: 4
Views: 256
Reputation: 6016
You can use ToLookup for best performance with a little memory penalty:
var clu = children.ToLookup(x => x.ParentName, x => x.ChildName);
parents.ForEach(p => p.ChildNames = clu[p.ParentName]);
Upvotes: 1
Reputation: 9377
Given that LINQ is based on functional principles, side effects are generally a big no-no (and also the reason for why there's no foreach
method).
I therefore suggest the following solution:
var parents = new List<Parent>()
{
new Parent() { ParentName = "Lee" },
new Parent() { ParentName = "Bob" },
new Parent() { ParentName = "Tom" }
};
var children = new List<Child>()
{
new Child() { ParentName = "Lee", ChildName = "A" },
new Child() { ParentName = "Tom", ChildName = "B" },
new Child() { ParentName = "Tom", ChildName = "C" }
};
var parentsWithChildren = parents.Select(x => new Parent
{
ParentName = x.ParentName,
ChildNames = children
.Where(c => c.ParentName == x.ParentName)
.Select(c => c.ChildName)
});
foreach (var parent in parentsWithChildren)
{
var childNamesConcentrated = string.Join(",", parent.ChildNames);
var childNames = string.IsNullOrWhiteSpace(childNamesConcentrated)
? "(empty)" : childNamesConcentrated;
Console.WriteLine("Parent = {0}, Children = {1}", parent.ParentName, childNames);
}
The solution above, do not modify the Parent
objects of the collection parents
by setting their ChildNames
. Instead it creates a new set of Parent
s with their respective ChildNames.
Upvotes: 1
Reputation: 782
Consider calling the name of a parent as Parent.Name
instead of Parent.ParentName
(a parent's parent?), Child
has the same problem...
class Parent
{
public string Name { get; set; }
public IEnumerable<string> ChildrenNames { get; set; }
}
class Child
{
public string ParentName { get; set; }
public string Name { get; set; }
}
You can completely avoid foreach
by creating parentNames
array first:
var parentNames = new[] { "Lee", "Bob", "Tom" };
var allChildren = new List<Child>()
{
new Child() {ParentName = "Lee", Name = "A"},
new Child() {ParentName = "Tom", Name = "B"},
new Child() {ParentName = "Tom", Name = "C"}
};
Such that the parents are constructed entirely by LINQ without any side-effects (no updates to any variables), and it should be very simple:
var parents =
from parentName in parentNames
join child in allChildren on parentName equals child.ParentName into children
select new Parent { Name = parentName, ChildrenNames = children.Select(c => c.Name) };
Upvotes: 1
Reputation: 13286
You could do a group join. LINQ isn't meant to update, though. So I'm not sure whether this would actually get you anywhere useful.
IEnumerable<Parent> parents = ...;
var parentsWithChildren = parents.GroupJoin(children,
c => c.ParentName,
c => c.ParentName,
(a, b) => new
{
Parent = a,
ChildNames = b.Select(x => x.ChildName)
});
foreach (var v in parentsWithChildren)
{
v.Parent.ChildNames = v.ChildNames;
}
This would certainly help if all you were given were parent names and children, rather than full Parent
objects, since then you could just group join that collection to the child names, and create instances of parents where I create an anonymous type ((a, b) => new { ... }
). But since I'm assuming your Parent
objects would realistically hold more than just a name and that this is just an example, this seems like your best bet.
Upvotes: 1
Reputation: 31077
You can add an extension method for enumerables:
public static void Each<T>(this IEnumerable<T> source, Action<T> action)
{
if (action == null)
return;
foreach (T obj in source)
action(obj);
}
And then do:
parents.Each(p => p.ChildNames = children.Where(c => c.ParentName == p.ParentName)
.Select(c => c.ChildName));
Upvotes: 1