【问题标题】:C# Constructing a Dynamic Query From DataTableC# 从 DataTable 构造动态查询
【发布时间】:2019-07-02 19:33:02
【问题描述】:

尝试根据返回给我的 DataTable 生成动态 Linq 查询... DataTable 中的列名会发生变化,但我会知道要汇总哪些列,以及要对哪些列进行分组。

我可以让它与循环一起工作并将输出写入变量,然后将部分重新转换回数据表,但我希望有一种更优雅的方式来做到这一点。

//C#
DataTable dt = new DataTable;
Dt.columns(DynamicData1)
Dt.columns(DynamicData1)
Dt.columns(DynamicCount)

在这种情况下,这些列是 LastName、FirstName、Age。我想按 LastName,FirstName 列计算年龄(都是在 group by 中)。因此,我的一个参数将指定 group by = LastName、FirstName 和另一个 TotalBy = Age。下一个查询可能会返回不同的列名。

 Datarow dr =.. 
    dr[0] = {"Smith","John",10}
    dr[1] = {"Smith","John",11}
    dr[2] = {"Smith","Sarah",8}

鉴于这些不同的潜在列名称...我希望生成一个 linq 查询,该查询创建一个通用的 group by 和 Total 输出。

结果: 姓氏、名字、年龄总计 史密斯,约翰 = 21 史密斯,莎拉 = 8

【问题讨论】:

  • I'm hoping there is a more elegant way of doing this 看不到代码,很难说
  • 只需将您的 DataTable 转换为 linq 对象。这很简单。然后 GroupBySum 可以实时构建并随心所欲地运行

标签: c# linq generics


【解决方案1】:

如果您使用Linq 的简单转换器,您可以轻松实现。

这是我为示例所做的快速数据生成:

// create dummy table
var dt = new DataTable();
dt.Columns.Add("LastName", typeof(string));
dt.Columns.Add("FirstName", typeof(string));
dt.Columns.Add("Age", typeof(int));

// action to create easily the records
var addData = new Action<string, string, int>((ln, fn, age) =>
    {
        var dr = dt.NewRow();
        dr["LastName"] = ln;
        dr["FirstName"] = fn;
        dr["Age"] = age;
        dt.Rows.Add(dr);
    });

// add 3 datarows records
addData("Smith", "John", 10);
addData("Smith", "John", 11);
addData("Smith", "Sarah", 8);

这是如何使用我的简单转换类:

// create a linq version of the table
var lqTable = new LinqTable(dt);

// make the group by query
var groupByNames = lqTable.Rows.GroupBy(row => row["LastName"].ToString() + "-" + row["FirstName"].ToString()).ToList();

// for each group create a brand new linqRow
var linqRows = groupByNames.Select(grp =>
{
    //get all items. so we can use first item for last and first name and sum the age easily at the same time
    var items = grp.ToList();

    // return a new linq row
    return new LinqRow()
    {
        Fields = new List<LinqField>()
            {
                new LinqField("LastName",items[0]["LastName"].ToString()),
                new LinqField("FirstName",items[0]["FirstName"].ToString()),
                new LinqField("Age",items.Sum(item => Convert.ToInt32(item["Age"]))),
            }
    };
}).ToList();

// create new linq Table since it handle the datatable format ad transform it directly
var finalTable = new LinqTable() { Rows = linqRows }.AsDataTable();

最后是使用的自定义类

公共类 LinqTable {

    public LinqTable()
    {

    }

    public LinqTable(DataTable sourceTable)
    {
        LoadFromTable(sourceTable);
    }

    public List<LinqRow> Rows = new List<LinqRow>();

    public List<string> Columns
    {
        get
        {
            var columns = new List<string>();

            if (Rows != null && Rows.Count > 0)
            {
                Rows[0].Fields.ForEach(field => columns.Add(field.Name));
            }

            return columns;
        }
    }

    public void LoadFromTable(DataTable sourceTable)
    {
        sourceTable.Rows.Cast<DataRow>().ToList().ForEach(row => Rows.Add(new LinqRow(row)));
    }

    public DataTable AsDataTable()
    {
        var dt = new DataTable("data");

        if (Rows != null && Rows.Count > 0)
        {
            Rows[0].Fields.ForEach(field =>
            {
                dt.Columns.Add(field.Name, field.DataType);
            });

            Rows.ForEach(row =>
            {
                var dr = dt.NewRow();

                row.Fields.ForEach(field => dr[field.Name] = field.Value);

                dt.Rows.Add(dr);
            });
        }

        return dt;
    }
}

public class LinqRow
{
    public List<LinqField> Fields = new List<LinqField>();

    public LinqRow()
    {

    }

    public LinqRow(DataRow sourceRow)
    {
        sourceRow.Table.Columns.Cast<DataColumn>().ToList().ForEach(col => Fields.Add(new LinqField(col.ColumnName, sourceRow[col], col.DataType)));
    }

    public object this[int index]
    {
        get
        {
            return Fields[index].Value;
        }
        set
        {
            Fields[index].Value = value;
        }
    }
    public object this[string name]
    {
        get
        {
            return Fields.Find(f => f.Name == name).Value;
        }
        set
        {
            var fieldIndex = Fields.FindIndex(f => f.Name == name);

            if (fieldIndex >= 0)
            {
                Fields[fieldIndex].Value = value;
            }
        }
    }

    public DataTable AsSingleRowDataTable()
    {
        var dt = new DataTable("data");

        if (Fields != null && Fields.Count > 0)
        {
            Fields.ForEach(field =>
            {
                dt.Columns.Add(field.Name, field.DataType);
            });

            var dr = dt.NewRow();

            Fields.ForEach(field => dr[field.Name] = field.Value);

            dt.Rows.Add(dr);
        }

        return dt;
    }
}

public class LinqField
{
    public Type DataType;
    public object Value;
    public string Name;

    public LinqField(string name, object value, Type dataType)
    {
        DataType = dataType;
        Value = value;
        Name = name;
    }

    public LinqField(string name, object value)
    {
        DataType = value.GetType();
        Value = value;
        Name = name;
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

【讨论】:

    【解决方案2】:

    我想我会用字典:

    public Dictionary<string, int> GroupTot(DataTable dt, string[] groupBy, string tot){
    
      var d = new Dictionary<string, int>();
      foreach(DataRow ro in dt.Rows){
        string key = "";
        foreach(string col in groupBy)
          key += (string)ro[col] + '\n';
        if(!d.ContainsKey(key))
          d[key] = 0;
        d[key]+= (int)ro[tot];
      }
      return d;
    }
    

    如果您想要每一行的总数,我们可以变得可爱并创建一个由一个 int 组成的数组而不是一个 int 的列:

    public void GroupTot(DataTable dt, string[] groupBy, string tot){
    
      var d = new Dictionary<string, int>();
      var dc = dt.Columns.Add("Total_" + tot, typeof(int[]));
    
      foreach(DataRow ro in dt.Rows){
        string key = "";
        foreach(string col in groupBy)
          key += (string)ro[col] + '\n'; //build a grouping key from first and last name
        if(!d.ContainsKey(key)) //have we seen this name pair before?
          d[key] = new int[1]; //no we haven't, ensure we have a tracker for our total, for this first+last name
        d[key][0] += (int)ro[tot]; //add the total
        ro[dc] = d[key]; //link the row to the total tracker
      }
    }
    

    在操作结束时,每一行的“Total_age”列中都会有一个 int 数组,表示该 First+Last name 的总数。我使用 int[] 而不是 int 的原因是因为 int 是一个值类型,而 int[] 是一个引用。因为随着表的迭代,每一行都被分配了一个对 int[] 的引用,其中一些具有相同的 First+Last name 的结果将是它们的 int[] 引用指向内存中的同一个对象,所以增加一个后面的对象也增加所有早期的(所有“John Smith”行总列都包含对相同 int[] 的引用。如果我们将该列设为 int 类型,那么每一行都会指向不同的计数器,因为每次我们说ro[dc] = d[key] 它会将 d[key] int 的当前值复制到 ro[dc] 的 int 中。任何引用类型都可以让这个技巧起作用,但值类型不会。如果你希望你的列是值类型,您必须再次迭代表,或者有两个字典,一个映射 DataRow -> total 并迭代键,将总计分配回行

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-29
      • 1970-01-01
      • 2020-08-07
      相关资源
      最近更新 更多