Home > Software design >  Linq - Join collection with child collection of other collection
Linq - Join collection with child collection of other collection

Time:02-16

I have a collection of users, each with a child collection of memberships. And then I also have a club collection. The child collection of each user is populated with memberships containing a club ID only. What I want is for each membership to also contain the club entity matching the club ID.

My entities:

public class User
{
    public string Name { get; set; }
    public IEnumerable<Membership> Memberships { get; set; }
}
public class Membership
{
    public Guid ClubId { get; set; }
    public Club Club { get; set; }
}
public class Club
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

Populated with data:

var club1Id = new Guid("FFEB77B9-1616-463B-B36E-F95F7E255FDE");
var club2Id = new Guid("7A6ECD38-5AEB-418B-9DD5-FBD777CE190C");

var users = new List<User>
{
    new User
    {
        Name = "Patrick Stewart",
        Memberships = new List<Membership>
        {
            new Membership { ClubId = club1Id },
            new Membership { ClubId = club2Id }
        }
    },
    new User
    {
        Name = "Brent Spiner",
        Memberships = new List<Membership>
        {
            new Membership { ClubId = club1Id }
        }
    },
    new User
    {
        Name = "Wesley Crusher",
        Memberships = null
    }
};

var clubs = new List<Club>
{
    new Club
    {
        Id = club1Id,
        Name = "Officers Club"
    },
    new Club
    {
        Id = club2Id,
        Name = "Captains Club"
    }
};

And then I need a LINQ query that adds the matched Club objects to the Membership objects.

So that when I print out users like this:

foreach (var user in users)
{
    Console.WriteLine($"User: {user.Name}");
    if (user.Memberships != null)
    {
        foreach (var membership in user.Memberships)
        {
            Console.WriteLine($"  Membership: {membership.Club?.Name}");
        }
    }
}

It will look like this:

User: Patrick Stewart
  Membership: Officers Club
  Membership: Captains Club
User: Brent Spiner
  Membership: Officers Club
User: Wesley Crusher

I want to do it in one go with a LINQ expression, so that I don't have to involve for-loops.

CodePudding user response:

I ended up solving it with this LINQ expression:

users = users.Select(user => new { 
        user, 
        Memberships = user.Memberships?
        .GroupJoin(clubs, membership => membership.ClubId, club => club.Id, (membership, club) => new { membership, club})
        .SelectMany(mc => mc.club.DefaultIfEmpty(), (mc, club) =>
        {
            mc.membership.Club = club;
            return mc.membership;
        })
    })
    .Select(x =>
    {
        x.user.Memberships = x.Memberships;
        return x.user;
    })
    .ToList();

CodePudding user response:

LINQ is not intended to be used for modifying data, rather querying data and/or creating new data from data.

Instead, use a nested foreach loop.

First, create a Dictionary to make finding Club objects easier:

var clubDict = clubs.ToDictionary(c => c.Id);

Then, use the clubDict to modify each Membership to include the matching Club entity:

foreach (var user in users.Where(u => u.Memberships != null))
    foreach (var membership in user.Memberships)
        membership.Club = clubDict[membership.ClubId];
  • Related