WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!


Pages:     || 2 |

Курс "Обзор перспективных технологий Microsoft.NET"

Лекция 8. C# 3.0 и LINQ project

(2 лекции)

Введение

Итак, на прошлой лекции мы упомянули то, что не все концепции окружающего мира хорошо укладываются в объектноориентированное представление. В частности, это относится к реляционным БД и XML.

Проект LINQ (language integrated query) предоставляет возможность создавать запросы к различным источникам информации средствами самого языка программирования. Важно то, что средства именно integrated, т.е. запросы не строятся во время исполнения, как, скажем, доступ к БД с помощью ADO.NET. Это дает нам строгую типизацию с проверкой типов во время компиляции, IntelliSense и прочие удобные возможности. Таким образом, такие понятия БД, как, например, запрос или преобразование, становятся полноправными понятиями языка. Достоинством LINQ также является то, что он предоставляет единый способ доступа к данным вне зависимости от их типа (коллекция C#, XML, база данных).

LINQ определяет стандартный язык запросов, который позволяет:

выборку обход фильтрацию и преобразование данных.

Секрет применимости запросов заключается в том, что в общем случае они работают над любым источником данных, реализующим интерфейс IEnumerable. Более того, за счет того, что запросы реализованы с помощью механизма методов расширения, существует возможность переопределить стандартное поведение запросов, чем, в частности, пользуются разработчики проектов LINQ to SQL (бывший DLinq) и LINQ to XML (бывший XLinq).

Пример Рассмотрим пример с прошлой лекции:

string[] Students = { "Ivanov", "Petrov", "Sidorov", "Medvedov", "Prevedov", "Shpak" };

IEnumerable GoodLastNames = from s in Students where s.Length == orderby s select s.ToUpper();

foreach (string LastName in GoodLastNames) { Console.WriteLine(LastName);

} Этот код выдаст два результата:

MEDVEDOV PREVEDOV Рассмотрим запрос поподробнее:

IEnumerable GoodLastNames = from s in Students where s.Length == orderby s select s.ToUpper();

Переменной GoodLastNames присваивается некоторое выражение запроса, в котором участвуют источник информации (Students) и различные операции запроса, такие как where, orderby и select. На самом деле такой вид запроса – так называемый «синтаксический сахар», т.е. просто более удобная форма написания, которую вообщето можно смоделировать и имеющимися средствами. Компилятор преобразует вышеприведенный оператор в такой:

IEnumerable GoodLastNames = Students.Where(s => s.Length == 8).OrderBy(s => s).Select(s => s.ToUpper());

т.е. в принципе и без выражения запроса вы могли бы сделать то же самое, используя методы IEnumerable под названием Where, OrderBy и Select. «Постойте, – скажете вы, – но у IEnumerable нет таких методов!» – и будете совершенно правы. Именно тут вступают в дело методы расширения, упомянутые на прошлой лекции. В самом деле, Where определяется так:

public static class Sequence { public static IEnumerable Where ( this IEnumerable source Func predicate ) { foreach(T item in source) if(predicate(item)) yield return item;

} } То, что метод Where является статическим, как и класс Sequence, позволяет нам записать вызов так:

IEnumerable GoodLastNames = Sequence.Where(Students, s => s.Length == 8);

однако словечко this в первом параметре метода позволяет нам написать это более компактно:

IEnumerable GoodLastNames = Students.Where(s => s.Length == 8);

Таким образом, мы расширяем интерфейс IEnumerable методом Where, который, в свою очередь, тоже возвращает IEnumerable, что дает возможность далее применять прочие методы расширения, такие как OrderBy и Select. Кроме того, такая техника позволяет нам подменять методы IEnumerable своими реализациями или реализациями третьих производителей, что дает большую гибкость, когда одним и тем же синтаксисом, и даже одним и тем же кодом мы можем доступаться к различным источникам данных. Вызов методов расширения разрешаются во время компиляции, и, подключая разные пространства имен с помощью using (или создавая одноименные методы как члены класса, реализующего IEnumerable), вы можете поразному переопределять эти методы. Стандартные же методы определены в System.Query.



Заметим, что упомянутые методы расширения принимают в качестве параметров лямбдавыражения, тип которых выводится автоматически, что обсуждалось на прошлой лекции. Если пытаться дальше «расшифровывать», что же происходит за сценой, то можно продолжить распутывание клубка, явно записав анонимные делегаты, которые соответствуют лямбдавыражениям:

Func filter = delegate (string s) { return s.Length == 8; };

Func extract = delegate (string s) { return s; };

Func project = delegate (string s) { return s.ToUpper(); };

IEnumerable GoodLastNames = Students.Where(filter).OrderBy(extract).Select(project);

Деревья выражений (expression trees) На прошлой лекции мы обсуждали деревья выражений, один из видов, в котором могут существовать лямбдавыражения. Возможность представлять эти выражения в памяти в виде абстрактного дерева, а не в виде скомпилированного ILкода чрезвычайно важна для расширений LINQ. Так, LINQ to SQL (exDLinq) пользуется этой возможностью для генерации на лету TSQL запросов по обращению к базе данных, описанному вами в вашей программе с помощью языка запросов. Аналогичную технику могут применять сторонние производители, да и вы сами для написания альтернативного доступа к БД или доступа к другим источникам информации, используя ровно тот же языковой синтаксис.

Отложенное вычисление запроса Как вы заметили, метод расширения Where, рассмотренный ранее, возвращает перечислитель (с помощью ключевого слова yield). Как известно, перечислитель не вычисляется в момент получения IEnumerable. Вместо этого он вычисляется в момент перечисления с помощью foreach или в момент преобразования в другой тип данных (например, в массив с помощью метода ToArray). Это дает нам множество различных возможностей:

обращаться к одному и тому же запросу много раз, каждый раз получая актуальные (возможно, разные) результаты производить реальные вычисления только когда это требуется (например, в момент вызова Where в примере выше запрос не готов еще полностью, т.к. на конечный его вид повлияют последующие OrderBy и Select).

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

Аналогичный IEnumerable интерфейс в LINQ to SQL называется IQueryable.

Анонимные типы и LINQ Еще одно нововведение C# 3.0 – анонимные типы – используется для компактной записи порождения сложных результатов запросов. Например, мы хотим породить структуру из имени студента и булевского поля, является ли студент отличником:

var GoodStudents = Students.Select(s => new { s.LastName, isGood = s.AverageMark == 5});

После создания такого запроса мы может итерироваться по его результатам:

foreach(var student in GoodStudents) Console.WriteLine("{0} is a {1} student", student.LastName, student.isGood ? "good" : "bad");

Стандартные операции запросов Рассмотрим основные операции, которые можно включать в запросы в LINQ. Слушатель, знакомый с SQL, испытает при знакомстве с ними легкое чувство дежа вю. J OrderBy – порядок сортировки:

var orderedStudents = Students.OrderBy(s = > s.LastName); // по возрастанию имени var orderedStudentsDesc = Students.OrderByDescending(s = > s.LastName); // по убыванию имени То же самое можно записать запросом:

var orderedStudents = from s in Students orderby s.LastName select s;

ThenBy – применение последующего критерия сортировки:

var orderedStudents = Students.OrderBy(s = > s.LastName).ThenBy(s => s.AverageMark);

// по возрастанию имени, потом – по оценке С помощью языка запросов это записывается проще:

var orderedStudents = from s in Students orderby s.LastName, s.AverageMark select s;

GroupBy – группировка результатов по некоторому критерию:

var groupedStudents = Students.GroupBy(s => s.AverageMark);

GroupBy возвращает перечислитель некоторых IGrouping, внутри которых содержится еще один перечислитель объединенных результатов:

foreach(IGrouping group in groupedStudents) { Console.WriteLine(“Students which have average mark {0}”, group.key);





foreach(Student in group) { Console.WriteLine(Student.LastName);

} } То же самое с помощью запросов:

var groupedStudents = from s in Students group s by s.AverageMark;

Max, Sum, Average – агрегирующие операции:

var totalAverageMark = Students.Sum(s => s.AverageMark) / Students.Length;

или var totalAverageMark = Students.Average(s => s.AverageMark);

Свои собственные агрегирующие операции вы можете описать с помощью метода расширения Aggregate.

Join – операция объединения. Допустим, у нас есть коллекция преподавателей, объект «преподаватель» содержит поле LastName, и мы хотим найти всех однофамильцев студентов и преподавателей и выдать их общую фамилию:

var sameLastNames = Students.Join( Teachers, student => student.LastName, teacher => teacher.LastName, (student, teacher) => student.LastName);

То же самое запросом:

var sameLastNames = from student in Students join teacher in Teachers on student.LastName equals teacher.LastName select student.LastName;

Вместо join можно использовать нескольких источников данных в одном запросе:

var sameNames8 = from student in Students where student.LastName.Length == from teacher in Teachers where student.LastName == teacher.LastName select new { student.AverageMark, teacher.Salary };

Этот запрос выдаст пары «средняя оценка – зарплата» для всех пар студентов и преподавателей с одинаковой фамилией из 8 букв.

LINQ to SQL (exDLinq) Перейдем к обсуждению части LINQ, которая нацелена на работу с базами данных. Как упоминалось ранее, одной из целей LINQ было сделать базаданческие понятия сущностями «первого класса». Сейчас мы рассмотрим, каким образом LINQ достигает этой цели.

Разметка классов Для начала вы должны разметить свои классы, предназначенные для хранения в базе данных, специализированными атрибутами. Довольно логично главные атрибуты называются Table и Column:

[Table(Name= "Students")] public class Student { [Column(Id=true)] public uint id;

[Column] public string LastName;

public float AverageMark;

} Приведенный класс Student хранится в таблице Students, его поле id хранится в колонке id и при этом является ключом, поле LastName хранится в колонке LastName, а поле AverageMark не хранится вовсе. В БД хранятся только поля или свойства, имеющие атрибут Column. Если у атрибута Column не проставлено явно имя колонки, то считается, что имя колонки совпадает с именем поля или свойства. Исходные типы, которые не укладываются в.NETтовский набор, могут быть помечены параметром DbType:

[Column(DbType=”nvarchar(32) not null”] string LastName;

С точки зрения соответствия типов полезными могут оказаться появившиеся во второй версии фреймворка nullableтипы. Действительно, размерные типы в.NET не могут принимать значение null, в то время как в БД поле типа, скажем, int, вполне может быть null. Сейчас, во второй версии.NET это можно записать так:

[Column] float? AverageMark;

DataContext Для того, чтобы осуществлять связь с базой данных, существует специальный класс DataContext. DataContext похож на класс Connection из ADO.NET:

DataContext studentsDb = new DataContext(pathToDatabase);

Table Students = studentsDb.GetTable();

Как и в случае с «обычными» запросами, GetTable не осуществляет никакого взаимодействия с базой данных. Это то же самое «отложенное исполнение». Работа с таблицей происходит при реальном обращении к данным, например, при переборе с помощью foreach:

var goodStudents = from student in Students where student.AverageMark = select student;

foreach(var student in goodStudents) { Console.WriteLine("{0} is a good student", student.LastName);

} Relationships Отношения в реляционных базах данных обычно строятся с помощью внешних ключей (foreign keys), которые ссылаются на первичный ключ в другой таблице. В программе же хотелось бы в объектах, представляющих связанные абстракции, иметь ссылки друг на друга. Для того чтобы LINQ to SQL автоматически построил для вас такие связи, надо использовать атрибут Association:

[Table(Name= "Students")] public class Student { [Column(Id=true)] public uint id;

[Column] public string LastName;

Pages:     || 2 |










© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

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