LINQ (Language INtegrated Query, язык интегрированных запросов) - это довольно мощная и удобная технология, впервые представленная в .NET Framework 3.5. Основная его идея заключается в том, чтобы можно было извлекать информацию из любых объектов написанием запросов, похожих на запросы к базе данных. Ключевое слово здесь "любых". Именно поэтому выделяются такие понятия:
Первое, с чем надо разобраться, заключается в том, что в LINQ можно использовать два синтаксиса. Самые базовые конструкции LINQ можно записывать в виде выражений запросов. Те же базовые конструкции + большая часть остальных доступна в виде обычной "точечной нотации" (т.е. методов, которые в качестве параметров получают лямбда-выражения). Базовые конструкции я использую только в виде выражений запросов, т.к. считаю этот синтаксис более наглядным. Для остальных конструкций приходится использовать "точечную нотацию".
Вторая особенность LINQ состоит в том, что все запросы делятся на отложенные и не отложенные. Отложенные запросы легко определить по типу возвращаемого значения. Для LINQ to Objects это тип IEnumerable<T>. Отложенные запросы на самом деле возвращают не данные, а лишь информацию о том, как их следует извлекать. Это сделано для того, чтобы можно было составлять цепочки ограничений на выборку без потери в производительности. В противовес этому, не отложенные запросы возвращают, как правило, конкретные объекты или коллекции этих объектов. Примеры методов, которые приводят к немедленному выполнению запросов:
Вполне логично предположить, что не отложенные операции должны заканчивать ваши запросы LINQ, т.к. в конечном итоге вам нужно получить конкретные данные. Но всегда нужно смотреть в зависимости от ситуации.
Теперь перейдём к примерам. У нас имеется тестовое приложение, состоящее из нескольких классов. Привожу их код.
Класс-сущность User.
Обратите внимание, что не отложенные операции я поместил в самом конце выражений. Все операции перед ними являются отложенными, т.е. не выполняются сразу. Их выполнение инициирует не отложенная операция.
В этой заметке я постарался рассмотреть самые распространённые конструкции LINQ to Objects. Теперь вы сможете довольно быстро разобраться и в других разновидностях LINQ.
Загрузить архив с примерами можно здесь.
P.S. Если возникнут вопросы как писать запросы с другими методами LINQ, пишите здесь в комментариях, постараюсь помочь оперативно.
- LINQ to Objects
- LINQ to XML
- LINQ to DataSet
- LINQ to SQL
- LINQ to Entities
- Parallel LINQ
Первое, с чем надо разобраться, заключается в том, что в LINQ можно использовать два синтаксиса. Самые базовые конструкции LINQ можно записывать в виде выражений запросов. Те же базовые конструкции + большая часть остальных доступна в виде обычной "точечной нотации" (т.е. методов, которые в качестве параметров получают лямбда-выражения). Базовые конструкции я использую только в виде выражений запросов, т.к. считаю этот синтаксис более наглядным. Для остальных конструкций приходится использовать "точечную нотацию".
Вторая особенность LINQ состоит в том, что все запросы делятся на отложенные и не отложенные. Отложенные запросы легко определить по типу возвращаемого значения. Для LINQ to Objects это тип IEnumerable<T>. Отложенные запросы на самом деле возвращают не данные, а лишь информацию о том, как их следует извлекать. Это сделано для того, чтобы можно было составлять цепочки ограничений на выборку без потери в производительности. В противовес этому, не отложенные запросы возвращают, как правило, конкретные объекты или коллекции этих объектов. Примеры методов, которые приводят к немедленному выполнению запросов:
- Преобразование к одному из типов коллекций: ToList(), ToDictionary(), ToArray() и т.п.
- Получение единственного элемента: Single(), First(), Last() и т.п.
- Функции агрегации: Count(), Sum(), Max(), Min(), Average() и т.п.
Вполне логично предположить, что не отложенные операции должны заканчивать ваши запросы LINQ, т.к. в конечном итоге вам нужно получить конкретные данные. Но всегда нужно смотреть в зависимости от ситуации.
Теперь перейдём к примерам. У нас имеется тестовое приложение, состоящее из нескольких классов. Привожу их код.
Класс-сущность User.
using System;Класс-сущность Record, зависящая от User.
namespace Developer.Remarks.Blogspot.Com
{
class User
{
public int ID { get; set; }
public String Name { get; set; }
public String Surname { get; set; }
public User(int id, String name, String surname)
{
this.ID = id;
this.Name = name;
this.Surname = surname;
}
public override string ToString()
{
return string.Format("ID={0}: {1} {2}", ID, Name, Surname);
}
}
}
using System;Класс бизнес-логики по обработке данных. Именно в нём находятся LINQ-запросы.
namespace Developer.Remarks.Blogspot.Com
{
class Record
{
public User Author { get; set; }
public String Message { get; set; }
public Record(User author, String message)
{
this.Author = author;
this.Message = message;
}
}
}
using System;Теперь будем рассматривать каждый запрос по отдельности.
using System.Collections.Generic;
using System.Linq;
namespace Developer.Remarks.Blogspot.Com
{
class BusinessLogic
{
private List<User> users = new List<User>();
private List<Record> records = new List<Record>();
public BusinessLogic()
{
// наполнение обеих коллекций тестовыми данными
}
public List<User> GetUsersBySurname(String surname)Фильтрация коллекции пользователей по имени. В отличие от SQL-запросов, в LINQ сначала идёт ключевое слово from. Это сделано для сужения контекста автоподстановки в Visual Studio. После from идёт локальная переменная u (может быть любой, это как алиас в SQL-запросах), которой мы обозначаем текущий элемент в коллекции users в рамках данного запроса. Затем задаём ограничения на выборку в конструкции where. Здесь указывается любое булевое выражение. Каждый элемент коллекции будет добавлен в выборку, если для него будет удовлетворяться это условие. И в конце указываем при помощи select, какие именно данные нам нужны. Можно указать как весь элемент целиком, так и отдельные его поля. Результатом этого запроса будет отложенная операция. После всю эту выборку мы преобразуем в список при помощи не отложенной операции ToList().
{
return (from u in users
where u.Surname == surname
select u).ToList();
}
public User GetUserByID(int id)Получение одного конкретного пользователя по его id. В конце вызываем метод Single(), которые возвращает элемент, если он единственный. В противном случае, метод генерирует исключение, если найдено несколько элементов.
{
return (from u in users
where u.ID == id
select u).Single();
}
public List<User> GetUsersBySubstring(String substring)Выборка пользователей по подстроке. Ищем эту подстроку в имени или в фамилии при помощи метода Contains().
{
return (from u in users
where u.Name.Contains(substring) || u.Surname.Contains(substring)
select u).ToList();
}
public List<String> GetAllUniqueNames()Выбираем только уникальные имена при помощи метода Distinct().
{
return (from u in users
select u.Name).Distinct().ToList();
}
public List<User> GetAllAuthors()Выбираем всех авторов, т.е. пользователей, у которых есть сообщения. Здесь демонстрируется объединение двух коллекций, аналогичное операции join в SQL-запросе. Связывание коллекций идёт при помощи ссылки на объект пользователя, указанного в сущности Record.
{
return (from u in users
join r in records on u equals r.Author
select u).ToList();
}
public Dictionary<int, User> GetUsersDictionary()Преобразуем коллекцию пользователей в словарь при помощи двух лямбда-выражений, которые принимает метод ToDictionary(). Первое выражение указывает, какое поле сделать ключом, второе - что выбрать в качестве значения. В данном случае в качестве значения выбираем саму сущность пользователя.
{
return (from u in users
select u).ToDictionary(i => i.ID, i => i);
}
public int GetMaxID()Выбираем максимальное значение поля ID сущности пользователя в коллекции.
{
return (from u in users
select u.ID).Max();
}
public List<User> GetOrderedUsers()Сортируем пользователей по их идентификатору при помощи конструкции orderby.
{
return (from u in users
orderby u.ID
select u).ToList();
}
public List<User> GetDescendingOrderedUsers()Обратная сортировка пользователей. После orderby добавлено ключевое слово descending.
{
return (from u in users
orderby u.ID descending
select u).ToList();
}
public List<User> GetReversedUsers()Здесь метод Reverse() возвращает итератор, который проходит по коллекции от конца в начало. Результат работы данного метода идентичен предыдущему результату (обратная сортировка).
{
return (from u in users
orderby u.ID
select u).Reverse().ToList();
}
public List<User> GetUsersPage(int pageSize, int pageIndex)И наконец, пример реализации пейджинга, т.е. постраничного вывода элементов коллекции. Метод Skip() пропускает указанное количество элементов от начала, а Take() включает в выборку то количество элементов с текущей позиции, которое максимально возможно на одной странице.
{
return (from u in users
select u).Skip(pageIndex * pageSize).Take(pageSize).ToList();
}
Обратите внимание, что не отложенные операции я поместил в самом конце выражений. Все операции перед ними являются отложенными, т.е. не выполняются сразу. Их выполнение инициирует не отложенная операция.
В этой заметке я постарался рассмотреть самые распространённые конструкции LINQ to Objects. Теперь вы сможете довольно быстро разобраться и в других разновидностях LINQ.
Загрузить архив с примерами можно здесь.
P.S. Если возникнут вопросы как писать запросы с другими методами LINQ, пишите здесь в комментариях, постараюсь помочь оперативно.
Комментариев нет:
Отправить комментарий