Вопрос по linq, c#, sql – Linq - рассчитать несколько средних в одном запросе

5

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

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

<code>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
</code>

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

<code>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; }
}
</code>

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

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

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

Ваш Ответ

3   ответа
1

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

К сожалению, только что увидел ваши изменения. Похоже, это будет самый простой способ. Я увидел средние методы в Linq и подумал, что они могут стать способом продвижения вперед. Но, возможно, нет. Graham Clark
Хмммм ... Вы могли бы заменить .avarege на .min или .max и работать оттуда?
Ну, оригинальный SQL содержит 9 различных операций над данными, вычисляя различные средние, минимальные, максимальные значения и т. Д. Я просто выбрал два самых сложных из них для своего примера и подумал, что смогу отработать все остальное. Извините за путаницу! Graham Clark
Извините, что немного запутался, вы имеете в виду, что для объекта типа A вы хотите вычислить не только среднее, но и min / max..etc?
Спасибо, это действительно сделает работу. Тем не менее, поскольку я получил 9 единиц среднего / минимального / максимального типа и т. Д. Для расчета по этим данным, мне стало интересно, можно ли все это сделать в одном запросе Linq, аналогично исходному SQL. Graham Clark
4

implicit grouping - возьми все это и составь одну группу. В 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 )
  };
Это тоже здорово, и я мог бы в конечном итоге использовать эту реализацию в конце. Если бы только у меня было два ответа! Graham Clark
5

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

Спасибо, это отлично работает! Я ушел с отдельным параметром агрегирования - это немного проясняет ситуацию. Graham Clark

Похожие вопросы