Показаны сообщения с ярлыком linq. Показать все сообщения
Показаны сообщения с ярлыком linq. Показать все сообщения

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, пишите здесь в комментариях, постараюсь помочь оперативно.

5 августа 2011 г.

Как инвертировать строку

Пусть нам дана строка "abcd". Нас просят её инвертировать, т.е. вывести буквы в обратном порядке. Это сделать весьма просто и не нужно писать свой собственный метод. Воспользуемся методом расширения Reverse() и ToArray() из LINQ to Objects.
static void Main(string[] args)
{
var rev = "abcd".Reverse().ToArray();
Console.WriteLine(rev);
Console.ReadKey();
}
Первый метод возвращает итератор символов IEnumerable<char>, который проходит по ним в обратном порядке. Второй метод создаёт на основе этого итератора массив типа char.

P.S. этот вопрос очень часто любят задавать на собеседованиях =)

30 июля 2011 г.

Как вычислить произведение всех чисел в столбце таблицы одним SQL запросом

Я думаю, каждый знает такую статистическую функцию SQL, как SUM(). Она позволяет посчитать сумму всех значений по определённому столбцу (если не задано дополнительных условий). Но что, если нам вдруг понадобиться посчитать не сумму, а произведение всех значений?

Как ни странно, функция SUM() нам тоже пригодится. Как нам от произведения перейти к сумме? Вспомним начала матанализа: логарифм произведения равен сумме логарифмов. Будем использовать натуральный логарифм в паре с функцией возведения экспоненты в степень EXP(). Привожу пример для SQL SERVER, но должно работать и в других СУБД:

SELECT EXP(SUM(LOG(FIELD))) FROM TABLE

То есть от каждого значения в некотором поле таблицы берется натуральный логарифм, затем считается сумма этих логарифмов и затем экспонента возводится в степень, равную этой сумме.

Вот так мы от суммы переходим к произведению в рамках одного изящного SQL запроса. С ходу не могу сказать, может ли это пригодиться в реальных проектах, но согласитесь - выглядит впечатляюще.

То же самое, только уже средствами LINQ to Objects:

// произведение этих чисел равно 120
int[] array = new int[] { 1, 2, 3, 4, 5 };

var result = Math.Exp((from a in array
  select Math.Log(a)).Sum());

Console.WriteLine("Произведение чисел равно: {0}", result);
Console.ReadKey();

В результате программа выдаст 120.

4 февраля 2011 г.

Как работать с таблицами БД из C#

Создайте таблицу в SQL Server. Откройте sql server management studio. В Object Explorer правой кнопкой на папке Tables - New Table. Откроется окно редактирования полей таблицы.

Если нет особых требований по типам данных, то для числовых используйте тип int, а для строковых nvarchar(N), где N - максимальное количество символов. Максимально N может быть равно 4000. Также можно указывать nvarchar(MAX).


Напротив каждого поля в конструкторе ставится отметка, допускающая неопределённые значения. Если это не противоречит вашим бизнес-процессам, в целях удобства сопровождения всегда снимайте эту метку, чтобы запретить неопределённые значения.

У каждой таблицы хотя бы одно поле должно быть помечено как первичный ключ. Первичный ключ позволяет однозначно определить строку в таблице, поэтому его значения не могут повторяться. В качестве такого ключа выбирается отдельный столбец с типом int или с типом uniqueidentifier. Критерии выбора можно посмотреть в этой заметке. Первичный ключ отмечается пиктограммой в виде золотого ключа.

После указания первичного ключа сохраните структуру таблицы (Ctrl+S), здесь вам будет предложено ввести название для неё.

В Visual Studio добавьте в свой проект Add - new item - LINQ to SQL classes. Откроется пока ещё пустая диаграмма с расширением dbml. Если диаграмма dbml будет называться NordmineDatabase, то соответствующий ей класс автоматически получит название NordmineDatabaseDataContext - имейте это в виду, когда будете придумывать ей имя.


Откройте окно server explorer. В нём нужно добавить вашу таблицу. Выберите data connections - add connection. Там нажмите кнопку refresh и выберите из выпадающего списка имя компьютера, на котором установлен sql server. После нажатия на ОК в окне server explorer появится древовидная структура всех элементов вашей БД. Выберите нужную таблицу и просто перетащите её на диаграмму dbml. Нажмите F5 (запуск) или F6 (компиляция без запуска), чтобы последние изменения в диаграмме стали доступны.


Теперь вы можете работать с этой таблицей при помощи linq из любого метода. Пример:
NordmineDatabaseDataContext context = new NordmineDatabaseDataContext();
List<Sale> Sales = (from s in context.Sales select s).ToList();
Здесь из таблицы sales выбираются все строки. На выходе получаем коллекцию из элементов Sale. При этом объект context.Sales и класс Sale со всеми членами были созданы автоматически.

Здесь из таблицы sales выбираются все строки. На выходе получаем коллекцию из элементов Sale. При этом объект context.Sales и класс Sale со всеми членами были созданы автоматически.