LINQ按自定义类分组

4

我正在使用c#中的linq对DataTable进行操作,想知道如何按多个字段进行分组。我发现可以使用匿名类来实现,例如:

var a = dt.AsEnumerable().GroupBy(e => new { name = e["Name"] })

问题是,我的分组键在运行时是动态确定的。因此,我尝试使用字典进行分组:
var a = dt.AsEnumerable().GroupBy(e => GetKey(e))

这里的GetKey(e)返回一个Dictionary<string, object>。思路是字典中的值代替匿名类的键和值。我的问题是,linq查询不再按预期工作 - 它似乎根本没有进行分组。我的直觉是因为内部必须比较每个DataTable行的分组键,而字典键不被视为相等,仅仅因为它们具有相同的键和值,所以每一行都有不同的分组键,因此不能聚合。

如果我对此正确,应该如何解决?我尝试将字典包装在一个类中,并覆盖Equals()方法,但从未调用过。

3个回答

1
为什么不直接让 GetKey() 返回一个字符串类型的键呢?
var a = dt.AsEnumerable().GroupBy(e => new { name = e[GetKey(e)] });

您可以从指定列中的值创建键,并将其组合成一个字符串以进行分组:

var keyDictionary = new Dictionary<string, IEnumerable<string>>();
keyDictionary.Add("Table1", new List<string> {"Group", "Position"});

var dt = new DataTable("Table1");
dt.Columns.AddRange(new [] { new DataColumn("Id", typeof(int)), new DataColumn("Group", typeof(string)), new DataColumn("Position", typeof(string)), new DataColumn("Name", typeof(string))});
var rowItemArrays = new [] { new object[] { 1, "Alpha", "Left", "Bob" }, new object[] { 2, "Alpha", "Right", "Mary"}, new object[] { 3, "Beta", "Right", "Bill"}, new object[] { 4, "Alpha", "Right", "Larry"}};
rowItemArrays.ToList().ForEach(i => dt.Rows.Add(i));

Func<DataRow, string> GetKeys = (dataRow) => string.Join("", keyDictionary[dataRow.Table.TableName].Select(key => dataRow[key].ToString()).ToArray());

var a = dt.AsEnumerable().GroupBy(GetKeys);

你需要小心处理空值等情况...


虽然我曾经考虑过这个想法,但最终没有采用它,因为我在迭代LINQ查询结果时利用了字典数据。我想我仍然可以将所有值附加到单个字符串中,并将其解析回键/值对。 - toasteroven

1
这是从帮助文件中抄来的,我还没有实现,但应该可以工作。问题在于您需要一个单一的类进行比较,并且它在比较中使用了ToString和GetHashCode(这就是为什么您的字典想法不起作用,它不是在比较字典的元素,而是在比较其ToString和GetHashCode)。让GetKey返回以下类,并使用上面的字典填充类的keyBag:
class PortableKey
{
    public Dictionary<string, object> keyBag { get; set; }

    public PortableKey(Dictionary<string, object> Keys)
    {
        this.keyBag = Keys;
    }

    public override bool Equals(object obj)
    {
        PortableKey other = (PortableKey)obj;
        foreach (KeyValuePair<string, object> key in keyBag)
        {
            if (other.keyBag[key.Key] != key.Value) return false;
        }
        return true;
    }

    public override int GetHashCode()
    {
        // hashCodes is an array of integers represented as strings. { "1", "4", etc. }
        string[] hashCodes = keyBag.Select(k => k.Value.GetHashCode().ToString()).ToArray();
        // hash is the Hash Codes all joined in a single string. "1,4,etc."
        string hash = string.Join(",", hashCodes);
        // returns a single hash code for the combined hash. 
        // Note, this is not guaranteed unique, nor is it intended to be so.
        return hash.GetHashCode();
    }
    public override string ToString()
    {
        string[] values = keyBag.Select(k => k.Value.ToString()).ToArray();
        return string.Join(",", values);
    }
}

-1
var keyDictionary = new Dictionary<string, IEnumerable<string>>();
keyDictionary.Add("Table1", new List<string> {"Group", "Position"});

var dt = new DataTable("Table1");
dt.Columns.AddRange(new [] { new DataColumn("Id", typeof(int)), new DataColumn("Group", typeof(string)), new DataColumn("Position", typeof(string)), new DataColumn("Name", typeof(string))});
var rowItemArrays = new [] { new object[] { 1, "Alpha", "Left", "Bob" }, new object[] { 2, "Alpha", "Right", "Mary"}, new object[] { 3, "Beta", "Right", "Bill"}, new object[] { 4, "Alpha", "Right", "Larry"}};
rowItemArrays.ToList().ForEach(i => dt.Rows.Add(i));

Func<DataRow, string> GetKeys = (dataRow) => string.Join("", keyDictionary[dataRow.Table.TableName].Select(key => dataRow[key].ToString()).ToArray());

var a = dt.AsEnumerable().GroupBy(GetKeys);

这是最好的逻辑,你可以尝试一下,我们有很多关于这方面的研究,所以我写的答案是我的教授给出的逻辑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接