C#从数据表构造动态查询

时间:2019-07-02 19:33:02

标签: c# linq generics

尝试根据返回给我的DataTable生成动态Linq查询... DataTable中的列名将更改,但是我知道要累加哪些列,以及要对哪些列进行分组。

我可以使它与循环配合使用,并将输出写入变量,然后将零件重铸回数据表,但是我希望有一种更优雅的方法。

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

在这种情况下,列为姓,名,年龄。我想按“姓氏”,“姓氏”列总计年龄(在分组依据中都是)。因此,我的一个参数将指定分组依据= 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

2 个答案:

答案 0 :(得分:0)

如果您将简单的转换器用于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();
    }
}

答案 1 :(得分:0)

我想我只会使用字典:

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名称的总数。我之所以使用int []而不是int的原因是因为int是一个值类型,而int []是一个引用。因为在迭代表时,每一行都被分配了对int []的引用,所以其中某些具有相同的名+姓氏将以其int []引用最终指向内存中的同一对象,因此增加后一个也增加所有先前的值(所有“ John Smith”行的总计列都对相同的int []进行引用。如果我们将列设为int类型,则每一行都将指向不同的计数器,因为每次说ro[dc] = d[key],它将把d [key] int的当前值复制到ro [dc]的int中。任何引用类型都可以使此技巧起作用,但值类型则不起作用。要成为值类型,您将不得不再次迭代表,或者具有两个字典,一个字典映射DataRow-> total并迭代键,将总数分配回行中