#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.