使用Linq to SQL生成销售报告

时间:2010-05-16 18:03:03

标签: c# linq-to-sql grouping sum

我目前有以下代码来生成过去30天的销售报告。我想知道是否可以使用linq一步生成此报告,而不是我在这里的相当基本的循环。

根据我的要求,每天都需要向我返还一个值,所以如果任何一天没有销售,则返回0。

那里的任何Sum linq示例都没有解释如何包含where过滤器,所以我很困惑如何获得每天的总金额,或者如果没有销售则为0,最后几天我通过了。

感谢您的帮助, 富

    //setup date ranges to use
    DateTime startDate = DateTime.Now.AddDays(-29);
    DateTime endDate = DateTime.Now.AddDays(1);
    TimeSpan startTS = new TimeSpan(0, 0, 0);
    TimeSpan endTS = new TimeSpan(23, 59, 59);

    using (var dc = new DataContext())
    {
        //get database sales from 29 days ago at midnight to the end of today
        var salesForDay = dc.Orders.Where(b => b.OrderDateTime > Convert.ToDateTime(startDate.Date + startTS) && b.OrderDateTime <= Convert.ToDateTime(endDate.Date + endTS));

        //loop through each day and sum up the total orders, if none then set to 0
        while (startDate != endDate)
        {
            decimal totalSales = 0m;
            DateTime startDay = startDate.Date + startTS;
            DateTime endDay = startDate.Date + endTS;
            foreach (var sale in salesForDay.Where(b => b.OrderDateTime > startDay && b.OrderDateTime <= endDay))
            {
                totalSales += (decimal)sale.OrderPrice;
            }

            Response.Write("From Date: " + startDay + " - To Date: " + endDay + ". Sales: " + String.Format("{0:0.00}", totalSales) + "<br>");

            //move to next day
            startDate = startDate.AddDays(1);
        }
    }

编辑: 约翰内斯的答案是处理我的查询的好方法。下面是对代码的调整,以便在其他人遇到此问题时使其适用于此示例。这将从allDays表执行外部联接,并在当天没有销售时返回0值。

var query = from d in allDays
                    join s in salesByDay on d equals s.Day into j
                    from s in j.DefaultIfEmpty()
                    select new { Day = d, totalSales = (s != null) ? s.totalSales : 0m };

2 个答案:

答案 0 :(得分:4)

您可以按日对所有数据进行分组,并对这些组进行总结。为了满足每天总和的要求,即使是那些没有订单的要求,您也可以加入所有日期的列表,或者只是使用循环来确保包含所有日期。小提示:如果您按DateTime.Date属性进行比较,则无需明确设置时间。

以下是使用生成器函数的解决方案(取自MoreLinq项目):

public static partial class MoreEnumerable
{

    public static IEnumerable<TResult> GenerateByIndex<TResult>(Func<int, TResult> generator)
    {
        // Looping over 0...int.MaxValue inclusive is a pain. Simplest is to go exclusive,
        // then go again for int.MaxValue.
        for (int i = 0; i < int.MaxValue; i++)
        {
            yield return generator(i);
        }
        yield return generator(int.MaxValue);
    }

}

public class MyClass
{
    private void test()
    {
        DateTime startDate = DateTime.Now.AddDays(-29);
        DateTime endDate = DateTime.Now.AddDays(1);

        using (var dc = new DataContext())
        {
            //get database sales from 29 days ago at midnight to the end of today
            var salesForPeriod = dc.Orders.Where(b => b.OrderDateTime > startDate.Date  && b.OrderDateTime <= endDate.Date);

            var allDays = MoreEnumerable.GenerateByIndex(i => startDate.AddDays(i)).Take(30);

            var salesByDay = from s in salesForPeriod
                        group s by s.OrderDateTime.Date into g
                        select new {Day = g.Key, totalSales = g.Sum(x=>(decimal)x.OrderPrice};

            var query = from d in allDays
                        join s in salesByDay on s.Day equals d
                        select new {Day = s.Day , totalSales = (s != null) ? s.totalSales : 0m;


            foreach (var item in query)
            {
                Response.Write("Date: " +item.Day.ToString() " Sales: " + String.Format("{0:0.00}", item.totalSales) + "<br>");
            }


        }
    }
}

答案 1 :(得分:0)

我认为如果您的枚举不包含一天的数据,则无法返回当天的值。我能想到的最好的方法是创建一个Order对象列表,每天的值为零,并创建一个带有查询结果的联合。这就是我想出的。但是,我认为循环遍历每个组,检查是否“跳过”任何一天,并且为每天“跳过”返回零比在内存中创建自己的枚举更简单(除非您希望枚举“缺少缺口” “ 填写)。请注意,我基本上假设对于每个组,您想要将一天中的所有值相加。

List<Order> zeroList = new List<Order>();
while (startDate <= endDate)
{
  zeroList.Add(new Order { OrderDateTime = startDate, OrderPrice = 0 });
  startDate = startDate.AddDays(1)
}

var comboList = zeroList.Union(dc.Orders.Where(b => b.OrderDateTime > Convert.ToDateTime(startDate.Date + startTS) && b.OrderDateTime <= Convert.ToDateTime(endDate.Date + endTS))

var groupedTotalSales = comboList.GroupBy(b => b.OrderDateTime.Date)
  .Select(b => new { StartDate = Convert.ToDateTime(b.Key + startTS), EndDate = Convert.ToDateTime(b.Key + endTS), Sum = b.Sum(x => x.OrderPrice });

foreach (totalSale in groupedTotalSales)
  Response.Write("From Date: " + totalSale.StartDate + " - To Date: " + totalSale.EndDate + ". Sales: " + String.Format("{0:0.00}", (decimal)totalSale.Sum) + "<br/>");