fjleon
fjleon

Reputation: 341

How to pass a LINQ result to a method?

From Efficient data structure to hold employee's activities? , i have a List of type ActividadEmpleado which is declared as:

public string Empleado { get; set; }
public DateTime Fecha { get; set; }
public string Actividad { get; set; }

The LINQ query variable reorders the result in the way i need, which is to store by date and then by ActividadEmpleado and a string. However, var types cannot be passed to methods, so searching this site i am finding out that i either need to create a class to store the results or to modify the LINQ variable to return a List, but i am having issues with the proper declaration.

The LINQ variable is:

var queryActividades = listaActividad
                .GroupBy(a => a.Fecha, (fecha, fechaActividades) => new
                {
                    Fecha = fecha,
                    FechaActividades = fechaActividades
                        .GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new
                        {
                            Actividades = actividadesEmpleado,
                            NombreEmpleado = nombreEmpleado
                        })
                        .OrderBy(a => a.NombreEmpleado)
                })
                .OrderBy(a => a.Fecha);

Visual Studio says that queryActividades is:

IOrderedEnumerable<'a>
Anonymous Types:
'a new datetime fecha, iorderedenumerable<'b>
'b new IEnumerable<ActividadEmpleado> Actividades, string NombreEmpleado

I need to pass queryActividades to another method. I tried passing it as an Object but then i lose the extension methods such as Where<> (can i cast it somehow?)

I also read that declaring the results as a tuple should work, but i think declaring a new class is cleaner.

I am just starting with LINQ, i have avoided it to use regular data structures but in this case it's really helpful and would like to know how to either handle anonymous types in them or convert the result to a regular List

Final solution:

class GrupoActividad
{
    public DateTime Fecha { get; set; }
    public IEnumerable<Actividad> Actividades { get; set; }
}

class Actividad
{
    public IEnumerable<ActividadEmpleado> Actividades { get; set; }
    public string NombreEmpleado { get; set; }
}

var queryActividades = listaActividad
                .GroupBy(a => a.Fecha, (fecha, fechaActividades) => new GrupoActividad
                {
                    Fecha = fecha,
                    Actividades = fechaActividades
                        .GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Actividad
                        {
                            Actividades = actividadesEmpleado,
                            NombreEmpleado = nombreEmpleado
                        })
                        .OrderBy(a => a.NombreEmpleado)
                })
                .OrderBy(a => a.Fecha);

Receiving method:

var actividades = from a in queryActividades
                       where a.Fecha == fechaCiclo
                       select new { a.Fecha, a.Actividades };
            foreach (var item in actividades)
            {
                //cycle that holds each day's activities
                foreach (var empleado in item.Actividades)
                {
                    //cycle that holds each employee with activities in that day
                    foreach (var actividad in empleado.Actividades)
                    {
                        //final cycle that actually reads the activity
                        ActividadEmpleado actividadEmpleado = (ActividadEmpleado)actividad;
                    }
                }
            }

Upvotes: 2

Views: 2903

Answers (3)

Ivan Stoev
Ivan Stoev

Reputation: 205539

You can use the approach presented by D Stanley. But it would be kind a boring to have to create such classes for any similar query that you write in the future. Instead, you can introduce as generic class for that, like this

public class Grouping<TKey, TElement>
{
    public TKey Key { get; set; }
    public IEnumerable<TElement> Elements { get; set; }
}

and use it instead of the anonymous types like this

var queryActividades = listaActividad
    .GroupBy(a => a.Fecha, (fecha, fechaActividades) => new Grouping<DateTime, Grouping<string, ActividadEmpleado>>
    {
        Key = fecha,
        Elements = fechaActividades
            .GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Grouping<string, ActividadEmpleado>
            {
                Key = nombreEmpleado,
                Elements = actividadesEmpleado
            })
            .OrderBy(a => a.Key)
    })
    .OrderBy(a => a.Key);

which can be be passed as IEnumerable<Grouping<DateTime, Grouping<string, ActividadEmpleado>>>.

As you can see, there is a trade off between reusability and readability. Basically this is a Tuple with a little more meaningful names. Note that although we cannot improve the verbosity in the result, we can use a similar technique to Tuple.Create to remove the verbosity inside the query, by adding a class like this

public static class Grouping
{
    public static Grouping<TKey, TElement> Create<TKey, TElement>(TKey key, IEnumerable<TElement> elements)
    {
        return new Grouping<TKey, TElement> { Key = key, Elements = elements };
    }
}

and use it like this

var queryActividades = listaActividad
    .GroupBy(a => a.Fecha, (fecha, fechaActividades) => Grouping.Create(
        fecha, fechaActividades
            .GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => Grouping.Create(
                nombreEmpleado, actividadesEmpleado))
            .OrderBy(a => a.Key)))
    .OrderBy(a => a.Key);

Upvotes: 2

D Stanley
D Stanley

Reputation: 152501

Right now you are creating a collection that's based on an anonymous type (actually two anonymous types), which cannot practically be passed to another method (other than by using reflection or dynamic). The cleanest way is to create a concrete type that represents the collection - something like

public class ActivityGroup
{
    public DateTime Fecha {get; set;}
    public IEnumerable<Activity> Activities {get; set;}
}

public class Activity
{
    public IEnumerable<Activity> Actividades {get; set;}
    public string NombreEmpleado {get; set;}
}

then change your query to:

var queryActividades = listaActividad
                .GroupBy(a => a.Fecha, (fecha, fechaActividades) => new ActivityGroup
                {
                    Fecha = fecha,
                    FechaActividades = fechaActividades
                        .GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Activity
                        {
                            Actividades = actividadesEmpleado,
                            NombreEmpleado = nombreEmpleado
                        })
                        .OrderBy(a => a.NombreEmpleado)
                })
                .OrderBy(a => a.Fecha);

and pass it as an IEnumerable<ActivityGroup>

Upvotes: 5

Thomas Levesque
Thomas Levesque

Reputation: 292355

You can't pass anonymous types between methods; if you need to pass the data to another method, you need to create an explicit named type that contains the Actividades and NombreEmpleado properties, and another one with Fecha and FechaActividades.

Upvotes: 0

Related Questions