22 октября 2012 г.

Основные запросы LINQ to Objects

LINQ (Language INtegrated Query, язык интегрированных запросов) - это довольно мощная и удобная технология, впервые представленная в .NET Framework 3.5. Основная его идея заключается в том, чтобы можно было извлекать информацию из любых объектов написанием запросов, похожих на запросы к базе данных. Ключевое слово здесь "любых". Именно поэтому выделяются такие понятия:
  • LINQ to Objects
  • LINQ to XML
  • LINQ to DataSet
  • LINQ to SQL
  • LINQ to Entities
  • Parallel LINQ
В этой своей заметке я хотел бы рассмотреть самую простую разновидность LINQ - LINQ to Objects. Тем не менее, изучив основные методы и конструкции LINQ to Objects можно будет легко использовать и другие его разновидности, т.к. интерфейс их почти одинаков.

Первое, с чем надо разобраться, заключается в том, что в LINQ можно использовать два синтаксиса. Самые базовые конструкции LINQ можно записывать в виде выражений запросов. Те же базовые конструкции + большая часть остальных доступна в виде обычной "точечной нотации" (т.е. методов, которые в качестве параметров получают лямбда-выражения). Базовые конструкции я использую только в виде выражений запросов, т.к. считаю этот синтаксис более наглядным. Для остальных конструкций приходится использовать "точечную нотацию".

Вторая особенность LINQ состоит в том, что все запросы делятся на отложенные и не отложенные. Отложенные запросы легко определить по типу возвращаемого значения. Для LINQ to Objects это тип IEnumerable<T>. Отложенные запросы на самом деле возвращают не данные, а лишь информацию о том, как их следует извлекать. Это сделано для того, чтобы можно было составлять цепочки ограничений на выборку без потери в производительности. В противовес этому, не отложенные запросы возвращают, как правило, конкретные объекты или коллекции этих объектов. Примеры методов, которые приводят к немедленному выполнению запросов:
  • Преобразование к одному из типов коллекций: ToList(), ToDictionary(), ToArray() и т.п.
  • Получение единственного элемента: Single(), First(), Last() и т.п.
  • Функции агрегации: Count(), Sum(), Max(), Min(), Average() и т.п.
За более полным списком обращайтесь к официальной документации MSDN.

Вполне логично предположить, что не отложенные операции должны заканчивать ваши запросы LINQ, т.к. в конечном итоге вам нужно получить конкретные данные. Но всегда нужно смотреть в зависимости от ситуации.

Теперь перейдём к примерам. У нас имеется тестовое приложение, состоящее из нескольких классов. Привожу их код.

Класс-сущность User.
using System;
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);
        }
    }
}
Класс-сущность Record, зависящая от User.
using System;
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;
        }
    }
}
Класс бизнес-логики по обработке данных. Именно в нём находятся LINQ-запросы.
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)
{
return (from u in users
where u.Surname == surname
select u).ToList();
}
Фильтрация коллекции пользователей по имени. В отличие от SQL-запросов, в LINQ сначала идёт ключевое слово from. Это сделано для сужения контекста автоподстановки в Visual Studio. После from идёт локальная переменная u (может быть любой, это как алиас в SQL-запросах), которой мы обозначаем текущий элемент в коллекции users в рамках данного запроса. Затем задаём ограничения на выборку в конструкции where. Здесь указывается любое булевое выражение. Каждый элемент коллекции будет добавлен в выборку, если для него будет удовлетворяться это условие. И в конце указываем при помощи select, какие именно данные нам нужны. Можно указать как весь элемент целиком, так и отдельные его поля. Результатом этого запроса будет отложенная операция. После всю эту выборку мы преобразуем в список при помощи не отложенной операции ToList().
public User GetUserByID(int id)
{
return (from u in users
where u.ID == id
select u).Single();
}
Получение одного конкретного пользователя по его id. В конце вызываем метод Single(), которые возвращает элемент, если он единственный. В противном случае, метод генерирует исключение, если найдено несколько элементов.
public List<User> GetUsersBySubstring(String substring)
{
return (from u in users
where u.Name.Contains(substring) || u.Surname.Contains(substring)
select u).ToList();
}
Выборка пользователей по подстроке. Ищем эту подстроку в имени или в фамилии при помощи метода Contains().
public List<String> GetAllUniqueNames()
{
return (from u in users
select u.Name).Distinct().ToList();
}
Выбираем только уникальные имена при помощи метода Distinct().
public List<User> GetAllAuthors()
{
return (from u in users
join r in records on u equals r.Author
select u).ToList();
}
Выбираем всех авторов, т.е. пользователей, у которых есть сообщения. Здесь демонстрируется объединение двух коллекций, аналогичное операции join в SQL-запросе. Связывание коллекций идёт при помощи ссылки на объект пользователя, указанного в сущности Record.
public Dictionary<int, User> GetUsersDictionary()
{
return (from u in users
select u).ToDictionary(i => i.ID, i => i);
}
Преобразуем коллекцию пользователей в словарь при помощи двух лямбда-выражений, которые принимает метод ToDictionary(). Первое выражение указывает, какое поле сделать ключом, второе - что выбрать в качестве значения. В данном случае в качестве значения выбираем саму сущность пользователя.
public int GetMaxID()
{
return (from u in users
select u.ID).Max();
}
Выбираем максимальное значение поля ID сущности пользователя в коллекции.
public List<User> GetOrderedUsers()
{
return (from u in users
orderby u.ID
select u).ToList();
}
Сортируем пользователей по их идентификатору при помощи конструкции orderby.
public List<User> GetDescendingOrderedUsers()
{
return (from u in users
orderby u.ID descending
select u).ToList();
}
Обратная сортировка пользователей. После orderby добавлено ключевое слово descending.
public List<User> GetReversedUsers()
{
return (from u in users
orderby u.ID
select u).Reverse().ToList();
}
Здесь метод Reverse() возвращает итератор, который проходит по коллекции от конца в начало. Результат работы данного метода идентичен предыдущему результату (обратная сортировка).
public List<User> GetUsersPage(int pageSize, int pageIndex)
{
return (from u in users
select u).Skip(pageIndex * pageSize).Take(pageSize).ToList();
}
И наконец, пример реализации пейджинга, т.е. постраничного вывода элементов коллекции. Метод Skip() пропускает указанное количество элементов от начала, а Take() включает в выборку то количество элементов с текущей позиции, которое максимально возможно на одной странице.

Обратите внимание, что не отложенные операции я поместил в самом конце выражений. Все операции перед ними являются отложенными, т.е. не выполняются сразу. Их выполнение инициирует не отложенная операция.

В этой заметке я постарался рассмотреть самые распространённые конструкции LINQ to Objects. Теперь вы сможете довольно быстро разобраться и в других разновидностях LINQ.

Загрузить архив с примерами можно здесь.

P.S. Если возникнут вопросы как писать запросы с другими методами LINQ, пишите здесь в комментариях, постараюсь помочь оперативно.

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

Отправить комментарий