Dynamic LINQ aggregates on IQueryable as a single query

后端 未结 3 727
一个人的身影
一个人的身影 2021-01-06 20:06

I\'m building a datagrid library that works on generic IQueryable data sources. At the bottom selected columns will have aggregates: sum, average, count etc.

I can c

3条回答
  •  迷失自我
    2021-01-06 20:48

    I found an alternative approach which uses the Dynamic LINQ library and avoids having to construct convoluted expression trees.

    The solution is in the unit test below for anyone who is interested. I have a random dataset called TestQueryableDataset. The generic type of this IQueryable datasource has a Total property (decimal), a Discount property (nullable decimal) and an ID property (int).

    The unit test gets the expected results first, using static LINQ queries.

    It then constructs a select statement that uses the groupby variable 'it' to compute the sum, average and count. The property names are passed in by string to demonstrate this is stringly-typed.

    The group-by method .GroupBy(x=> 1) is a dummy grouping to enable the aggregates to apply to the whole dataset.

    Note that this returns a single dynamic result with properties t0, t1 and t2. However, the groupby/select operation still returns an IQueryable but with a single result. We have to use the t.Cast().First(); to convert to an array of object, then get the first result.

    We can then use reflection to get the properties of each result (t0, t1, t2) as the actual values and assert that they match the static result we got earlier.

        [TestMethod()]
        [TestProperty("Anvil.DataSets", "QueryableExtensions")]
        public void DynamicAggregate_test()
        {
            var source = new Anvil.Test.DataSets.TestQueryableDataset();
    
            var data = source.GetData();
    
            var expectedTotal = (from d in data select d.Total).Sum();
            var expectedDiscount = (from d in data select d.Discount).Average();
            var expectedCount = (from d in data select d.ID).Count();
    
            const string prop0 = "Total";
            const string prop1 = "Discount";
            const string prop2 = "ID";
    
            string sumExpr = string.Format("new ( Sum(it.{0}) as t0, Average(it.{1}) as t1 , Count() as t2)", prop0,prop1, prop2);
            var t = data.GroupBy(x => 1).Select(sumExpr);
    
            var firstItem = t.Cast().First();
    
            var ttype = firstItem.GetType();
            var p0 = ttype.GetProperty("t0");
            var p1 = ttype.GetProperty("t1");
            var p2 = ttype.GetProperty("t2");
    
            decimal actualTotal = (decimal)(p0.GetValue(firstItem));
            decimal actualDiscount = (decimal)(p1.GetValue(firstItem));
            int actualCount = (int)(p2.GetValue(firstItem));
    
            Assert.AreEqual(expectedTotal, actualTotal);
            Assert.AreEqual(expectedDiscount, actualDiscount);
            Assert.AreEqual(expectedCount, actualCount);
        }
    
    
    

    See also:

    • Dynamic Linq GroupBy
    • System.LINQ.Dynamic: Select(" new (...)") into a List (or any other enumerable collection of )
    • http://developergems.blogspot.co.uk/2012/02/group-by-with-dynamic-linq.html

    提交回复
    热议问题