Главная страница   /   4.8. Использование LINQ (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

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