Search code examples
c#linqlinq-to-dataset

Combining multiple linq queries and concatenated results into a smaller query


Can I restructure the following into a more compact linq query, ideally without the introduction of a helper function?

var revPerUnitChanges = 
    from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
    let field = "Rev/Unit"
    select new {
        Field = field,
        From = row.Field<decimal>(field, DataRowVersion.Original),
        To = row.Field<decimal>(field, DataRowVersion.Current),
        ItemIds = row.Field<string>("ItemIds"),};
var costPerUnitChanges = 
    from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
    let field = "Cost/Unit"
    select new {
        Field = field,
        From = row.Field<decimal>(field, DataRowVersion.Original),
        To = row.Field<decimal>(field, DataRowVersion.Current),
        ItemIds = row.Field<string>("ItemIds"), };
var numUnitsChanges = 
    from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
    let field = "Units"
    select new {
        Field = field,
        From = row.Field<decimal>(field, DataRowVersion.Original),
        To = row.Field<decimal>(field, DataRowVersion.Current),
        ItemIds = row.Field<string>("ItemIds"), };
var changes = 
    revPerUnitChanges
        .Concat(costPerUnitChanges
        .Concat(numUnitsChanges))
        .Where(c => c.From != c.To);

Solution

  • Start out by creating a helper class to hold onto the data. (Your code doesn't have any problems using anonymous types, but if you want to refactor sections into methods it'll be much easier with a named class.)

    public class MyClass //TODO give better name
    {
        public MyClass(DataRow row, string field) //You could have a public static generate method if it doesn't make sense for this to be a constructor.
        {
            Field = field;
            From = row.Field<decimal>(field, DataRowVersion.Original);
            To = row.Field<decimal>(field, DataRowVersion.Current);
            ItemIds = row.Field<string>("ItemIds");
        }
        public string Field { get; set; }
        public decimal From { get; set; }
        public decimal To { get; set; }
        public string ItemIds { get; set; }
    }
    

    Now that we have that out of the way the query is fairly straightforward.

    var changes = dataTable.GetChanges(DataRowState.Modified)
        .AsEnumerable()
        .Select(row => new[]{ //create 3 new items for each row
                        new MyClass(row, "Rev/Unit"),
                        new MyClass(row, "Cost/Unit"),
                        new MyClass(row, "Units"),
                    })
        .SelectMany(item => item) //flatten the inner array
        .Where(item => item.From != item.To);