Monday, April 11, 2011

Use LINQ to concatenate multiple rows into single row (CSV property)

I'm looking for the LINQ equivalent to the Sybase's LIST() or MySQL's group_concat()

It'll convert:

User  Hobby
--------------
Bob   Football 
Bob   Golf 
Bob   Tennis 
Sue   Sleeping 
Sue   Drinking

To:

User  Hobby
--------------
Bob   Football, Golf, Tennis 
Sue   Sleeping, Drinking
From stackoverflow
  • That's the GroupBy operator. Are you using LINQ to Objects?

    Here's an example:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    public class Test
    {
        static void Main()
        {
            var users = new[]
            {
                new { User="Bob", Hobby="Football" },
                new { User="Bob", Hobby="Golf" },
                new { User="Bob", Hobby="Tennis" },
                new { User="Sue", Hobby="Sleeping" },
                new { User="Sue", Hobby="Drinking" },
            };
    
            var groupedUsers = users.GroupBy(user => user.User);
    
            foreach (var group in groupedUsers)
            {
                Console.WriteLine("{0}: ", group.Key);
                foreach (var entry in group)
                {
                    Console.WriteLine("  {0}", entry.Hobby);
                }
            }
        }
    }
    

    That does the grouping - can you manage the rest yourself?

    Hosam Aly : It's also possible to replace the inner loop with `String.Join(", ", group.ToArray())`, as long as the number of elements is not very large.
    John Paul Jones : Thanks! I'm going to learn LINQ myself instead of constantly bugging you ;-)
    Carter : Am I the only one that noticed the man is into sports while the woman's hobbies are sleeping and drinking? You're terrible Jon Skeet!
    Jon Skeet : @Carter: They're not *my* examples...
    Carter : Ha, you're right, my bad. Still funny.
  • See if this solution helps you:

    List<User> users = new List<User>() 
    { 
        new User {Name = "Bob", Hobby = "Football" },
        new User {Name = "Bob", Hobby = "Golf"},
        new User {Name = "Bob", Hobby = "Tennis"},
        new User {Name = "Sue", Hobby = "Sleeping"},
        new User {Name = "Sue", Hobby = "Drinking"}
    };
    
    var groupedUsers = from u in users
             group u by u.Name into g
             select new
             {
                 Name = g.First<User>().Name,
                 Hobby = g.Select(u => u.Hobby)
             };
    
    
    foreach (var user in groupedUsers)
    {
        Console.WriteLine("Name: {0}", user.Name);
        foreach (var hobby in user.Hobby)
        {
            Console.WriteLine("Hobby: {0}", hobby);
        }
    }
    
  • re the _concat aspect of your question, using:

    static class EnumerableExtensions 
    {  
        public static String AsJoined( this IEnumerable<String> enumerable )
        {
            return AsJoined( enumerable, "," );
        }
    
        public static String AsJoined( this IEnumerable<String> enumerable, String separator )
        {
            return String.Join( separator, enumerable.ToArray() );
        }
    }
    

    The outputting foreach in bruno conde and Jon Skeet's answers can become:

    Console.WriteLine( "User:\tHobbies");
    foreach ( var group in groupedUsers )
        Console.WriteLine( "{0}:\t{1}", group.Key, group.Select( g => g.Hobby ).AsJoined( ", " ) );
    

    ... and you'll get the precise result output format you asked for (yes, I know the others have already solved your problem, but its hard to resist!)

  • To do it in one Linq Statement. There is no way I'd recommend the code, but it shows that it could be done.

                var groupedUsers = from user in users
                               group user by user.User into userGroup
                               select new
                               {
                                   User = userGroup.Key,
                                   userHobies =
                                       userGroup.Aggregate((a, b) => 
                                           new { User = a.User, Hobby = (a.Hobby + ", " + b.Hobby) }).Hobby
                               }
                                ;
            foreach (var x in groupedUsers)
            {
                Debug.WriteLine(String.Format("{0} {1}", x.User, x.userHobies));
            }
    

0 comments:

Post a Comment