Используйте Linq to SQL для создания отчета о продажах - PullRequest
2 голосов
/ 16 мая 2010

В настоящее время у меня есть следующий код для создания отчета о продажах за последние 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);
        }
    }

EDIT: Ответ Йоханнеса был отличным способом справиться с моим запросом. Ниже приведена корректировка кода, чтобы он работал в этом примере на случай, если у кого-то еще возникнет эта проблема. Это выполнит внешнее объединение из таблицы 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 ]

4 голосов
/ 16 мая 2010

Вы можете сгруппировать все свои данные по дням и просуммировать эти группы. Чтобы выполнить требование наличия суммы на каждый день, даже если у вас нет заказа, вы можете либо присоединиться к списку всех дат, либо просто использовать цикл, чтобы убедиться, что все даты включены. Небольшой совет: вам не нужно задавать время явно, если вы сравниваете по 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>");
            }


        }
    }
}
0 голосов
/ 16 мая 2010

Я думаю, что если ваше перечисление не содержит данных за день, вы не можете вернуть значения за этот день. Лучшее, что я могу придумать, - это создать список объектов 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/>");
...