GroupBy с использованием даты в EF Core

Я работаю над приложением, в котором я получаю набор результатов JSON от стороннего API. Я десериализовал его в свою пользовательскую модель представления.

Существует список записей, который содержит записи с повторяющимися записями. Я хочу получить список со средним пользователем на эту дату.

Вот мой код и JSON. Я хочу использовать столбец даты dt_txt в одной записи и получить среднюю температуру, скорость ветра и влажность. Он считает 2 из приведенного ниже JSON, чтобы получить среднее значение для вышеупомянутых полей упоминаний.

public async Task<WeatherViewModel?> GetForecast(string param)
{
    var response = JsonConvert.DeserializeObject<WeatherReponseModel>(await httpClientApiService.GetRequestAsync(param, string.Format(WeatherEndPointsUrls.forecast, param)));

    if (response?.list == null)
        return null;

    // Here I want to rearrange the records. 
    // var orderList = response.list.GroupBy(x => Convert.ToDateTime(x.dt_txt)).ToList();
    
    return new WeatherViewModel();
}

Результирующий JSON:

"message":0,
   "cnt":40,
   "list":[
      {
         "dt":1668632400,
         "main":{
            "temp":5.98,
            "feels_like":2.53,
            "temp_min":5.23,
            "temp_max":5.98,
            "pressure":1000,
            "sea_level":1000,
            "grnd_level":1002,
            "humidity":81,
            "temp_kf":0.75
         },
         "weather":[
            {
               "id":803,
               "main":"Clouds",
               "description":"broken clouds",
               "icon":"04n"
            }
         ],
         "clouds":{
            "all":83
         },
         "wind":{
            "speed":5.06,
            "deg":91,
            "gust":10.53
         },
         "visibility":10000,
         "pop":0.29,
         "sys":{
            "pod":"n"
         },
         "dt_txt":"2022-11-16 21:00:00"
      },
      {
         "dt":1668643200,
         "main":{
            "temp":4.69,
            "feels_like":0.63,
            "temp_min":3.86,
            "temp_max":4.69,
            "pressure":1003,
            "sea_level":1003,
            "grnd_level":1002,
            "humidity":78,
            "temp_kf":0.83
         },
         "weather":[
            {
               "id":803,
               "main":"Clouds",
               "description":"broken clouds",
               "icon":"04n"
            }
         ],
         "clouds":{
            "all":84
         },
         "wind":{
            "speed":5.69,
            "deg":94,
            "gust":12.3
         },
         "visibility":10000,
         "pop":0.27,
         "sys":{
            "pod":"n"
         },
         "dt_txt":"2022-11-17 00:00:00"
      },
      {
         "dt":1668654000,
         "main":{
            "temp":3.12,
            "feels_like":-1.59,
            "temp_min":3.12,
            "temp_max":3.12,
            "pressure":1006,
            "sea_level":1006,
            "grnd_level":1001,
            "humidity":73,
            "temp_kf":0
         },
         "weather":[
            {
               "id":804,
               "main":"Clouds",
               "description":"overcast clouds",
               "icon":"04n"
            }
         ],
         "clouds":{
            "all":97
         },
         "wind":{
            "speed":6.22,
            "deg":93,
            "gust":12.73
         },
         "visibility":10000,
         "pop":0,
         "sys":{
            "pod":"n"
         },
         "dt_txt":"2022-11-17 03:00:00"
      },
      {
         "dt":1668664800,
         "main":{
            "temp":3.11,
            "feels_like":-1.81,
            "temp_min":3.11,
            "temp_max":3.11,
            "pressure":1006,
            "sea_level":1006,
            "grnd_level":1000,
            "humidity":74,
            "temp_kf":0
         },
         "weather":[
            {
               "id":804,
               "main":"Clouds",
               "description":"overcast clouds",
               "icon":"04n"
            }
         ],
         "clouds":{
            "all":89
         },
         "wind":{
            "speed":6.72,
            "deg":100,
            "gust":12.45
         },
         "visibility":10000,
         "pop":0,
         "sys":{
            "pod":"n"
         },
         "dt_txt":"2022-11-17 06:00:00"
      },
      {
         "dt":1668675600,
         "main":{
            "temp":5.35,
            "feels_like":0.76,
            "temp_min":5.35,
            "temp_max":5.35,
            "pressure":1006,
            "sea_level":1006,
            "grnd_level":1000,
            "humidity":59,
            "temp_kf":0
         },
         "weather":[
            {
               "id":804,
               "main":"Clouds",
               "description":"overcast clouds",
               "icon":"04d"
            }
         ],
         "clouds":{
            "all":100
         },
         "wind":{
            "speed":7.59,
            "deg":111,
            "gust":12.21
         },
         "visibility":10000,
         "pop":0,
         "sys":{
            "pod":"d"
         },
         "dt_txt":"2022-11-17 09:00:00"
      },
      {
         "dt":1668686400,
         "main":{
            "temp":5.65,
            "feels_like":1.15,
            "temp_min":5.65,
            "temp_max":5.65,
            "pressure":1004,
            "sea_level":1004,
            "grnd_level":999,
            "humidity":66,
            "temp_kf":0
         },
         "weather":[
            {
               "id":804,
               "main":"Clouds",
               "description":"overcast clouds",
               "icon":"04d"
            }
         ],
         "clouds":{
            "all":100
         },
         "wind":{
            "speed":7.6,
            "deg":113,
            "gust":13.15
         },
         "visibility":10000,
         "pop":0,
         "sys":{
            "pod":"d"
         },
         "dt_txt":"2022-11-17 12:00:00"
      }
   ],
   "city":{
      "id":2950159,
      "name":"Berlin",
      "coord":{
         "lat":52.5244,
         "lon":13.4105
      },
      "country":"DE",
      "population":1000000,
      "timezone":3600,
      "sunrise":1668580182,
      "sunset":1668611564
   }
}

Классы моделей:

public class WeatherReponseModel
{
        public string? cod { get; set; }
        public dynamic? message { get; set; }
        public int cnt { get; set; }
        public List<ListViewModel>? list { get; set; }
        public CityViewModel? city { get; set; }
}

public class Main
{
        public double temp { get; set; }
        public double feels_like { get; set; }
        public double temp_min { get; set; }
        public double temp_max { get; set; }
        public int pressure { get; set; }
        public int sea_level { get; set; }
        public int grnd_level { get; set; }
        public int humidity { get; set; }
        public double temp_kf { get; set; }
}

public class CityViewModel
{
        public int id { get; set; }
        public string? name { get; set; }
        public Coord? coord { get; set; }
        public string? country { get; set; }
        public int population { get; set; }
        public int timezone { get; set; }
        public int sunrise { get; set; }
        public int sunset { get; set; }
}

public class Clouds
{
        public int all { get; set; }
}

public class Coord
{
        public double lat { get; set; }
        public double lon { get; set; }
}

public class ListViewModel
{
        public ListViewModel()
        {
            this.weather = new List<Weather>();
        }

        public int dt { get; set; }
        public Main main { get; set; }
        public List<Weather> weather { get; set; }
        public Clouds clouds { get; set; }
        public Wind wind { get; set; }
        public int visibility { get; set; }
        public double pop { get; set; }
        public Sys sys { get; set; }
        public DateTime dt_txt { get; set; }
        public Rain rain { get; set; }
        public Snow snow { get; set; }
}

public class Rain
{
        [JsonProperty("3h")]
        public double _3h { get; set; }
}

public class Root
{
}

public class Snow
{
        [JsonProperty("3h")]
        public double _3h { get; set; }
}

public class Sys
{
        public string? pod { get; set; }
}

public class Weather
{
        public int id { get; set; }
        public string? main { get; set; }
        public string? description { get; set; }
        public string? icon { get; set; }
}

public class Wind
{
        public double speed { get; set; }
        public int deg { get; set; }
        public double gust { get; set; }
}

Можете ли вы добавить определение вашего класса WeatherReponseModel?

Philip P. 16.11.2022 19:46

Что-то не так с кодом, который у вас есть? Я имею в виду, помимо того, что свойство Convert.ToDateTime не нужно, потому что свойство dt_txt уже является свойством DateTime. Также возникает вопрос, что означает «средний пользователь» в наборе данных без пользователей...

Heretic Monkey 16.11.2022 19:59
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот довольно простой ответ, и я объясню его ниже:

var result = response.list
    .GroupBy(listViewModelElement => DateOnly.FromDateTime(new DateTime(listViewModelElement.dt_txt)))
    .Select(dateGroup => new {
            dt = dateGroup.Key,
            TemperatureAverage = dateGroup.Average(x => x.main.temp),
            // not sure how "Wind" class looks, so replace "SPEED" with whatever the correct prop name is
            WindSpeedAverage = dateGroup.Average(x => x.wind.SPEED),
            HumidityAverage = dateGroup.Average(x => x.main.humidity),
    });

Примечание: не уверен насчет синтаксиса new DateTime(listViewModelElement.dt_txt), возможно, вам нужно помассировать строку, чтобы она работала.

Примечание 2. Помните о возможных проблемах с производительностью при использовании этого упрощенного подхода. Если ваш ввод (количество элементов в response.list) очень велик, это не самое оптимальное решение. Если производительность становится реальной проблемой, необходимо добавить больше ручных операций вместо того, чтобы полагаться на LINQ.

Так что же делает этот код?

Во-первых, как говорится в вопросе, мы группируем содержимое respose.list по DateTime, построенному из значения свойства dt_txt, с полностью удаленным временем. Это .GroupBy(listViewModelElement => DateOnly.FromDateTime(new DateTime(listViewModelElement.dt_txt))) часть. DateOnly.FromDateTime выполняет часть «удалить бит времени».

Это GroupBy создает своего рода «группу». Думайте об этом как о наборе ключ-значение, один ко многим. В этом случае «ключ» для каждой группы — это значение DateTime, а «значение» для каждой группы — это набор (IEnumerable) значений ListViewModel.

После этого для каждой полученной «группы» (имеется в виду каждая отдельная дата во всех значениях dt_txt здесь) мы создаем новый анонимный объект из результатов. Это кусок внутри Select лямбды:

new {
            dt_datetime = dateGroup.Key,
            TemperatureAverage = dateGroup.Average(x => x.main.temp),
            WindSpeedAverage = dateGroup.Average(x => x.wind.SPEED),
            HumidityAverage = dateGroup.Average(x => x.main.humidity),
    }

Вы можете видеть, что dt_datetime установлено значение свойства Key (которое присутствует в «группировке», созданной GroupBy), а другие значения рассчитываются из значений dateGroup.

Последнее примечание: если вы планируете повторно использовать значения в своем коде, я бы предложил создать правильный класс для хранения результатов. Что-то подобное:

class WeatherAveragesResult {
    public dt_datetime DateTime;
    public TemperatureAverage float;
    public WindSpeedAverage float;
    public HumidityAverage float;

    public WeatherAveragesResult(datdt_datetime DateTime, tempAvg float, windSpeedAvg float, humidityAvg float) { this.dt = date; ... }
}

И, возможно, создание общедоступного метода в классе ListViewModel для удаления шаблона DateOnly.FromDateTime(new DateTime(listViewModelElement.dt_txt)):

public class ListViewModel {
   // ...

   public DateTime GetDateValue() {
       return DateOnly.FromDateTime(new DateTime(this.dt_txt));
   }
   
   // ...
}

И делать

var result = response.list
    .GroupBy(listViewModelElement => listViewModelElement.GetDateValue())
    .Select(dateGroup => new WeatherAveragesResult (
        dateGroup.Key,
        dateGroup.Average(x => x.main.temp),
        dateGroup.Average(x => x.wind.SPEED),
        dateGroup.Average(x => x.main.humidity))
    );

я думаю, OP хочет сгруппировать по дням, а не покупать dt (в течение дня есть несколько точек dt, см. dt_text опору в json)

Ryan 16.11.2022 20:25

Он считает 2 из приведенного ниже JSON, чтобы получить среднее значение для упомянутых выше полей — 6 точек данных в течение 2 дней => 2 новых средних точки данных.

Ryan 16.11.2022 20:30

Исправлено на dt_txt дней.

Philip P. 16.11.2022 20:34

Другие вопросы по теме