Click here to Skip to main content
15,887,135 members
Please Sign up or sign in to vote.
4.11/5 (2 votes)
See more:
Hi peeps I have the following group by :


C#
var groupedData = outputTable.AsEnumerable()
    .GroupBy(x => new { name = x.Field<string>("name"), source = x.Field<string>     ("source"), destination = x.Field<string>("destination") }).ToList();


this works but source and destination could be anything that I pull from the database as I need to make these groupings dynamic. So I want to build a kind of group string like this :

C#
string dyno = "name = x.Field(\"name\"), sourceAddress = x.Field(\"sourceAddress\"), destinationAddress = x.Field(\"destinationAddress\")";


and then say:
C#
var groupedData = outputTable.AsEnumerable()
    .GroupBy(x => new { dyno }).ToList();


is this possible?

What I have tried:

C#
string bob = "name = x.Field<string>(\"name\"), sourceAddress = x.Field<string>(\"sourceAddress\"), destinationAddress = x.Field<string>(\"destinationAddress\")";

var groupedData = outputTable.AsEnumerable().GroupBy(x => new { x =>  bob }).ToList();
Posted
Updated 10-Feb-16 4:46am
v2
Comments
Sascha Lefèvre 10-Feb-16 10:35am    
1) Is there a reason why you named the fields of the anonymous type 'source' and 'destination' in the first example and 'sourceAddress' and 'destinationAddress' in the second? Or could it always be the same names for the fields?
2) Is it always going to be those three fields 'name', 'source' and 'destination' or could it happen that you sometimes want to group on less or more fields?

---
[Response copied from 'accidental solution':]

1) i just edited the field names when I posted on here the second example is from my actual code thats the only reason they are different.

2) No the names can differ depending on what the user decides they want to group by. Which is why I need to build some sort of string or something to pass the lamda.

thanks.
Sascha Lefèvre 10-Feb-16 10:48am    
"No the names can differ depending on what the user decides they want to group by."

The names you're talking about here, do you mean by those what's on the left side here or what's on the right side?:
source = x.Field ("source")
mickos 10-Feb-16 10:57am    
I mean fields in the group by clause, it could be either as everything is dynamic

so name, source and destination could be called anything and may not be 3 fields it could just be a couple of fields it depends what the user wants.

so its like i need to build the entire group by clause and pass that to the linq query.
Sascha Lefèvre 10-Feb-16 11:42am    
If you've already seen my solution take another look, I edited it.

It's possible but a really advanced topic: You would have to build the group-by clause "manually" with expressions (namespace "System.Linq.Expression"): Expression Trees (C# and Visual Basic)[^]

I've done that for dynamic where-clauses and join-clauses but not with a group-by-clause yet, so I don't have a sample code ready. You might find something helpful with these Google search results: linq dynamic group by - Google Search[^]

The alternative would be to do the grouping without LINQ, by iterating over the rows of your DataTable and building groups (some collection object) of rows that have the same values in the relevant columns.

Edit:
Just remembered that there's a "LINQ Dynamic Query Library" out there which allows to do some "dynamic LINQing". Not sure if it will be able to do what you want but you might give it a try. I assume you would have to transform your DataTable into a collection of class-objects though.
ScottGu's Blog - Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)[^]
 
Share this answer
 
v2
Comments
Richard Deeming 10-Feb-16 12:04pm    
There shouldn't be any need to transform the DataTable to use it with Dynamic LINQ - just use the indexer:
.GroupBy("new(it[\"name\"] as name, it[\"sourceAddress\"] as sourceAddress, it[\"destinationAddress\"] as destinationAddress)", "it")

You can get the latest version from NuGet:
System.Linq.Dynamic[^]
Sascha Lefèvre 10-Feb-16 12:22pm    
Richard, you're a genius! :) You might want to post this as a solution!?
Richard Deeming 10-Feb-16 13:25pm    
I think dynamic LINQ has some drawbacks. I've come up with an alternative solution (#3) which I think would work better in this case.
BillWoodruff 10-Feb-16 13:28pm    
+5 a great start ...
Sascha Lefèvre 10-Feb-16 13:42pm    
Thank you, Bill :) Comparatively, a rather humble start ;)
Dynamic LINQ[^] is one option, but it has some drawbacks. You have to format the list of column names into an appropriate query, using a syntax that looks like it's loosely based on VB.NET; and, as there's no way to create an anonymous type at runtime, the library has to emit and load a new in-memory assembly for each set of columns used.

Depending on what you're doing with the groups, you might have better luck using a custom class to define the grouping:
C#
public sealed class DynamicDataRowGroup : DynamicObject, ICustomTypeDescriptor, IEquatable<DynamicDataRowGroup>
{
    private readonly DataRow _row;
    private readonly ISet<string> _columns;
    private readonly PropertyDescriptorCollection _properties;
    
    public DynamicDataRowGroup(DataRow row, IEnumerable<string> columns)
    {
        if (row == null) throw new ArgumentNullException("row");
        if (columns == null) throw new ArgumentNullException("columns");
        
        _row = row;
        _columns = new HashSet<string>(columns, StringComparer.OrdinalIgnoreCase);
        
        var properties = _columns.Select(name => DynamicDataRowGroupProperty.Create(row, name));
        _properties = new PropertyDescriptorCollection(properties.ToArray<PropertyDescriptor>(), true);
    }
    
    public DynamicDataRowGroup(DataRow row, params string[] columns) : this(row, columns.AsEnumerable())
    {
    }
    
    public override int GetHashCode()
    {
        int result = 0;
        foreach (string column in _columns)
        {
            object value = _row[column];
            int code = (value == null) ? 0 : value.GetHashCode();
            result = unchecked((result * 397) + code);
        }
        
        return result;
    }
    
    public override bool Equals(object obj)
    {
        return Equals(obj as DynamicDataRowGroup);
    }
    
    public bool Equals(DynamicDataRowGroup other)
    {
        if (ReferenceEquals(other, null)) return false;
        if (ReferenceEquals(other, this)) return true;
        if (!_columns.SetEquals(other._columns)) return false;
        return _columns.All(c => Equals(_row[c], other._row[c]));
    }
    
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return _columns;
    }
    
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (_columns.Contains(binder.Name))
        {
            result = _row[binder.Name];
            return true;
        }
        
        return base.TryGetMember(binder, out result);
    }
    
    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
        if (indexes != null && indexes.Length == 1)
        {
            string name = indexes[0] as string;
            if (name != null && _columns.Contains(name))
            {
                result = _row[name];
                return true;
            }
        }
        
        return base.TryGetIndex(binder, indexes, out result);
    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return _properties;
    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        return _properties;
    }
    
    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }

    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return null;
    }
    
    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return AttributeCollection.Empty;
    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return EventDescriptorCollection.Empty;
    }
    
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return EventDescriptorCollection.Empty;
    }

    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return null;
    }

    string ICustomTypeDescriptor.GetClassName()
    {
        return null;
    }

    string ICustomTypeDescriptor.GetComponentName()
    {
        return null;
    }

    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return null;
    }

    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return null;
    }
    
    private sealed class DynamicDataRowGroupProperty : PropertyDescriptor
    {
        private static readonly Attribute[] EmptyAttributes = new Attribute[0];
        private readonly Type _propertyType;
        
        private DynamicDataRowGroupProperty(string name, Type propertyType) : base(name, EmptyAttributes)
        {
            _propertyType = propertyType;
        }

        public override Type ComponentType
        {
            get { return typeof(DynamicDataRowGroup); }
        }

        public override Type PropertyType
        {
            get { return _propertyType; }
        }

        public override bool IsReadOnly
        {
            get { return true; }
        }

        public override object GetValue(object component)
        {
            var group = component as DynamicDataRowGroup;
            return (group == null) ? null : group._row[Name];
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        public override bool CanResetValue(object component)
        {
            return false;
        }

        public override void ResetValue(object component)
        {
            throw new NotSupportedException();
        }

        public override void SetValue(object component, object value)
        {
            throw new NotSupportedException();
        }
        
        public static DynamicDataRowGroupProperty Create(DataRow row, string name)
        {
            DataColumn column = row.Table.Columns[name];
            if (column == null) throw new ArgumentException(string.Format("Column '{0}' was not found.", name));
            return new DynamicDataRowGroupProperty(name, column.DataType);
        }
    }
}

With that class in place, you can now group your rows like this:
C#
var groupedData = outputTable.AsEnumerable()
    .GroupBy(row => new DynamicDataRowGroup(row, "name", "sourceAddress", "destinationAddress"))
    .ToList();

You can either cast the keys to dynamic and access the properties directly:
C#
dynamic key = groupedData[0].Key;
string name = key.name;

Or you can use any binding mechanism that relies on the TypeDescriptor (such as Eval in ASP.NET) to retrieve the properties:
ASP.NET
<asp:literal runat="server"
    Text='<%# Eval("Key.name") %>'
/>
 
Share this answer
 
Comments
BillWoodruff 10-Feb-16 13:27pm    
+5 Another mind-expander !
Sascha Lefèvre 10-Feb-16 13:41pm    
+5 Would upvote again :)
Sascha Lefèvre 10-Feb-16 13:49pm    
PS: I think you should post this as a Tip/Trick or Article
Jörgen Andersson 10-Feb-16 15:55pm    
Agree with Sascha

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900