Попытка установить свойство в группе LINQ путем выбора с использованием цикла

#linq

#linq

Вопрос:

У меня есть следующий код:

 public class Report
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Sales { get; set; }
}

var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name }).Select(x => new Report { Id = x.Key.Id, Name = x.Key.Name });

foreach (var item in result)
{
    item.Sales = anotherColletion.FirstOrDefault(x => x.Id == item.Id).Sales;
}
  

Я не могу установить для свойства sales какое-либо значение таким образом. Даже если я попытаюсь:

 foreach (var item in result)
{
    item.Sales = 50;
}
  

Однако, если я установлю свойство, используя следующий код, оно сработает:

 var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name }).Select(x => new Report { Id = x.Key.Id, Name = x.Key.Name, Sales = 50 });
  

Это специально?

Ответ №1:

Проблема в том, что запросы LINQ являются ленивыми («отложенное выполнение»). Вы устанавливаете свойство для каждого результата запроса в foreach цикле, но эти результаты, по сути, растворятся в воздухе. Когда вы снова перечисляете результаты запроса после вашего foreach (который вы нам не показали), запрос выполняется повторно, и результаты воссоздаются, фактически отменяя ваши изменения. Помните, что запрос — это просто спецификация того, как выдавать результаты, а не сами результаты.

Простое исправление заключается в том, чтобы сначала материализовать запрос в коллекцию.

 var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name })
                    .Select(x => new Report { Id = x.Key.Id, Name = x.Key.Name })
                    .ToList();
  

foreach В конечном итоге это приведет к изменению элементов коллекции в памяти, а не результатов отложенного запроса, и поэтому они будут видны ниже по потоку.

Лично, однако, рассмотрите возможность установки свойства в самом запросе:

 var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name })
                    .Select(x => new Report
                                 { 
                                   Id = x.Key.Id,
                                   Name = x.Key.Name,
                                   Sales = anotherCollection.First(a => a.Id == x.KeyId)
                                                            .Sales
                                  });
  

Комментарии:

1. Если бы я должен был выполнять свои элементы. Сначала выполните ToList(), а затем выполните GroupBy() без ToList(), сработает ли это или это все равно приведет к повторному выполнению запроса?

2. @Thomas: Технически это должно сработать, потому что изменяемые Report объекты уже были созданы и материализованы на этом этапе. Повторно будет выполнена только группировка, а не создание объекта. Я настоятельно рекомендую этого не делать.

3. Причина, по которой я бы материализовал свои элементы. ToList () — это потому, что я хочу создать несколько GroupBy (не включенных в этот вопрос) для коллекции и использовать эти коллекции для вычислений. Я рассуждаю о том, чтобы запустить один запрос к базе данных, а затем использовать коллекцию in memory для другой GroupBy. Могу я спросить, почему вы не рекомендуете это делать?

4. @Thomas: Есть ли какая-либо причина не реализовывать результаты GroupBy , если вы собираетесь перечислять результаты несколько раз? Вы хотите, чтобы запрос реагировал на изменения в списке?

5. Я думал о материализации своих элементов. ToList() в память и предварительные операции над коллекцией в памяти (например, с несколькими GroupBy), в отличие от того, чтобы не выполнять и запускать несколько SQL-запросов для каждой операции. Таким образом, единственной причиной является количество запросов, которые могут быть сгенерированы.

Ответ №2:

Томас,

Используемые вами результаты var — это просто запрос, когда вы выполняете итерацию по нему в цикле foreach, вы генерируете новый объект отчета, но он нигде не «сохраняется».

Добавьте toArray() или ToList() в конец запроса, чтобы устранить проблему:

 var result = myItems.GroupBy(x => new { Id = x.Id, Name = x.Name }).Select(x => new Report { Id = x.Key.Id, Name = x.Key.Name }).ToList();  
  

Amir.