简体   繁体   English

C#Linq GroupBy方法对于匿名和非匿名类型的工作方式不同

[英]C# Linq GroupBy method works different for anonymous vs non-anonymous types

I have a DataTable with 4 columns as follows: 我有一个包含4列的DataTable,如下所示:

    private static DataSet dataSet;
    private const string tableName = "MyTable";
    private const string columnName1 = "Supplier";  //Column names
    private const string columnName2 = "Invoice";
    private const string columnName3 = "Item";
    private const string columnName4 = "Amount";

I grouped the table by Supplier, Invoice columns and calculated the sum for the Amount using the following linq query: 我按照供应商,发票列对表进行分组,并使用以下linq查询计算金额的总和:

    private static DataTable GroupQueryA(DataTable dataTable)
    {
        DataTable groupedTable = dataTable.AsEnumerable()
            .GroupBy(r => new { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
            .Select(g => new GroupSum
            {
                Key1 = g.Key.Key1,
                Key2 = g.Key.Key2,
                Sum = g.Sum(x => x.Field<double>(columnName4))
            }).PropertiesToDataTable<GroupSum>();

        return groupedTable;
    }

The GroupSum type I declared as: GroupSum类型我声明为:

    private class GroupSum
    {
        public string Key1 { get; set; }
        public string Key2 { get; set; }
        public Double Sum { get; set; }
    }

The PropertiesToDataTable() method I copied from : 我复制的PropertiesToDataTable()方法:

Convert Datatable GroupBy Multiple Columns with Sum using Linq 使用Linq将Datatable GroupBy Multiple Columns转换为Sum

It works perfect so that for the table rows like: 它非常完美,因此对于表格行如下:

    AddRow(dataTable, "SA", "INVA", "ITA", 10);
    AddRow(dataTable, "SA", "INVA", "ITB", 20);
    AddRow(dataTable, "SB", "INVB", "ITC", 50);

I receive 2 rows in the result: 我在结果中收到2行:

    "SA", "INVA", 30
    "SB", "INVB", 50

However I decided to modify my query so that instead of using the anonymous type I coded the : 但是我决定修改我的查询,以便不使用我编码的匿名类型:

    public class GroupKeys
    {
        public string Key1 { get; set; }
        public string Key2 { get; set; }
    }

and the query. 和查询。

    private static DataTable GroupQueryB(DataTable dataTable)
    {
        DataTable groupedTable = dataTable.AsEnumerable()
            .GroupBy(r => new GroupKeys { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
            .Select(g => new GroupSum
            {
                Key1 = g.Key.Key1,
                Key2 = g.Key.Key2,
                Sum = g.Sum(x => x.Field<double>(columnName4))
            }).PropertiesToDataTable<GroupSum>();

        return groupedTable;
    }

And now for the same source data I receive different result: 现在对于相同的源数据,我收到不同的结果:

    "SA", "INVA", 10
    "SA", "INVA", 20
    "SB", "INVB", 50

It seems that the source data is not grouped at all athough the only difference in the query is one line: 似乎源数据根本没有分组,查询的唯一区别是一行:

    //QueryA
    .GroupBy(r => new { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
    //QueryB
    .GroupBy(r => new GroupKeys { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })

Could anybody explain that please ? 请问有人解释一下吗? For those who would like to run the test in the Visual Studio please find the complete source below. 对于那些想在Visual Studio中运行测试的人,请在下面找到完整的源代码。

internal static class TestForStackOverflow
{
    private static DataSet dataSet;
    private const string tableName = "MyTable";
    private const string columnName1 = "Supplier";  //Column names
    private const string columnName2 = "Invoice";
    private const string columnName3 = "Item";
    private const string columnName4 = "Amount";

    private class GroupKeys
    {
        public string Key1 { get; set; }
        public string Key2 { get; set; }
    }

    private class GroupSum
    {
        public string Key1 { get; set; }
        public string Key2 { get; set; }
        public Double Sum { get; set; }
    }

    public static void Test()
    {
        DataTable dataTable = InitializeDataTable();

        //DataTable groupedTable = GroupQueryA(dataTable);      //Please uncomment to run test A
        DataTable groupedTable = GroupQueryB(dataTable);        //Please uncomment to run test B

        DisplayData(groupedTable);
    }


    private static DataTable GroupQueryA(DataTable dataTable)
    {
        DataTable groupedTable = dataTable.AsEnumerable()
            .GroupBy(r => new { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
            .Select(g => new GroupSum
            {
                Key1 = g.Key.Key1,
                Key2 = g.Key.Key2,
                Sum = g.Sum(x => x.Field<double>(columnName4))
            }).PropertiesToDataTable<GroupSum>();

        return groupedTable;
    }

    private static DataTable GroupQueryB(DataTable dataTable)
    {
        DataTable groupedTable = dataTable.AsEnumerable()
            .GroupBy(r => new GroupKeys { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
            .Select(g => new GroupSum
            {
                Key1 = g.Key.Key1,
                Key2 = g.Key.Key2,
                Sum = g.Sum(x => x.Field<double>(columnName4))
            }).PropertiesToDataTable<GroupSum>();

        return groupedTable;
    }

    private static System.Data.DataTable PropertiesToDataTable<T>(this System.Collections.Generic.IEnumerable<T> source)
    {
        System.Data.DataTable dt = new System.Data.DataTable();

        //Weź listę właściwości typu <T> i dla każdej właściwości dodaj do tabeli kolumnę tego samego typu co właściwość
        var props = System.ComponentModel.TypeDescriptor.GetProperties(typeof(T));
        foreach (System.ComponentModel.PropertyDescriptor prop in props)
        {
            System.Data.DataColumn dc = dt.Columns.Add(prop.Name, prop.PropertyType);
            dc.Caption = prop.DisplayName;
            dc.ReadOnly = prop.IsReadOnly;
        }
        //Kopiuj rekordy z kwerendy do DataTable
        foreach (T item in source)
        {
            System.Data.DataRow dr = dt.NewRow();
            foreach (System.ComponentModel.PropertyDescriptor prop in props)
            {
                dr[prop.Name] = prop.GetValue(item);
            }
            dt.Rows.Add(dr);
        }
        return dt;
    }

    private static DataTable InitializeDataTable()
    {
        dataSet = new DataSet();
        DataTable dataTable = dataSet.Tables.Add(tableName);

        dataTable.Columns.Add( columnName1, typeof(string));
        dataTable.Columns.Add( columnName2, typeof(string));
        dataTable.Columns.Add( columnName3, typeof(string));
        dataTable.Columns.Add( columnName4, typeof(double));

        AddRow(dataTable, "SA", "INVA", "ITA", 10);
        AddRow(dataTable, "SA", "INVA", "ITB", 20);
        AddRow(dataTable, "SB", "INVB", "ITC", 50);
        return dataTable;
    }
    private static void AddRow( DataTable dataTable, string supplier, string invoice, string item, double amount)
    {
        DataRow row = dataTable.NewRow();
        row[columnName1] = supplier;
        row[columnName2] = invoice;
        row[columnName3] = item;
        row[columnName4] = amount;
        dataTable.Rows.Add(row);
    }
    private static void DisplayData(System.Data.DataTable table)
    {
        foreach (System.Data.DataRow row in table.Rows)
        {
            foreach (System.Data.DataColumn col in table.Columns)
            {
                Console.WriteLine("{0} = {1}", col.ColumnName, row[col]);
            }
            Console.WriteLine("============================");
        }
    }
}

You need to touch up your GroupKeys object a little bit more. 您需要稍微修改一下GroupKeys对象。 Since it's a reference type, and GroupBy is using the Default Equality Comparer , part of it's check is going to be testing for referential equality, which will always return false. 由于它是一个引用类型,并且GroupBy正在使用Default Equality Comparer ,因此它的部分检查将测试引用相等性,它将始终返回false。

You can adjust your GroupKeys class by overriding the Equals and GetHashCode methods to test for structural equality. 您可以通过重写EqualsGetHashCode方法来调整GroupKeys类,以测试结构相等性。 As an example, this is generated by ReSharper: 例如,这是由ReSharper生成的:

private class GroupKeys
{
    public string Key1 { get; set; }
    public string Key2 { get; set; }

    public override int GetHashCode()
    {
        unchecked
        {
            return ((Key1 != null ? Key1.GetHashCode() : 0) * 397) ^ (Key2 != null ? Key2.GetHashCode() : 0);
        }
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (obj.GetType() != this.GetType())
            return false;

        return Equals((GroupKeys)obj);
    }

    public bool Equals(GroupKeys other)
    {
        if (ReferenceEquals(null, other))
            return false;
        if (ReferenceEquals(this, other))
            return true;

        return string.Equals(Key1, other.Key1)
               && string.Equals(Key2, other.Key2);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM