Остальные стандартные операторы




До этого момента мы рассматривали наиболее часто используемые операторы запросов: Select и Where. Сейчас мы приступим к рассмотрению остальных операторов запросов.

Сортировка и группировка

Получить результаты запроса – это одно дело. Но зачастую требуется нечто большее, например бывает необходимо построить результаты запроса в определённом порядке. Здесь нам поможет оператор OrderBy.

Операторы OrderBy и OrderByDescending позволяют выстроить результирующие элементы в некотором заранее определённом порядке, например в порядке возрастания какого-нибудь свойства. Вот простенький пример их использования:

Код C# 3.0

IEnumerable<Forum> forums = new Forum[]

{

new Forum(){Admin = "WoWa",

MembersCount = 17000,

Name = "Vingrad",

URL = " https://forum.vingrad.ru "},

new Forum(){Admin = "Bill Gates",

MembersCount = 12345,

Name = "GotDotNet",

URL = " https://gotdotnet.ru "},

new Forum(){Admin = "IT",

MembersCount = -1,

Name = "RSDN",

URL = " https://rsdn.ru "}

}

var sortedByMembers = forums.OrderBy(f => f.MembersCount);

var sortedByName = forums.OrderBy(f => f.Name);

var sortedByAdminDescending = forums.OrderByDescending(f => f.Admin);

var sortedByNameLength = forums.OrderBy(f => f.Name.Length);

 

Код VB.NET 9.0

Dim forums As IEnumerable(Of Forum) = New Forum() { _

New Forum {Admin:= "WoWa", _

MembersCount:= 17000, _

Name:= "Vingrad", _

URL:= " https://forum.vingrad.ru "}, _

New Forum {Admin:= "Bill Gates", _

MembersCount:= 12345, _

Name:= "GotDotNet", _

URL:= " https://gotdotnet.ru "}, _

New Forum {Admin:= "IT", _

MembersCount:= -1, _

Name:= "RSDN", _

URL:= " https://rsdn.ru "} _

}

Dim sortedByMembers = Select aForum _

From aForum In forums _

Order By aForum.MembersCount

Dim sortedByName = Select aForum _

From aForum In forums _

Order By aForum.Name

Dim sortedByAdminDescending = Select aForum _

From aForum In forums _

Order By aForum.Admin Descending

 

Примечание

Поскольку в VB.NET ещё не реализованы λ-выражения, примеров будет мало, а если и будут, то только написанные с использованием стандартного синтаксиса запросов, который поддерживается.

И оператор OrderBy, и OrderByDescending возвращают SortedSequence<T> для того, чтобы было возможным добавлять ещё критерии сортировки через операторы ThenBy и ThenByDescending:

Код C# 3.0

string[] someMembers = {"Exception", "WoWa", "mr.DUDA", "arilou", "chipset", "Vit"};

var bla = names.OrderBy(s => s.Length).ThenByDescending (s => s);

Код VB.NET 9.0

Dim someMembers As String() = _

{"Exception", "WoWa", "mr.DUDA", "arilou", "chipset", "Vit"}

Dim bla = Select name _

From name In someMembers _

Order By name.Length, name Descending

Собственно говоря, это значит, что сначала все имена сортируются по количеству знаков, а те, у которых оно равное, сортируются по алфавиту:

"Vit", "WoWa", "arilou", "mr.DUDA", "chipset", "Exception"

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

 

Ещё предусмотрен оператор GroupBy, позволяющий группировать элементы перечисления по какому-либо признаку. Оператор GroupBy возвращает последовательность элементов Grouping, по одному для каждого уникального значения ключа. Впрочем, это проще показать на примере:

Код C# 3.0

string[] names = { "Albert", "Burke", "Connor", "David",

"Everett", "Frank", "George", "Harris"};

 

//группируем по количеству знаков

var grouping = names.GroupBy(s => s.Length);

 

foreach (Grouping<int, string> group in grouping) { //для каждой группы

Console.WriteLine("Строки длины {0}", group.Key); //пишем кол-во символов

 

foreach (string value in group.Group) //для каждого значения в группе

Console.WriteLine(" {0}", value); //выписываем его

}

Этот код выводит на консоль

 

Строки длины 6

Albert

Connor

George

Harris

Строки длины 5

Burke

David

Frank

Строки длины 7

Everett

Сам интерфейс класса Grouping выглядит так:

Код C# 3.0

public sealed class Grouping<K, T> {

public Grouping(K key, IEnumerable<T> group);

public Grouping();

public K Key { get; set; }

public IEnumerable<T> Group { set; get; }

}

Код VB.NET 9.0

Public NotInheritable Class Grouping(Of K, T)

Public Sub New (key As K, group As IEnumerable(Of T))

Public Sub New()

Public Property Key As K

Public Property Group As IEnumerable(Of T)

End Class

Как и Select, оператор GroupBy позволяет указать функцию, показывающую, какие значения следует включить в группу:

 

Код C# 3.0

string[] names = { "Albert", "Burke", "Connor", "David",

"Everett", "Frank", "George", "Harris"};

 

var grouping = names.GroupBy(s => s.Length, //группируем по количеству знаков

s => s[0]); //отбираем только первые символы

foreach (Grouping<int, char> group in grouping) {

Console.WriteLine("Строки длины {0}", group.Key);

 

foreach (char value in group.Group)

Console.WriteLine(" {0}", value);

}

Этот код, как и следовало ожидать, выводит только первые символы:

Строки длины 6

A

C

G

H

Строки длины 5

B

D

F

Строки длины 7

E

Аггрегирующие операторы

Наверняка, многие где-то слышали это слово. Как Вы помните, в SQL были выражения COUNT, SUM и т.п., которые возвращают не много строк, а только одно значение. Неудивительно, что эти операторы перекочевали и в LINQ.

Для начала мы рассмотрим новый оператор Fold. Вот как он выглядит:

Код C# 3.0

public static U Fold<T, U>(this IEnumerable<T> source,

U seed, Func<U, T, U> func) {

U result = seed;

 

foreach (T element in source)

result = func(result, element);

 

return result;

}

Наверное, это самый интересный аггрегирующий оператор. Суть его действия заключается в том, что при переборе он каждый раз выполняет λ-выражение, передавая в него предыдущий результат, так что получается эдакая «копилка». На самом деле это нетрудно понять из примера:

 

Код C# 3.0

string[] names = { "Albert", "Burke", "Connor", "David",

"Everett", "Frank", "George", "Harris"}; //имена

 

int count = names.Fold(0, //первоначальное значение

(counter, str) => counter + str.Length); //его изменение

// count == 46 (суммарное количество букв во всех словах)

 

В λ-выражение передаются два параметра:

· counter – это текущее значение

· str – это текущий элемент

Выражение возвращает результирующее значение counter, которое будет передано функции в следующий раз (или возвращено по окончании перебора).

Также, разумеется, определены операторы Min, Max, Sum, и Average, работающие только с числовыми типами. Тут всё предельно просто:

Код C# 3.0

int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

string[] names = {"Albert", "Burke", "Connor", "David",

"Everett", "Frank", "George", "Harris"};

 

int totalNumbers = numbers.Sum(); // totalNumbers == 55

int totalNamesLengths = names.Sum(s => s.Length); // totalNamesLengths == 46

Код VB.NET 9.0

Dim numbers As Integer() = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Dim names As String() = {"Albert", "Burke", "Connor", "David", _

"Everett", "Frank", "George", "Harris"}

Dim totalNumbers As Integer = numbers.Sum()

 

Кстати, подсчёт totalNamesLengths здесь аналогичен использованию Fold в предыдущем примере.

Select vs. SelectMany

Оператор Select позволяет получать одно значение из исходного. Если необходимо получать несколько значений (к примеру, массив), необходимо прибегать к использованию промежуточной последовательности, что не очень удобно:

Код C# 3.0

string[] text = { "Albert was here",

"Burke slept late",

"Connor is happy" };

 

var tokens = text.Select(s => s.Split(' ')); //получаем последовательность массивов

 

foreach (string[] line in tokens) //для каждого массива слов

foreach (string token in line) //для каждого слова в массиве

Console.Write("{0}.", token); //вывести его с точкой на конце

Эта программа выводит на консоль следующий текст:

Albert.was.here.Burke.slept.late.Connor.is.happy.

В отличие от оператора Select, оператор SelectMany позволяет сразу запихнуть всё в одну последовательность:

string[] text = { "Albert was here",

"Burke slept late",

"Connor is happy" };

 

var tokens = text.SelectMany(s => s.Split(' ')); //собираем все слова в var

 

foreach (string token in tokens) //перечисляем их

Console.Write("{0}.", token);

Синтаксис запросов

Как я уже говорил, синтаксис запросов (query syntax) упрощает использование наиболее распространённых операторов запросов: Where, Select, SelectMany, GroupBy, OrderBy, ThenBy, OrderByDescending, и ThenByDescending.

Давайте вновь взглянем на приведённый мной пример в начале:

 

Код C# 3.0

IEnumerable<string> coolForum = forums

.Where(f => f.Length == 7)

.OrderBy(f => f)

.Select(f => f.ToUpper());

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

Код C# 3.0

IEnumerable<string> coolForum = from forum in forums

where forum.Length == 7

orderby forum

select forum.ToUpper();

Код VB.NET 9.0

Dim coolForum As IEnumerable(Of String) = _

Select forum.ToUpper() _

From forum In forums _

Where forum.Length = 7 _

Order By forum

Давайте взглянем на структуру выражения в C#. Выражение в C# начинается с первого выражения from...in, затем идут остальные выражения from...in, where, затем идёт выражение orderby с возможностью добавления ключевых слов ascending или descending (по умолчанию используется ascending). Ну и завершает наш список операторы selectлибоgroup...by.

Общая (правда, несколько упрощённая) схема выглядит примерно так:

from itemName in srcExpr

((from itemName in srcExpr) | (where predExpr))*

(orderby (keyExpr (ascending|descending)?)+)?

((select selExpr) | (group selExpr by keyExpr))

Вот пример двух выражений:

Код C# 3.0

var query1 = from p in people

where p.Age > 20

orderby p.Age descending, p.Name

select new {

p.Name, Senior = p.Age > 30, p.CanCode

};

 

var query2 = from p in people

where p.Age > 20

orderby p.Age descending, p.Name

group new {

p.Name, Senior = p.Age > 30, p.CanCode

} by p.CanCode;

Компилятор преобразует их вот во что:

var query1 = people.Where(p => p.Age > 20)

.OrderByDescending(p => p.Age)

.ThenBy(p => p.Name)

.Select(p => new {

p.Name,

Senior = p.Age > 30,

p.CanCode

});

 

var query2 = people.Where(p => p.Age > 20)

.OrderByDescending(p => p.Age)

.ThenBy(p => p.Name)

.GroupBy(p => p.CanCode,

p => new {

p.Name,

Senior = p.Age > 30,

p.CanCode

});

Мы также можем использовать сразу несколько источников отбора:

 

 

Код C# 3.0

var query = from s1 in names where s1.Length == 5

from s2 in names where s1 == s2

select s1 + " " + s2;

Код VB.NET 9.0

Dim query = Select It.s1 + " " + It.s2 _

From s1 In names, s2 In names _

Where It.s1.Length = 5 And It.s1 = It.s2

 

Как видно, синтаксис VB.NET несколько отличается: при объявлении нескольких источников данных в конструкции From...In необходимо обращаться к ним через ключевое слово It, а также все источники отбора разделять запятыми в одной конструкции From...In (в C# они просто перечисляются). Также в VB.NET все условия должны находиться в одонй конструкции Where.

Если запустить код, объявив массив names вот так:

Код C# 3.0

string[] names = {"Burke", "Connor", "Frank", "Everett",

"Albert", "George", "Harris", "David"};

Код VB.NET 9.0

Dim names() As String = {"Burke", "Connor", "Frank", "Everett", _

"Albert", "George", "Harris", "David"}

, мы получим следующие результаты:

Burke Burke

Frank Frank

David David

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

Код C# 3.0

var query = names.Where(s1 => s1.Length == 5)

.SelectMany(s1 =>

names.Where(s2 => s1 == s2)

.Select(s2 => s1 + " " + s2)

);

Заметьте, что благодаря использованию SelectMany мы сразу получаем строки, а не массивы строк.

Иногда бывает нужным проводить с группой, полученной с помощью group...by, некоторые дополнительные операции, например осуществить сортировку по значению ключа в обратном порядке. Чтобы сделать это, нам пришлось бы писать такой код:

Код C# 3.0

var query = from item in names

orderby item.Length descending, item

group item by item.Length;

Специально для таких целей существует ключевое слово into, позволяющее работать с группами, используя операторы orderby и select:

Код C# 3.0

var query = from item in names

orderby item

group item by item.Length into lengthGroups

orderby lengthGroups.Key descending

select lengthGroups;

Оба способа выводят одно и то же, но второй чуть более логичный (хотя и более длинный):

Strings of length 7

Everett

Strings of length 6

Albert

Connor

George

Harris

Strings of length 5

Burke

David

Frank

Только что мы обсудили, как C# и VB.NET реализуют так называемый LINQ pattern, способность языка предоставлять более удобные синтаксические конструкции для осуществления запросов. Сейчас мы вкратце рассмотрим два потомка LINQ – DLinq и XLinq, которые послужат темой моего следующего обзора.

Интеграция с SQL

.NET Language Integrated Query позволяет использовать синтаксические конструкции, схожие с SQL, в обычных выражениях языка. Было бы крайне странно, если бы эта возможность не использовалась по своему прямому назначению – при работе с базами данных. Её плюсы очевидны: проверка типов при компиляции, IntelliSense... Сейчас мы рассмотрим, как же это реализуется.

DLinq определяет два главных атрибута, [Table] и [Column], которые показывают нам, что соответствующий тип и его свойства (поля) отражают структуру базы данных.

Давайте вообразим, что форумы Vingrad, RSDN и GotDotNet решили объединиться и создать единую базу данных участников. Весьма упрощённо её структура выглядит так:

Код SQL

CREATE TABLE Forums(

[Name] nvarchar(30) NOT NULL,

URL nvarchar(50) NOT NULL,

ForumID uniqueidentifier ROWGUIDCOL NOT NULL,

)

 

CREATE TABLE Users(

ForumID uniqueidentifier NOT NULL,

[Login] nvarchar(15) NOT NULL,

RealName nvarchar(50) NULL,

RoleID uniqueidentifier NOT NULL,

UserID uniqueidentifier ROWGUIDCOL NOT NULL

)

 

 

CREATE TABLE Roles(

RoleID uniqueidentifier ROWGUIDCOL NOT NULL,

RoleName nvarchar(15) NOT NULL

)

Соответствующие классы упрощённо (очень упрощённо:)) выглядят так:

Код C# 3.0

[Table(Name = "Roles")]

public partial class Role

{

 

[Column(DbType = "UniqueIdentifier NOT NULL", Id = true)]

public System.Guid RoleID;

 

[Column(DbType = "NVarChar(15) NOT NULL")]

public string RoleName;

}

[Table(Name = "Users")]

public partial class User

{

[Column(DbType = "UniqueIdentifier NOT NULL")]

public System.Guid ForumID;

 

[Column(DbType = "NVarChar(15) NOT NULL")]

public string Login;

 

[Column(DbType = "NVarChar(50)")]

public string RealName;

 

[Column(DbType = "UniqueIdentifier NOT NULL")]

public System.Guid RoleID;

 

[Column(DbType = "UniqueIdentifier NOT NULL", Id = true)]

public System.Guid UserID;

}

[Table(Name = "Forums")]

public partial class Forum

{

[Column(DbType = "NVarChar(30) NOT NULL")]

public string Name;

 

[Column(DbType = "NVarChar(50) NOT NULL")]

public string URL;

 

[Column(DbType = "UniqueIdentifier NOT NULL", Id = true)]

public System.Guid ForumID;

}

 

 

Код VB.NET 9.0

<Table(Name:="Roles")> _

Public Class Role

 

<Column(DbType:="UniqueIdentifier NOT NULL", Id:=True)> _

Public RoleID As System.Guid

 

<Column(DbType:="NVarChar(15) NOT NULL")> _

Public RoleName As String

 

End Class

 

<Table(Name:="Users")> _

Public Class User

<Column(DbType:="UniqueIdentifier NOT NULL")> _

Public ForumID As System.Guid

 

<Column(DbType:="NVarChar(15) NOT NULL")> _

Public Login As String

 

<Column(DbType:="NVarChar(50)")> _

Public RealName As String

 

<Column(DbType:="UniqueIdentifier NOT NULL")> _

Public RoleID As System.Guid

 

<Column(DbType:="UniqueIdentifier NOT NULL", Id:=True)> _

Public UserID As System.Guid

 

End Class

 

<Table(Name:="Forums")> _

Public Class Forum

 

<Column(DbType:="NVarChar(30) NOT NULL")> _

Public Name As String

 

<Column(DbType:="NVarChar(50) NOT NULL")> _

Public URL As String

 

<Column(DbType:="UniqueIdentifier NOT NULL", Id:=True)> _

Public ForumID As System.Guid

 

End Class

Как видно, если тип CLR не полностью соответствует типу БД, он прописывается в DbType.

Собственно говоря, для того, чтобы преобразовать структуру БД в эквивалентный класс, лучше использовать специально созданную для этого утилиту SQLMetal (для SQL Server):

Мы её запустим с такими параметрами:

SqlMetal /server:DANICH\SQL2005 /database:DLinqTest /user:sa /password:********** /code:e:\dlinq.cs /language:csharp

 

SqlMetal /server:DANICH\SQL2005 /database:DLinqTest /user:sa /password:********** /code:e:\dlinq.vb /language:vb

В итоге у нас получатся два файла: dlinq.vb и dlinq.cs. К сожалению, детальное рассмотрение их содержания выходит за рамки данного обзора, но, будьте уверены, мы их рассмотрим в обзоре DLinq, который грядёт:)

Сейчас я кратко покажу их структуру, упустив многие важные, но более сложные конструкции.

Для начала рассмотрим простенький класс DLinqTest:

Код C# 3.0

public partial class DLinqTest: DataContext {

 

public Table<Role> Roles;

 

public Table<User> Users;

 

public Table<Forum> Forums;

 

public DLinqTest() {

}

 

public DLinqTest(string connection):

base(connection) {

}

 

public DLinqTest(System.Data.IDbConnection connection):

base(connection) {

}

}

 

Код VB.NET 9.0

Partial Public Class DLinqTest

Inherits DataContext

 

Public Roles As Table(Of Role)

 

Public Users As Table(Of User)

 

Public Forums As Table(Of Forum)

 

Public Sub New()

MyBase.New()

End Sub

 

Public Sub New(ByVal connection As String)

MyBase.New(connection)

End Sub

 

Public Sub New(ByVal connection As System.Data.IDbConnection)

MyBase.New(connection)

End Sub

End Class

Как видно, сначала определяется главный класс, наследующий от DataContext. Зачем он нужен?

Для того, чтобы получить таблицу Users, нам пришлось бы писать

Код C# 3.0

DataContext db = new DataContext(

connection); //connection – раннее созданное соединение с БД

Table<User> users = db.GetTable<User>(); //получаем таблицу типу User

Код VB.NET 9.0

Dim db As New DataContext(connection) 'connection – раннее созданное соединение с БД

Dim users As Table(Of User) = db.GetTable(Of User)() 'получаем таблицу по типу User

Благодаря класу DLinqTest мы можем просто написать

Код C# 3.0

DLinqTest db = new DLinqTest (

connection); //connection – раннее созданное соединение с БД

Table<User> users = db.Users; //берём таблицу из свойства

Код VB.NET 9.0

Dim db As New DLinqTest(connection) 'connection – раннее созданное соединение с БД

Dim users As Table(Of User) = db.Users ' берём таблицу из свойства

Вот пример кода, который работает с классами, сгенерированными SQLMetal:

 

Код C# 3.0

class Program

{

static void Main()

{

SqlConnection connection = new SqlConnection

(@"server=DANICH\SQL2005;uid=sa;pwd=**********;database=DLinqTest;");

DLinqTest db = new DLinqTest(connection);

var moderators = from u in db.Users

where u.Role.RoleName == "Moderator"

select new {Login = u.Login,

RealName = u.RealName,

ForumName = u.Forum.Name};

foreach (var m in moderators)

Console.WriteLine("{0}, {1}, {2};", m.Login, m.RealName, m.ForumName);

Console.ReadKey();

}

}

Код VB.NET 9.0

Module Program

 

Sub Main()

Dim connection As New SqlConnection _

("server=DANICH\SQL2005;uid=sa;pwd=**********;database=DLinqTest;")

Dim db As New DLinqTest(connection)

Dim moderators = Select New {Login:= u.Login, _

RealName:= u.RealName, _

ForumName:= u.Forum.Name} _

From u In db.Users _

Where u.Role.RoleName = "Moderator"

For Each m In moderators

Console.WriteLine("{0}, {1}, {2};", m.Login, m.RealName, m.ForumName)

Next

Console.ReadKey()

End Sub

End Module

Ещё один плюс такого подхода – поскольку DataContext в конструкторе принимает соединение в виде IDbConnection, то он может работать с любыми совместимыми с T-SQL базами данных.

Во время перебора элементов наш запрос транслируется из деревьев выражений в SQL-запрос.

Интеграция с XML

Параллельно с DLinq разрабатывалась технология для доступа через запросы к данным в виде XML, которая была названа XLinq. Эта технология предоставляет доступ к таким функциям языка XML, как XPath, XQuery. XLinq призвана упростить доступ к информации в виде XML. В основном в XLinq используются три главных класса: XName, XElement и XAttribute, представляющие, соответственно, имя XML-узла, узел (node) XML-документа и атрибут узла.

Как можно заметить, конструктор XElement принимает два параметра – XName и «внутренности» элемента, выраженные object’ом. Обычно вместо XName передают просто строку с именем элемента, которая автоматически в него преобразуется. Далее можно передать любой объект (будет вызван метод ToString()), Xelement (дочерний узел) или XAttribute (он превратится в атрибут узла), причём в любом количестве, т.к. второй параметр объявлен как ParamArray aka args. Давайте взглянем на простенький примерчик.

 

 

Код C# 3.0

var xml = new XElement("Forum",

new XAttribute("Name", "Vingrad"),

new XAttribute("URL", " https://forum.vingrad.ru "),

new XElement("Members",

new XElement("Member",

new XAttribute("Role","Moderator"),

"Exception"),

new XElement("Member",

new XAttribute("Role","Admin"),

"WoWa"),

new XElement("Member",

new XAttribute("Role","Expert"),

"arilou")

)

);

Console.WriteLine(xml);

 

 

Код VB.NET 9.0

Dim xml = New XElement("Forum", _

New XAttribute("Name", "Vingrad"), _

New XAttribute("URL", " https://forum.vingrad.ru "), _

New XElement("Members", _

New XElement("Member", _

New XAttribute("Role", "Moderator"), _

"Exception"), _

New XElement("Member", _

New XAttribute("Role", "Admin"), _

"WoWa"), _

New XElement("Member", _

New XAttribute("Role", "Expert"), _

"arilou") _

))

Console.WriteLine(xml)

Этот код выводит вот что:

Код XML

<Forum Name="Vingrad" URL=" https://forum.vingrad.ru ">

<Members>

<Member Role="Moderator">Exception</Member>

<Member Role="Admin">WoWa</Member>

<Member Role="Expert">arilou</Member>

</Members>

</Forum>

Кстати, в VB.NET 9.0 есть и другая возможность написания такого кода, называемая XML literals. Суть её сводится к тому, что XML-выражения интегрированы в язык и могут присваиваться переменным. На самом деле компилятор анализирует их и приводит к виду XElement. Например, пример выше мы можем переписать так:

Код VB.NET 9.0

Dim xml = _

<Forum Name="Vingrad" URL=" https://forum.vingrad.ru ">

<Members>

<Member Role="Moderator">Exception</Member>

<Member Role="Admin">WoWa</Member>

<Member Role="Expert">arilou</Member>

</Members>

</Forum>

Это выглядит нагляднее, не так ли:) Поэтому в примерах для VB.NET 9.0 я буду использовать именно такой стиль. Кстати, переносы строк (_) использовать в XML-литералах не нужно.

Мы также можем загрузить содержимое в XElement из файла, строки или XmlReader’а, но сейчас это нас не особо интересует.

Давайте рассмотрим простейший запрос.

 

 

Код C# 3.0

var xmlItems = from forum in forums

where forum.URL == " https://forum.vingrad.ru "

select new XElement("CoolForumAdmin",

new XAttribute("Forum",forum.Name),

forum.Admin

);

foreach(var anXml in xmlItems) Console.WriteLine(anXml);

Код VB.NET 9.0

Dim xmlItems = Select <CoolForumAdmin Forum=<%= Forum.Name %>>

<%= Forum.Admin %>

</CoolForumAdmin> _

From forum In forums _

Where Forum.URL = " https://forum.vingrad.ru "

For Each anXml In xmlItems

Console.WriteLine(anXml)

Next

Этот код нам выдаст

Код XML

<СoolForumAdmin Forum="Vingrad">WoWa</СoolForumAdmin>

Вместо перебора элементов xmlItems можно просто «обернуть» результат запроса:

Код C# 3.0

var xmlItem = new XElement("Forums",

from forum in forums

where forum.URL == " https://forum.vingrad.ru "

select new XElement("CoolForumAdmin",

new XAttribute("Forum",forum.Name),

forum.Admin

));

Код VB.NET 9.0

Dim xmlItem = <ForumInfo>

<%= Select <CoolForumAdmin Forum=<%= Forum.Name %>>

<%=forum.Admin %>

</CoolForumAdmin> _

From forum In forums _

Where Forum.URL = " https://forum.vingrad.ru " %>

</ForumInfo>

Код XML

<ForumInfo>

<СoolForumAdmin Forum="Vingrad">WoWa</СoolForumAdmin>

</ForumInfo>

 

Только что мы использовали запросы для составления XML-выражений. Их также можно использовать и для их «распаковки». Допустим, у нас есть следующая XML-структура:

Код XML

<ForumInfo>

<Forum Name="Vingrad" URL="https://forum.vingrad.ru">

<Admin>WoWa</Admin>

<MembersCount>17000</MembersCount>

</Forum>

</ForumInfo>

Следующий код создаст массив объектов Forum из неё:

Код C# 3.0

var xmlForumInfo = XElement.Parse(

@"<ForumInfo>

<Forum Name=""Vingrad"" URL=""https://forum.vingrad.ru"">

<Admin>WoWa</Admin>

<MembersCount>17000</MembersCount>

</Forum>

</ForumInfo>");

var forum = from f in xmlForumInfo.Descendants("Forum")

select new Forum {Name =(string)f.Attribute("Name"),

URL = (string)f.Attribute("URL"),

Admin = (string)f.Element("Admin"),

MembersCount = (int)f.Element("MembersCount")};

 

Код VB.NET 9.0

Dim xmlForumInfo = _

<ForumInfo>

<Forum Name="Vingrad" URL="https://forum.vingrad.ru">

<Admin>WoWa</Admin>

<MembersCount>17000</MembersCount>

</Forum>

</ForumInfo>

Dim forum = Select New Forum {Name:= f.@Name.Value, _

URL:= f.@URL.Value, _

Admin:= f.Admin(0).Value, _

MembersCount:= f.MembersCount(0).Value} _

From f In xmlForumInfo.Forum

VB.NET 9.0 опять-таки предоставляет более простой синтаксис с доступом к атрибутам через знак @, к дочерним элементам через простой синтаксис с точкой, а к элементам, расположенным «где-то глубоко» (дочерними дочерных и т.п.:)) через три точки (или через метод Descendants). Минус такого подхода – приходится выключать Option Strict, что есть зло. К счастью, в недрах MS готовится некий аналог SQLMetal, который создаёт «типизированные» XElement’ы по схемам.

В C# же используются определённые XAttribute’ом и XElement’ом операторы приведения типа, которые возвращают нужное значение. Эти операторы определены для типов string, bool, bool?, int, int?, uint, uint?, long, long?, ulong, ulong?, float, float?, double, double?, decimal, decimal?, DateTime, DateTime?, TimeSpan, TimeSpan?, GUID и GUID?.

Эпилог

Честно говоря, при переводе я сразу удалил эту часть статьи, ибо она была совсем неинтересная. Вместо этого, пожалуй, я напишу кое-что своё. Для начала хочу сказать, что при написании статьи использовалась статья The LINQ Project Overview (которая и послужила placeholder’ом для этой). Почти все примеры в статье мои, как и почти весь текст (признаюсь, чуть-чуть было и простого перевода). Не знаю, какое впечатление у Вас сложилось о LINQ, но я надеюсь, что хорошее (это было целью написания статьи). Наверняка многие захотят поставить себе LINQ Preview прямо сейчас. Что для этого нужно сделать?

  1. Зайдите на https://msdn.microsoft.com/vbasic/future или https://msdn.microsoft.com/vcsharp/future
  2. Скачайте последний LINQ Technology Preview (под надписью Downloads)
  3. Естественно, его надо установить:)
  4. Если Вы ставите LINQ для C#, нужно ещё установить поддержку для VS 2005 (в VB она ставится автоматически), процедура установки описана в readme.html
  5. Дерзайте:)!

На данный момент существует достаточно недоделок и исправлений, но всё это постоянно совершенствуется. Кстати, чтобы добавить функциональность LINQ, необходимо подключить библиотеки System.Query.dll (если надо - System.Xml.XLinq.dll и System.Data.DLinq.dll), которые лежат в LINQ Preview\Bin и добавить импорт на них. Также можно воспользоваться шаблоном LINQ Console/Windows Application.

Далее я приведу табличку со стандартными операторами запросов и их функциями:

OfType Filter based on type affiliation
Select/SelectMany Project based on transform function
Where Filter based on predicate function
Count Count based on optional predicate function
All/Any Universal/Existential quantification based on predicate function
First/FirstOrDefault Access initial member based on optional predicate function
ElementAt Access member at specified position
Take/Skip Access members before/after specified position
TakeWhile/SkipUntil Access members before/after predicate function is satisfied
GroupBy Partition based on key extraction function
ToDictionary Create key/value dictionary based on key extraction function
OrderBy/ThenBy Sort in ascending order based on key extraction function and optional comparison function
OrderByDescending/ ThenByDescending Sort in descending order based on key extraction function and optional comparison function
Reverse Reverse the order of a sequence
Fold Aggregate value over multiple values based on aggregation function
Min/Max/Sum/Average Numeric aggregation functions
Distinct Filter duplicate members
Except Filter elements that are members of specified set
Intersect Filter elements that are not members of specified set
Union Combine distinct members from two sets
Concat Concatenate the values of two sequences
ToArray/ToList Buffer results of query in array or List<T>
Range Create a sequence of numbers in a range
Repeat Create a sequence of multiple copies of a given value

 

И уж совсем напоследок хочу привести свободно распространяемый исходный код классов пространства имён System.Query. Попытайтесь в нём разобраться:)))

 

Код C# 3.0

// Copyright (c) Microsoft Corporation. All rights reserved.

using System;

using System.Collections;

using System.Collections.Generic;

 

namespace System.Query

{

public delegate T Func<T>();

public delegate T Func<A0, T>(A0 arg0);

public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);

public delegate T Func<A0, A1, A2, T>(A0 arg0, A1 arg1, A2 arg2);

public delegate T Func<A0, A1, A2, A3, T>(A0 arg0, A1 arg1, A2 arg2, A3 arg3);

 

public sealed class EmptySequenceException: Exception { }

 

public static class Sequence

{

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate) {

foreach (T element in source) {

if (predicate(element)) yield return element;

}

}

 

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, int, bool> predicate) {

int index = 0;

foreach (T element in source) {

if (predicate(element, index)) yield return element;

index++;

}

}

 

public static IEnumerable<S> Select<T, S>(this IEnumerable<T> source, Func<T, S> selector) {

foreach (T element in source) {

yield return selector(element);

}

}

 

public static IEnumerable<S> Select<T, S>(this IEnumerable<T> source, Func<T, int, S> selector) {

int index = 0;

foreach (T element in source) {

yield return selector(element, index);

index++;

}

}

 

public static IEnumerable<S> SelectMany<T, S>(this IEnumerable<T> source, Func<T, IEnumerable<S>> selector) {

foreach (T element in source) {

foreach (S subElement in selector(element)) {

yield return subElement;

}

}

}

 

public static IEnumerable<S> SelectMany<T, S>(this IEnumerable<T> source, Func<T, int, IEnumerable<S>> selector) {

int index = 0;

foreach (T element in source) {

foreach (S subElement in selector(element, index)) {

yield return subElement;

}

index++;

}

}

 

public static IEnumerable<T> Take<T>(this IEnumerable<T> source, int count) {

if (count > 0) {

foreach (T element in source) {

yield return element;

if (--count == 0) break;

}

}

}

 

public static IEnumerable<T> TakeWhile<T>(this IEnumerable<T> source, Func<T, bool> predicate) {

foreach (T element in source) {

if (!predicate(element)) break;

yield return element;

}

}

 

public static IEnumerable<T> TakeWhile<T>(this IEnumerable<T> source, Func<T, int, bool> predicate) {

int index = 0;

foreach (T element in source) {

if (!predicate(element, index)) break;

yield return element;

index++;

}

}

 

public static IEnumerable<T> Skip<T>(this IEnumerable<T> source, int count) {

using (IEnumerator<T> e = source.GetEnumerator()) {

while (count > 0 && e.MoveNext()) count--;

if (count <= 0) {

while (e.MoveNext()) yield return e.Current;

}

}

}

 

public static IEnumerable<T> SkipWhile<T>(this IEnumerable<T> source, Func<T, bool> predicate) {

bool yielding = false;

foreach (T element in source) {

if (!yielding &&!predicate(element)) yielding = true;

if (yielding) yield return element;

}

}

 

public static IEnumerable<T> SkipWhile<T>(this IEnumerable<T> source, Func<T, int, bool> predicate) {

int index = 0;

bool yielding = false;

foreach (T element in source) {

if (!yielding &&!predicate(element, index)) yielding = true;

if (yielding) yield return element;

index++;

}

}

 

public static OrderedSequence<T> OrderBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) {

return new OrderedSequence<T, K>(source, null, keySelector, null, false);

}

 

public static OrderedSequence<T> OrderBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K> comparer) {

return new OrderedSequence<T, K>(source, null, keySelector, comparer, false);

}

 

public static OrderedSequence<T> OrderByDescending<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) {

return new OrderedSequence<T, K>(source, null, keySelector, null, true);

}

 

public static OrderedSequence<T> OrderByDescending<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K> comparer) {

return new OrderedSequence<T, K>(source, null, keySelector, comparer, true);

}

 

public static OrderedSequence<T> ThenBy<T, K>(this OrderedSequence<T> source, Func<T, K> keySelector) {

return new OrderedSequence<T, K>(source.source, source, keySelector, null, false);

}

 

public static OrderedSequence<T> ThenBy<T, K>(this OrderedSequence<T> source, Func<T, K> keySelector, IComparer<K> comparer) {

return new OrderedSequence<T, K>(source.source, source, keySelector, comparer, false);

}

 

public static OrderedSequence<T> ThenByDescending<T, K>(this OrderedSequence<T> source, Func<T, K> keySelector) {

return new OrderedSequence<T, K>(source.source, source, keySelector, null, true);

}

 

public static OrderedSequence<T> ThenByDescending<T, K>(this OrderedSequence<T> source, Func<T, K> keySelector, IComparer<K> comparer) {

return new OrderedSequence<T, K>(source.source, source, keySelector, comparer, true);

}

 

public static IEnumerable<Grouping<K, T>> GroupBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) {

return GroupBy(source, keySelector, null);

}

 

public static IEnumerable<Grouping<K, T>> GroupBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IEqualityComparer<K> comparer) {

Dictionary<K, List<T>> dict = new Dictionary<K, List<T>>(comparer);

foreach (T element in source) {

K key = keySelector(element);

List<T> list;

if (!dict.TryGetValue(key, out list)) {

list = new List<T>();

dict.Add(key, list);

}

list.Add(element);

}

foreach (KeyValuePair<K, List<T>> pair in dict) {

yield return new Grouping<K, T>(pair.Key, pair.Value);

}

}

 

public static IEnumerable<Grouping<K, E>> GroupBy<T, K, E>(this IEnumerable<T> source, Func<T, K> keySelector, Func<T, E> elemSelector) {

return GroupBy(source, keySelector, elemSelector, null);

}

 

public static IEnumerable<Grouping<K, E>> GroupBy<T, K, E>(this IEnumerable<T> source, Func<T, K> keySelector, Func<T,E> elemSelector, IEqualityComparer<K> comparer) {

Dictionary<K, List<E>> dict = new Dictionary<K, List<E>>(comparer);

foreach (T element in source) {

K key = keySelector(element);

E elem = elemSelector(element);

List<E> list;

if (!dict.TryGetValue(key, out list)) {

list = new List<E>();

dict.Add(key, list);

}

list.Add(elem);

}

foreach (KeyValuePair<K, List<E>> pair in dict) {

yield return new Grouping<K, E>(pair.Key, pair.Value);

}

}

 

public static IEnumerable<T> Concat<T>(this IEnumerable<T> first, IEnumerable<T> second) {

foreach (T element in first) yield return element;

foreach (T element in second) yield return element;

}

 

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source) {

Dictionary<T, object> dict = new Dictionary<T, object>();

foreach (T element in source) {

if (!dict.ContainsKey(element)) {

dict.Add(element, null);

yield return element;

}

}

}

 

public static IEnumerable<T> Union<T>(this IEnumerable<T> first, IEnumerable<T> second) {

Dictionary<T, object> dict = new Dictionary<T, object>();

foreach (T element in first) {

if (!dict.ContainsKey(element)) {

dict.Add(element, null);

yield return element;

}

}

foreach (T element in second) {

if (!dict.ContainsKey(element)) {

dict.Add(element, null);

yield return element;

}

}

}

 

public static IEnumerable<T> Intersect<T>(this IEnumerable<T> first, IEnumerable<T> second) {

Dictionary<T, object> dict = new Dictionary<T, object>();

foreach (T element in first) dict[element] = null;

foreach (T element in second) {

if (dict.ContainsKey(element)) dict[element] = dict;

}

foreach (KeyValuePair<T, object> pair in dict) {

if (pair.Value!= null) yield return pair.Key;

}

}

 

public static IEnumerable<T> Except<T>(this IEnumerable<T> first, IEnumerable<T> second) {

Dictionary<T, object> dict = new Dictionary<T, object>();

foreach (T element in first) dict[element] = null;

foreach (T element in second) dict.Remove(element);

foreach (T element in dict.Keys) yield return element;

}

 

public static IEnumerable<T> Reverse<T>(this IEnumerable<T> source) {

Buffer<T> buffer = new Buffer<T>(source);

for (int i = buffer.count - 1; i >= 0; i--) yield return buffer.items[i];

}

 

public static bool EqualAll<T>(this IEnumerable<T> first, IEnumerable<T> second) {

using (IEnumerator<T> e1 = first.GetEnumerator())

using (IEnumerator<T> e2 = second.GetEnumerator()) {

while (e1.MoveNext()) {

if (!(e2.MoveNext() && Equals(e1.Current, e2.Current))) return false;

}

if (e2.MoveNext()) return false;

}

return true;

}

 

public static T[] ToArray<T>(this IEnumerable<T> source) {

return new Buffer<T>(source).ToArray();

}

 

public static List<T> ToList<T>(this IEnumerable<T> source) {

return new List<T>(source);

}

 

public static Dictionary<K, T> ToDictionary<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) {

Dictionary<K, T> d = new Dictionary<K, T>();

foreach (T element in source) d.Add(keySelector(element), element);

return d;

}

 

public static Dictionary<K, E> ToDictionary<T, K, E>(this IEnumerable<T> source, Func<T, K> keySelector, Func<T, E> elemSelector) {

Dictionary<K, E> d = new Dictionary<K, E>();

foreach (T element in source) d.Add(keySelector(element), elemSelector(element));

return d;

}

 

public static IEnumerable<T> OfType<T>(this IEnumerable source) {

foreach (object obj in source) {

if (obj is T) yield return (T)obj;

}

}

 

public static IEnumerable<T> ToSequence<T>(this IEnumerable<T> seq) {

return seq;

}

 

public static T First<T>(this IEnumerable<T> source) {

foreach (T element in source) {

return element;

}

throw new EmptySequenceException();

}

 

public static T First<T>(this IEnumerable<T> source, Func<T, bool> predicate) {

foreach (T element in source) {

if (predicate(element)) return element;

}

throw new EmptySequenceException();

}

 

public static T First<T>(this IEnumerable<T> source, Func<T, int, bool> predicate) {

int index = 0;

foreach (T element in source) {

if (predicate(element, index)) return element;

index++;

}

throw new EmptySequenceException();

}

 

public static T FirstOrDefault<T>(this IEnumerable<T> source) {

foreach (T element in source) {

return element;

}

return default(T);

}

 

public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate) {

foreach (T element in source) {

if (predicate(element)) return element;

}

return default(T);

}

 

public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, int, bool> predicate) {

int index = 0;

foreach (T element in source) {

if (predicate(element, index)) return element;

index++;

}

return default(T);

}

 

public static T ElementAt<T>(this IEnumerable<T> source, int index) {

IList<T> list = source as IList<T>;

if (list!= null) return list[index];

if (index < 0) throw new ArgumentException();

using (IEnumerator<T> e = source.GetEnumerator()) {

while (true) {

if (!e.MoveNext()) throw new ArgumentException();

if (index == 0) return e.Current;

index--;

}

}

}

 

public static IEnumerable<int> Range(int start, int count) {

if (count < 0) throw new ArgumentException();

for (int i = 0; i < count; i++) yield return start + i;

}

 

public static IEnumerable<T> Repeat<T>(T element, int count) {

if (count < 0) throw new ArgumentException();

for (int i = 0; i < count; i++) yield return element;

}



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-06-26 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: