Использование LINQ
Все возможности, которые мы описывали до сих пор, хорошо работают с LINQ. Мы любим LINQ. Это прекрасное и важное дополнение к .NET. Если вы никогда не использовали LINQ, вы очень многое упустили. LINQ представляет собой SQL-подобный синтаксис для выборки данных в классах. Представьте себе, что у нас есть набор объектов Product
, и мы хотим получить три из них с самыми высокими ценами и передать их методу View
. Без LINQ мы в итоге получили бы нечто похожее на листинг 4-27, который показывает метод действия FindProducts
, добавленный в контроллер Home
.
Листинг 4-27: Выборка без LINQ
public ViewResult FindProducts()
{
Product[] products = {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
// определяем массив для результатов
Product[] foundProducts = new Product[3];
// сортируем содержание массива
Array.Sort(products, (item1, item2) =>
{
return Comparer<decimal>.Default.Compare(item1.Price, item2.Price);
});
// получаем три первых элемента массива в качестве результата
Array.Copy(products, foundProducts, 3);
// создаем результат
StringBuilder result = new StringBuilder();
foreach (Product p in foundProducts)
{
result.AppendFormat("Price: {0} ", p.Price);
}
return View("Result", (object)result.ToString());
}
С LINQ мы можем значительно упростить процесс выборки, как показано в листинге 4-28.
Листинг 4-28: Использование LINQ для выборки данных
public ViewResult FindProducts()
{
Product[] products = {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
var foundProducts = from match in products
orderby match.Price descending
select new
{
match.Name,
match.Price
};
// создаем результат
int count = 0;
StringBuilder result = new StringBuilder();
foreach (var p in foundProducts)
{
result.AppendFormat("Price: {0} ", p.Price);
if (++count == 3)
{
break;
}
}
return View("Result", (object)result.ToString());
}
}
Это намного аккуратнее. Вы можете увидеть SQL-подобную выборку, выделенную жирным шрифтом. Мы располагаем объекты Product
в порядке убывания по свойству Price
и используем ключевое слово select
, чтобы вернуть анонимный тип, содержащий только свойства Name
и Price
. Этот стиль LINQ известен как синтаксис запросов, и это тот стиль, который разработчики считают наиболее удобным, когда они начинают использовать LINQ. Такая выборка возвращает один анонимно типизированный объект для каждого Product
в массиве, который мы использовали в исходном запросе, поэтому нам нужно поработать с результатами, чтобы получить первые три объекта и вывести их на экран.
Если же мы готовы отказаться от простоты синтаксиса запросов, мы сможем ощутить намного больше мощи LINQ. Альтернативой является синтаксис точечной нотации, или точечная нотация, которая основана на методах расширения. В листинге 4-29 показано, как мы можем использовать этот альтернативный синтаксис для обработки наших объектов Product
.
Листинг 4-29: Использование точечной нотации LINQ
public ViewResult FindProducts() {
Product[] products = {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
var foundProducts = products.OrderByDescending(e => e.Price)
.Take(3)
.Select(e => new {
e.Name,
e.Price
});
StringBuilder result = new StringBuilder();
foreach (var p in foundProducts) {
result.AppendFormat("Price: {0} ", p.Price);
}
return View("Result", (object)result.ToString());
}
Мы будем первыми, кто признает, что на эту LINQ выборку, выделенную жирным шрифтом, не так приятно смотреть, как на синтаксис запроса. Однако, не для всех LINQ функций имеются соответствующие ключевые слова C#. Как серьезные LINQ программисты мы должны перейти к использованию методов расширения. Каждый из методов расширение LINQ в листинге применяется к IEnumerable<T>
и возвращает тоже IEnumerable<T>
, что позволяет связывать методы цепочкой, чтобы формировать сложные выборки.
Примечание
Все методы расширения LINQ находятся в пространстве имен
System.Linq
, которое вы должны вставить в код при помощи ключевого словаusing
, прежде чем делать выборку. Visual Studio автоматически добавляет пространство именSystem.Linq
к классам контроллера, но вы можете добавить его вручную в любом другом месте MVC проекта.
Метод OrderByDescending
сортирует элементы в исходных данных. В этом случае лямбда-выражение возвращает значение, которое мы хотим использовать для сравнения. Метод Take
возвращает указанное число элементов с начала цепочки результатов (это то, что мы не могли сделать, используя синтаксис запроса). Метод Select
позволяет нам проектировать нужные результаты. В данном случае мы проектируем анонимный объект, который содержит свойства Name
и Price
. Обратите внимание, что нам даже не нужно указывать имена свойств в анонимном типе. C# сделал вывод, основываясь на свойствах, которые мы вставили в метод Select
.
В таблице 4-1 описаны наиболее полезные методы расширения LINQ. Мы много используем LINQ в этой книге, и , возможно, вы захотите вернуться к этой таблице, когда повстречаете метод расширения, с которым вы не сталкивались раньше. Все методы LINQ, представленные в таблице, работают с IEnumerable<T>
.
Таблица 4-1: Некоторые полезные методы расширения LINQ
Метод расширения | Описание | Отложенный |
All |
Возвращает true , если все элементы в исходных данных соответствуют утверждению |
Нет |
Any |
Возвращает true , если, как минимум, один из элементов в исходных данных соответствуют утверждению |
Нет |
Contains |
Возвращает true , если исходные данные содержат указанный элемент или значение |
Нет |
Count |
Возвращает число элементов в исходных данных | Нет |
First |
Возвращает первый элемент из исходных данных | Нет |
FirstOrDefault |
Возвращает первый элемент из исходных данных или значение по умолчанию, если элементов нет | Нет |
Last |
Возвращает последний элемент из исходных данных | Нет |
LastOrDefault |
Возвращает последний элемент из исходных данных или значение по умолчанию, если элементов нет | Нет |
Max , Min |
Возвращает самое большое или самое маленькое значение, указанное лямбда-выражением | Нет |
OrderBy , OrderByDescending |
Сортирует исходные данные, основываясь на значении, возвращенном лямбда-выражением | Да |
Reverse |
Меняет порядок элементов в исходных данных | Да |
Select |
Проектирует результат выборки | Да |
SelectMany |
Проектирует каждый элемент данных в последовательность элементов, а затем объединяет все эти результирующие последовательности в одну последовательность | Да |
Single |
Возвращает первый элемент из исходных данных или выбрасывает исключение, если есть несколько совпадений | Нет |
SingleOrDefault |
Возвращает первый элемент из исходных данных или значение по умолчанию, если элементов нет, или выбрасывает исключение, если есть несколько совпадений | Нет |
Skip , SkipWhile |
Пропускает указанное число элементов или пропускает элементы, соответствующие утверждению | Да |
Sum |
Подсчитывает выбранные значения | Нет |
Take , TakeWhile |
Выбирает указанное число элементов от начала исходных данных или выбирает элементы, пока идет соответствие утверждению | Да |
ToArray , ToDictionary , ToList |
Конвертирует исходные данные в массив или другие типы коллекций | Нет |
Where |
Фильтрует элементы из исходных данных, которые не соответствуют утверждению | Да |
Отложенные выборки LINQ
Вы заметили, что в таблице 4-1 содержится столбец «Отложенный». Есть интересный момент в том, как методы расширения выполняются в LINQ запросе. Выборка, которая содержит только отложенные методы, не будет выполняться до тех пор, пока не будут перечислены элементы результата, как показано в листинге 4-30. Здесь представлено простое изменение в методе действия FindProducts
.
Листинг 4-30: Использование в выборке отложенных методов расширения LINQ
public ViewResult FindProducts()
{
Product[] products = {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
var foundProducts = products.OrderByDescending(e => e.Price)
.Take(3)
.Select(e => new
{
e.Name,
e.Price
});
products[2] = new Product { Name = "Stadium", Price = 79600M };
StringBuilder result = new StringBuilder();
foreach (var p in foundProducts)
{
result.AppendFormat("Price: {0} ", p.Price);
}
return View("Result", (object)result.ToString());
}
Между определением LINQ выборки и перечислением результатов мы изменили один из элементов массива products
. Результат этого примера выглядит следующим образом:
Price: 79600 Price: 275 Price: 48.95
Вы видите, что запрос не обрабатывается до получения результатов перечисления, и поэтому изменение, которое мы сделали – введение Stadium
в массив Product
– отображается в выходных данных.
Совет
Одна интересная особенность отложенных методов расширения LINQ заключается в том, что запросы обрабатываются с нуля каждый раз после перечисления результатов, это обозначает, что вы можете выполнить выборку повторно в качестве исходных данных для изменений.
В отличие от этого, использование любого из не отложенных (nondeferred) методов расширения приводит к тому, что выборка LINQ выполняется немедленно. В листинге 4-31 показан метод действия SumProducts
, которые мы добавили в контроллер Home
.
Листинг 4-31: Немедленно выполняемая выборка LINQ
public ViewResult SumProducts()
{
Product[] products = {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
var results = products.Sum(e => e.Price);
products[2] = new Product { Name = "Stadium", Price = 79500M };
return View("Result", (object)String.Format("Sum: {0:c}", results));
}
В этом примере используется метод Sum
, который является не отложенным и выдает следующий результат:
Sum: $378.40
Вы видите, что элемент Stadium
с его гораздо более высокой ценой не был включен в результаты. Это произошло потому, что метод Sum
сразу же выдает результат, как только вызывается этот метод, поскольку он является не отложенным методом.
LINQ и интерфейс IQueryable<T>
Можно повстречать различные вариации LINQ, хотя используется он всегда практически одинаково. Одной из разновидностей является LINQ to Objects, и эту разновидность мы использовали в примерах данной главы. LINQ to Objects позволяет делать выборку C# объектов, которые находятся в памяти. Другая разновидность, LINQ to XML – это очень удобный и мощный способ создания, обработки и выборки XML содержания. Параллельный LINQ (Parallel LINQ, PLINQ) является расширенной версией LINQ to Object, которая поддерживают выполнение LINQ выборок одновременно на нескольких процессорах или ядрах.
Особый интерес для нас представляет LINQ to Entities, который позволяет делать LINQ запросы к данным, полученным из Entity Framework. Entity Framework является ORM фреймворком Microsoft, представляющий собой часть более широкой платформы ADO.NET. ORM позволяет работать с реляционными данными при помощи C# объектов. И это тот механизм, который мы будем использовать в данной книге для доступа к данным, хранящимся в базах данных. Вы увидите, как используются Entity Framework и LINQ to Entities, в главе 4. Но мы также хотели упомянуть интерфейс
IQueryable<T>
во время представления LINQ.Интерфейс
IQueryable<T>
является производным отIEnumerable<T>
и используется для обозначения результата выборки, выполненной в отношении конкретного источника данных. В наших примерах это будет база данных SQL Server. Вообще, нет необходимости использоватьIQueryable<T>
напрямую. Одна из приятных особенностей LINQ заключается в том, что одна и та же выборка может быть выполнена для нескольких типов исходных данных (объектов, XML, баз данных и т. д.). Когда вы увидите, что мы используемIQueryable<T>
в примерах дальнейших глав, то вы поймете, что мы делаем это, чтобы было ясно, что мы имеем дело с данными, которые приходят из базы данных.