Linq - рассчитать несколько средних в одном запросе - PullRequest
5 голосов
/ 29 июня 2009

Я пытаюсь преобразовать некоторые SQL-запросы в Linq, чтобы избежать многократных обращений к базе данных.

Старый SQL, который я пытаюсь преобразовать, делает:

SELECT
    AVG(CAST(DATEDIFF(ms, A.CreatedDate, B.CompletedDate) AS decimal(15,4))),
    AVG(CAST(DATEDIFF(ms, B.CreatedDate, B.CompletedDate) AS decimal(15,4)))
FROM
    dbo.A
INNER JOIN
    dbo.B ON B.ParentId = A.Id

Итак, я создал два класса C #:

class B
{
    public Guid Id { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime CompletedDate { get; set; }
}

class A
{
    public Guid Id { get; set; }
    public DateTime CreatedDate { get; set; }
    public List<B> BList { get; set; }
}

И у меня есть List<A> объект, который я хочу запросить. Он заполняется из базы данных, поэтому каждый A в списке имеет подсписок с загрузкой Bs. Я хочу использовать Linq-to-objects для запроса этого списка.

Так что мне нужно использовать Linq, чтобы получить среднее время между началом A и завершением его дочерних B, а также среднее время между началом и завершением каждого B. Я не писал оригинальный SQL, поэтому я не совсем уверен, что он делает то, что должен!

У меня есть несколько из этих средних для вычисления, поэтому я бы хотел сделать их все в одном магическом запросе Linq. Это возможно?

Ответы [ 3 ]

5 голосов
/ 29 июня 2009

Более интересным вопросом будет то, как сделать это на сервере, например, чтобы следующий запрос преобразовался в LINQ to SQL.

        var q = from single in Enumerable.Range(1, 1)
                let xs = sourceSequence
                select new
                {
                    Aggregate1 = xs.Sum(),
                    Aggregate2 = xs.Average(),
                    // etc
                };

Но нет смысла пытаться втиснуть его в один запрос, если вы используете LINQ to Objects. Просто напишите агрегаты отдельно:

var aggregate1 = xs.Sum();
var aggregate2 = xs.Average();
// etc

Другими словами:

var xs = from a in listOfAs
         from b in a.Bs
         select new { A=a, B=b };

var average1 = xs.Average(x => (x.B.Completed - x.A.Created).TotalSeconds);
var average2 = xs.Average(x => (x.B.Completed - x.B.Created).TotalSeconds);

Правильно ли я понял?

Редактировать: Дэвид Б ответил так же. Я забыл преобразовать TimeSpan в простой числовой тип, поддерживаемый методом Average. Используйте TotalMilliseconds / Seconds / Minutes или любой другой подходящий масштаб.

4 голосов
/ 29 июня 2009

Sql и Linq различаются по определенным пунктам. Sql имеет неявную группировку - возьмите все это и создайте одну группу. В linq можно подделать неявную группировку, введя константу для группировки по.

var query = 
  from i in Enumerable.Repeat(0, 1)
  from a in AList
  from b in a.BList
  select new {i, a, b} into x
  group x by x.i into g
  select new
  {
    Avg1 = g.Average(x => (x.b.CompletedDate - x.a.CreatedDate)
       .TotalMilliseconds ),
    Avg2 = g.Average(x => (x.b.CompletedDate - x.b.CreatedDate)
       .TotalMilliseconds )
  };
1 голос
/ 29 июня 2009

Давайте начнем с получения среднего времени между началом и концом B:

1) Сначала вам нужна разница во времени между началом и концом каждого объекта B, поэтому вы должны сделать это:

List<TimeSpan> timeSpans = new List<TimeSpan>();
foreach (B myB in BList)
{
  TimeSpan myTimeSpan = myB.CompletedDate.Subtract(myB.CreatedDate);
  timeSpans.Add(myTimeSpan);
}

2) Теперь вам нужно получить среднее значение этого. Вы можете сделать это, используя лямбда-выражения:

//This will give you the average time it took to complete a task in minutes
Double averageTimeOfB = timeSpans.average(timeSpan => timeSpan.TotalMinutes);

Теперь вы можете сделать то же самое, чтобы получить среднее время между стартом А и финишем Б следующим образом:

1) Получите разницу во времени для даты начала A и каждой даты завершения B:

 List<TimeSpan> timeSpans_2 = new List<TimeSpan>();
 foreach (B myB in BList)
 {
    TimeSpan myTimeSpan = myB.CompletedDate.Subtract(objectA.CreatedDate);
    timeSpans_2.Add(myTimeSpan);
 }

2) Получите среднее значение этих разностей времени точно так же, как указано выше:

//This will give you the average time it took to complete a task in minutes
Double averageTimeOfAandB = timeSpans_2.average(timeSpan => timeSpan.TotalMinutes);

И все готово. Я надеюсь, что это помогает. Обратите внимание, что этот код не был проверен.

РЕДАКТИРОВАТЬ: Помните, что LINQ использует выражения Lamda, но это скорее синтаксический сахар (не знаю много о реализации, поэтому я надеюсь, что вы не против меня) А также вы можете обернуть реализации, которые я предоставляю в методы, чтобы вы могли вызывать их несколько раз для Списка объектов A.

...