Linq - вычислить несколько средних значений в одном запросе

Я пытаюсь преобразовать некоторые 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

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

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 в списке имеет подсписок с нагрузкой B. Я хочу использовать Linq-to-objects для запроса этого списка.

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

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


person Graham Clark    schedule 29.06.2009    source источник


Ответы (3)


Более интересным вопросом было бы то, как это сделать на сервере, например, чтобы следующий запрос транслировался в 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 или любую другую подходящую шкалу.

person Pete Montgomery    schedule 29.06.2009
comment
Спасибо, это делает работу отлично! Я выбрал вариант отдельной агрегации — это немного проясняет ситуацию. - person Graham Clark; 29.06.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 )
  };
person Amy B    schedule 29.06.2009
comment
Это тоже здорово, и в конце концов я могу использовать эту реализацию. Если бы у меня было два ответа! - person Graham Clark; 29.06.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);

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

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, но это скорее синтаксический сахар (мало знаю о реализации, поэтому я надеюсь, что вы не держите это против меня). А также вы можете обернуть реализации, которые я предоставляю, в методы, чтобы вы могли вызывать их несколько раз для объектов List of A.

person Draco    schedule 29.06.2009
comment
Спасибо, это действительно поможет. Однако, поскольку у меня есть 9 значений среднего/минимального/максимального и т. д. для расчета из этих данных, мне было интересно, можно ли все это сделать в одном запросе Linq, подобно исходному SQL. - person Graham Clark; 29.06.2009
comment
ой, только что увидел вашу правку. Похоже, это будет самый простой способ. Я видел методы Average в Linq и подумал, что они могут стать шагом вперед. А может и нет. - person Graham Clark; 29.06.2009
comment
Извините, что немного запутался, вы имеете в виду, что для объекта типа A вы хотите рассчитать не только среднее значение, но и мин./макс..и т.д.? - person Draco; 29.06.2009
comment
Ну, исходный SQL содержит 9 различных операций над данными, вычисляя различные средние значения, минимумы, максимумы и т. д. Я просто выбрал две самые сложные для своего примера и подумал, что смогу обработать остальные оттуда. Извините за путаницу! - person Graham Clark; 29.06.2009
comment
Хмммм... Вы могли бы заменить .avarege на .min или .max и работать оттуда? - person Draco; 29.06.2009