- •Лабораторная работа 4. Язык linq Задание
- •Синтаксис запроса
- •Синтаксис метода
- •Смешанный синтаксис запроса и метода
- •Пример соединения с использованием простого ключа
- •Пример соединения с использованием составного ключа
- •Пример множественного соединения
- •Пример внутреннего соединения, реализуемого с помощью группового соединения
Синтаксис запроса
Лучше всего для создания запросов использовать синтаксис запроса, создавая выражения запросов. В следующем примере показано три выражения запроса.В первом выражении демонстрируется фильтрация или ограничение результатов путем применения условий в предложении where.Оно возвращает все элементы в исходной последовательности со значениями больше 7 и меньше 3. Второе выражение демонстрирует сортировку возвращаемых результатов. Третий запрос демонстрирует группировку результатов. Он возвращает две группы на основе первой буквы слова.
// Query #1.
List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// The query variable can also be implicitly typed by using var
varfilteringQuery =
from num in numbers
where num < 3 || num > 7
select num;
// Query #2.
varorderingQuery =
from num in numbers
where num < 3 || num > 7
orderbynumascending
select num;
// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" };
varqueryFoodGroups =
from item ingroupingQuery
groupitembyitem[0];
В каждом из приведенных выше примеров фактическое выполнение запроса откладывается до использования переменной запроса в операторе foreach.
Синтаксис метода
Некоторые операции запросов должны быть выражены в виде вызова метода. Чаще всего используются методы, возвращающие одноэлементные числовые значения, например Sum, Max, Min, Average и т.д.Эти методы всегда должны быть вызваны последними в запросе, поскольку они представляют только одно значение и не могут служить источником дополнительных действий запроса. В следующем примере демонстрируется вызов метода в выражении запроса.
List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };
// Query #4.
double average = numbers1.Average();
// Query #5.
var concatenationQuery = numbers1.Concat(numbers2);
Если у метода есть параметры, они представлены в виде лямбда-выражения, как показано в следующем примере.
// Query #6.
var largeNumbersQuery = numbers2.Where(c => c > 15);
Смешанный синтаксис запроса и метода
В этом примере демонстрируется использование синтаксиса метода для результатов предложения запроса. Нужно всего лишь заключить выражение запроса в скобки, а затем применить оператор точки и вызвать метод. В следующем примере запрос #7 возвращает количество чисел, значение которых лежит в диапазоне от 3 до 7.Однако в общем случае лучше использовать вторую переменную для хранения результатов вызова метода. Таким образом, будет меньше вероятность перепутать запрос с результатами запроса.
// Query #7.
// Using a query expression with method syntax
int numCount1 =
(from num in numbers1
where num < 3 || num > 7
select num).Count();
// Better: Create a new variable to store
// the method call result
мar numbersQuery =
from num in numbers1
where num < 3 || num > 7
select num;
int numCount2 = numbersQuery.Count();
Запрос №7 возвращает одиночное значение, а не коллекцию, поэтому он выполняется мгновенно.
Можно использовать синтаксис метода следующим образом.
var numCount = numbers.Where(n => n < 3 || n > 7).Count();
Можно использовать явную типизацию следующим образом.
Int numCount = numbers.Where(n => n < 3 || n > 7).Count();
LINQ для коллекций классов
Создадим класс Customer и коллекцию типа List, содержащую экземпляры Customer.
В классе Customer есть поля, конструктор, методы Get_Region(0 и Get_ID() для возврата значений соответствующих полей и метод Info() для отображения полей экземпляра класса:
class Customer
{public string ID ;
private string City ;
private string Country ;
private string Region ;
private decimal Sales ;
public string Get_Region() { return Region; }
public Customer(string ID, string City, string Country, string Region, decimal Sales)
{this.ID = ID; this.City = City; this.Country = Country;
this.Region = Region; this.Sales = Sales;}
public string Info()
{ return "ID: " + ID + " Город: " + City + " Страна: " + Country +
"Регион: " + Region + " Продажи: " + Convert.ToString(Sales); } }
List<Customer> customers = new List<Customer>();
При загрузке формы добавим к коллекции несколько объектов:
customers.Add(new Customer("A", "New York", "USA","North America", 9999));
customers.Add(new Customer("C", "Karachi", "Pakistan", "Asia", 7777));
customers.Add(new Customer("D", "Delhi", "India", "Asia", 6666));
customers.Add(new Customer("E", "SantaPaulo", "Brazil", "South America", 5555));
customers.Add(new Customer("F", "Moscow", "Russia", "Europe", 4444));
customers.Add(new Customer("G", "Seoul", "Korea", "Asia", 3333));
customers.Add(new Customer("H", "Istanbul", "Turkey", "Asia", 2222));
customers.Add(new Customer("I", "Shanghai", "China", "Asia", 1111));
customers.Add(new Customer("J", "Lagos", "Nigeria", "Africa", 1000));
customers.Add(new Customer("K", "Mexico City", "Mexico", "North America", 1000));
customers.Add(new Customer("LM", "Jakarta", "Indonesia", "Asia", 3000));
customers.Add(new Customer("M", "Tokyo", "Japan", "Asia", 4000));
customers.Add(new Customer("N", "Los Angeles", "USA", "North America", 5000));
customers.Add(new Customer("O", "Cairo", "Egypt", "Africa", 6000));
customers.Add(new Customer("P", "Tehran", "Iran", "Asia", 7000));
customers.Add(new Customer("Q", "London", "UK", "Europe", 8000));
customers.Add(new Customer("R", "Beijing", "China", "Asia", 9000));
customers.Add(new Customer("S", "Bogota", "Colombia", "South America", 1001));
customers.Add(new Customer("T", "Lima", "Peru", "South America", 2002));
На форме поместим кнопку и список listBox для вывода объектов из Азии при нажатии на кнопку (рис. ):
Рис. Форма для вывода части коллекции
Код события нажатия кнопки:
private void button1_Click(object sender, EventArgs e)
{var queryResults = from c in customers where c.Get_Region() == "Asia" select c;
listBox1.Items.Clear();
foreach (Customer d in queryResults)
listBox1.Items.Add(d.Info()); }
Переменная типа var содержит результаты запроса для коллекции List с условием отбора экземпляров из Азии. Цикл foreach добавляет в список ListBox значение метода Info (рис. )
Рис. Форма при выполнении приложения
Для применения нужного числа выражений фильтра в предложении where можно использовать логические операторы C# && и ||.
В предложении where может содержаться один или несколько методов, возвращающих логические значения.
Например,
varqueryEvenNums =from num in numbers where IsEven(num)select num;
// Выполнениезапроса
foreach (vars inqueryEvenNums)
{Console.Write(s.ToString() + " "); } }
// Метод.
staticboolIsEven(inti)
{ return i % 2 == 0; }
Включение в запрос дополнительных полей
В LINQ можно создать новый объект в конструкции select для хранения результатов, которые нужны для запроса.
Модифицируем код нажатия кнопки:
var queryResults =
from c in customers select new { Value = c.Get_Region(), Id = c.Get_ID() };
foreach (var d in queryResults)
{ string v = d.Value;string id = d.Id;
listBox1.Items.Add(v+" ***"+id);}
Выбор уникальных значений
Еще один тип запросов — запрос selectdistinct, в котором выполняется поиск уникальных значений в данных, то есть значений, которые не повторяются. Потребность в этом возникает очень часто при работе с запросами.
Предположим, что нужно извлечь список регионов из данных заказчиков, которые применялись в предыдущих примерах. В используемых данных нет отдельного списка регионов, поэтому необходимо получить уникальный, не повторяющийся список регионов из списка заказчиков. LINQ предоставляет метод Distinct(), который облегчает выполнение задачи нахождения данных подобного рода.
Например,
var queryResults =(from c in customers select c.Get_Region()).Distinct();
Запросы ANY и ALL
Еще один тип запросов состоит в определении того, что какие-то данные удовлетворяют определенному условию, или в определении того, что все данные удовлетворяют условию. Например, может потребоваться знать, что продукта нет на складе (количество равно нулю) или что транзакция произошла.
LINQ предоставляет булевские методы Any() и All(), которые могут быстро сообщить, истинно ли некоторое условие для существующих данных.
Например, поместим на форму кнопки для определения того, что есть заказчики из США и из Азии.
Код, выполняемый при нажатии на ту и другую кнопки, следующий:
private void button2_Click(object sender, EventArgs e)
{ bool anyUSA = customers.Any(с =>с.Get_Country() == "USA");
if (anyUSA)
label1.Text = "Некоторые заказчики находятся в США";
else
label1.Text = "Нет заказчиков из США"; }
private void button6_Click(object sender, EventArgs e)
{bool allAsia = customers.All(с =>с.Get_Region() == "Asia");
if (allAsia)
label2.Text="Все заказчики находятся в Азии";
else
label2.Text="He все заказчики находятся в Азии"; }
Групповой запрос
Групповой запрос делит данные на группы и позволяет сортировать, вычислять агрегатные значения и сравнивать группы.
В код события нажатия кнопки Сумма заказов по каждому региону поместим два запроса и цикл обработки результатов:
var queryResults =
from с in customers group с by с.Get_Region() into eg
select new { TotalSales = eg.Sum(c => c.Get_Sales()), Region = eg.Key };
var orderedResults =
from eg in queryResults
orderby eg.TotalSales descending
select eg;
foreach (var item in orderedResults)
{listBox2.Items.Add(item.TotalSales + "\t: " + item.Region);
Данные в групповом запросе группируются по ключевому полю — полю, для которого все члены каждой группы имеют одноименное значение – сумма продаж. В этом примере ключевым полем является Region:
group с by с.Get_Region()
Необходимо вычислить сумму по каждой группе, поэтому выполняется группирование в новый результирующий набор по имени eg:
group с by с.Get_Region() into eg
В конструкции select определяются новые поля — общая сумма продаж (вычисляемая путем сылки на результирующий набору eg) и ключевое значение группы, к которому обращение происходит по специальному полю Key группы:
selectnew { TotalSales = eg.Sum(c => с. Get_Sales()), Region = eg.Key }.это критерий создания каждой группы данных.
Желательно упорядочить результат в порядке убывания по полю TotalSales. Для этого создается второй запрос для упорядочивания результатов группового запроса. Это стандартный запрос select с конструкцией orderby; он не использует никаких групповых средств LINQ, за исключением того, что источник данных для него поступает от предыдущего группового запроса.
Затем результаты второго запроса выводятся в список.
Вложенная группировка
В следующем примере показано, как создавать вложенные группы в выражении запроса LINQ. Каждая группа, созданная в соответствии с годом или уровнем обучения, затем подразделяется на группы на основе имен учащихся.
public void QueryNestedGroups()
{
var queryNestedGroups =
from student in students
group student by student.Year into newGroup1
from newGroup2 in
(from student in newGroup1
group student by student.LastName)
group newGroup2 by newGroup1.Key;
// Three nested foreach loops are required to iterate
// over all elements of a grouped group. Hover the mouse
// cursor over the iteration variables to see their actual type.
foreach (varouterGroupinqueryNestedGroups)
{Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key);
foreach (varinnerGroupinouterGroup)
{Console.WriteLine("\tNames that begin with: {0}", innerGroup.Key);
foreach (varinnerGroupElementininnerGroup)
{Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName);
} } }}
/*
Output:
DataClass.Student Level = SecondYear
Names that begin with: Adams
Adams Terry
Names that begin with: Garcia
Garcia Hugo
Names that begin with: Omelchenko
Omelchenko Svetlana
DataClass.Student Level = ThirdYear
Names that begin with: Fakhouri
FakhouriFadi
Names that begin with: Garcia
Garcia Debra
Names that begin with: Tucker
Tucker Lance
DataClass.Student Level = FirstYear
Names that begin with: Feng
FengHanying
Names that begin with: Mortensen
Mortensen Sven
Names that begin with: Tucker
Tucker Michael
DataClass.Student Level = FourthYear
Names that begin with: Garcia
Garcia Cesar
Names that begin with: O'Donnell
O'Donnell Claire
Names that begin with: Zabokritski
ZabokritskiEugene
*/
Обратите внимание на то, что для выполнения итерации по внутренним элементам вложенной группы необходимы три вложенных цикла foreach.
Take и Skip
В ряде случаев нужно найти несколько первых данных из запроса. Их количество заранее неизвестно, поэтому нельзя использовать условие where для их поиска.
LINQ-операция такого запроса представлена методом Take (), который
учитывает первые n результатов при выводе запроса. При практическом применении это может быть скомбинировано с orderby для получения первых n результатов.
Противоположностью Take () является метод Skip (), который пропускает первые n результатов, возвращая остальные.
Предположим, что добавлено две кнопки – выбор первого десятка продаж с наибольшими суммами и выдача всех продаж, кроме первого десятка.
Запрос:
var queryResults =
from c in customers
orderby c.Get_Sales() descending select new {Dan=c.Info()};
Опишем два цикла обработки результатов, один с использованием Take (), а другой — с применением Skip ():
foreach (var item in queryResults.Take(10))
{ listBox2.Items.Add(item.Dan); }
foreach (var item in queryResults.Skip(10)) { listBox2.Items.Add(item.Dan);}
First и FirstOrDefault
Предположим, что требуется найти заказчика из Африки в наборе данных.
LINQ предоставляет эту возможность через метод First(), который возвращает первый элемент результирующего набора, отвечающий заданным критериям. Если нет ни одного заказчика, например, из Антарктиды, то LINQпредоставляет метод для обработки такого случая без дополнительного кода обработки ошибок: FirstOrDefault():
var queryResults = from c in customers
select new{ID=c.Get_ID(),Region=c.Get_Region()} ;
listBox1.Items.Add(queryResults.First(c => c.Region == "Africa"));
listBox1.Items.Add(queryResults.FirstOrDefault(c => c.Region == "Antarctica"));
Операции с множествами
В LINQ операциями с множествами называют операции запроса, образующие результирующий набор, основанный на наличии или отсутствии эквивалентных элементов внутри одной или разных коллекций (или множеств).
В следующем разделе перечислены методы стандартных операторов запросов, выполняющие операции с множествами.
Имя метода |
Описание |
Distinct |
Удаляет повторяющиеся значения из коллекции. |
Except |
Возвращает разность множеств — набор элементов одной коллекции, которые не содержатся во второй коллекции. |
Intersect |
Возвращает пересечение множеств — набор элементов, содержащихся в каждой из коллекций. |
Union |
Возвращает объединение множеств — набор уникальных элементов, содержащихся в любой из коллекций. |
Сравнение операций над множествами
Distinct
На следующем рисунке показано действие метода Distinct в последовательности знаков. Возвращаемая последовательность содержит уникальные элементы из входной последовательности.
Except
На следующем рисунке показано действие метода Except. Возвращаемая последовательность содержит только те элементы из первой входной последовательности, которые не содержатся во второй входной последовательности.
Intersect
На следующем рисунке показано действие метода Intersect. Возвращаемая последовательность содержит элементы, общие для обеих входных последовательностей.
Union
На следующем рисунке показана операция объединения двух последовательностей знаков. Возвращаемая последовательность содержит уникальные элементы из обеих входных последовательностей.
LINQ поддерживает стандартные операции множеств, такие как Union () и Intersect () - объединентие и пересечение, оперирующие результатами запроса.
В примере добавим список заказов и используем операции над множествами для сопоставления заказов с существующими закшзчиками.
Создадимновыйкласс:
class Order
{ string ID; decimal Amount;
public string Get_ID() { return ID; }
public decimal Get_Amount() { return Amount; }
public Order(string ID, decimal Amount)
{ this.ID = ID; this.Amount = Amount;}
public string Info()
{ return "ID: " + ID + " Сумма: " + Convert.ToString(Amount); }
Создадим коллекцию объектов класса Order и поместим в нее экземпляры:
List <Order> orders = new List<Order>();
orders.Add(new Order("P", 100 ));
orders.Add(new Order ("Q", 200 ));
orders.Add(new Order ("R",300));
orders.Add(new Order ("S",400));
orders.Add(new Order ("T",500));
orders.Add(new Order ("MU", 600));
orders.Add(new Order ("V",700));
orders.Add(new Order ("MW",800));
orders.Add(new Order ("X",900));
orders.Add(new Order ("Y",1000));
orders.Add(new Order ("Z",1100));
Создадим запросы по каждой коллекции:
var customerlDs =
from с in customers
select с.Get_ID();
var orderlDs =
from o in orders
select o.Get_ID();
Далее создадим три запроса для выполнения соответственно операций пересечения, исключения и объединения:
var customersWithOrders = customerlDs.Intersect(orderlDs);
listBox1.Items.Clear();
foreach (var item in customersWithOrders)
{listBox1.Items.Add(item); }
var ordersNoCustomers = orderlDs.Except(customerlDs);
listBox1.Items.Clear();
foreach (var item in ordersNoCustomers)
{ listBox1.Items.Add(item);}}
var allCustomerOrderlDs = customerlDs.Union(orderlDs);
listBox1.Items.Clear();
foreach (var item in allCustomerOrderlDs)
{listBox1.Items.Add(item);}}
Соединения
Наборы данных, подобные спискам customers и orders, которые были только что созданы с общим ключевым полем (ID) позволяют использовать запрос join, посредством которого можно запросить взаимосвязанные данные из обоих списков в единственном запросе, объединяя их по значению ключевого поля.
var queryResults =
from c in customers
join o in orders on c.Get_ID() equals o.Get_ID()
select new { ID=c.Get_ID(), City=c.Get_City(), SalesBefore = c.Get_Sales(),
NewOrder = o.Get_Amount(), SalesAfter = c.Get_Sales()+o.Get_Amount() };
listBox1.Items.Clear();
foreach (var item in queryResults)
{listBox1.Items.Add(item.ID+" "+item.City+" "+Convert.ToString(item.SalesBefore)+" "
+Convert.ToString(item.NewOrder)+" "+Convert.ToString(item.SalesAfter));}
Операнды equals – это сравниваемые значения ключа в двух списках.
Вид формы при выполнении и нажатии кнопки Соединение (рис. 45):
Рис. 45. Выполнение запроса Соединение
Во всех примерах в данном разделе используются следующие вспомогательные классы и источники данных.
publicclassStudentClass
{
#region data
protectedenumGradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
protectedclass Student
{
publicstringFirstName { get; set; }
publicstringLastName { get; set; }
publicint ID { get; set; }
publicGradeLevel Year;
public List<int>ExamScores;
}
protectedstatic List<Student> students = new List<Student>
{
new Student {FirstName = "Terry", LastName = "Adams", ID = 120,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 99, 82, 81, 79}},
new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 99, 86, 90, 94}},
new Student {FirstName = "Hanying", LastName = "Feng", ID = 117,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 93, 92, 80, 87}},
new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 97, 89, 85, 82}},
new Student {FirstName = "Debra", LastName = "Garcia", ID = 115,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 35, 72, 91, 70}},
new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 92, 90, 83, 78}},
new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 88, 94, 65, 91}},
new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 75, 84, 91, 39}},
new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 97, 92, 81, 60}},
new Student {FirstName = "Lance", LastName = "Tucker", ID = 119,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 68, 79, 88, 92}},
new Student {FirstName = "Michael", LastName = "Tucker", ID = 122,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 94, 92, 91, 91}},
new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 96, 85, 91, 60}}
};
#endregion
//Helper method, used in GroupByRange.
protectedstaticintGetPercentile(Student s)
{
doubleavg = s.ExamScores.Average();
returnavg> 0 ? (int)avg / 10 : 0;
}
publicvoidQueryHighScores(int exam, int score)
{
varhighScores = from student in students
wherestudent.ExamScores[exam] > score
selectnew {Name = student.FirstName, Score = student.ExamScores[exam]};
foreach (var item inhighScores)
{
Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
}
}
}
publicclass Program
{
publicstaticvoid Main()
{
StudentClasssc = newStudentClass();
sc.QueryHighScores(1, 90);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
В следующем примере показывается группировка элементов источника с помощью использования отдельного свойства элемента в качестве ключа группы. В данном случае в качестве ключа используется string, фамилия учащегося. Для ключа можно также использовать подстроку. При операции группирования используется компаратор проверки на равенство, используемый по умолчанию для данного типа.
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupBySingleProperty().
publicvoidGroupBySingleProperty()
{
Console.WriteLine("Group by a single property in an object:");
// Variable queryLastNames is an IEnumerable<IGrouping<string,
// DataClass.Student>>.
varqueryLastNames =
from student in students
group student bystudent.LastNameintonewGroup
orderbynewGroup.Key
selectnewGroup;
foreach (varnameGroupinqueryLastNames)
{
Console.WriteLine("Key: {0}", nameGroup.Key);
foreach (var student innameGroup)
{
Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
}
}
}
/* Output:
Group by a single property in an object:
Key: Adams
Adams, Terry
Key: Fakhouri
Fakhouri, Fadi
Key: Feng
Feng, Hanying
Key: Garcia
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: Mortensen
Mortensen, Sven
Key: O'Donnell
O'Donnell, Claire
Key: Omelchenko
Omelchenko, Svetlana
Key: Tucker
Tucker, Lance
Tucker, Michael
Key: Zabokritski
Zabokritski, Eugene
*/
В следующем примере показывается группировка элементов источника, когда в качестве ключа группы используется не свойство объекта. В данном случае в качестве ключа используется первая буква фамилии учащегося.
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupBySubstring().
publicvoidGroupBySubstring()
{
Console.WriteLine("\r\nGroup by something other than a property of the object:");
varqueryFirstLetters =
from student in students
group student bystudent.LastName[0];
foreach (varstudentGroupinqueryFirstLetters)
{
Console.WriteLine("Key: {0}", studentGroup.Key);
// Nestedforeach is required to access group items.
foreach (var student instudentGroup)
{
Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
}
}
}
/* Output:
Group by something other than a property of the object:
Key: A
Adams, Terry
Key: F
Fakhouri, Fadi
Feng, Hanying
Key: G
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: M
Mortensen, Sven
Key: O
O'Donnell, Claire
Omelchenko, Svetlana
Key: T
Tucker, Lance
Tucker, Michael
Key: Z
Zabokritski, Eugene
*/
В следующем примере показывается группировка элементов источника с помощью использования числового диапазона в качестве ключа группы. Затем запрос передает результаты в анонимный тип, содержащий только имя и фамилию, а также диапазон процентилей, к которому принадлежит студент. Использование анонимного типа объясняется тем, что для отображения результатов не требуется использовать полный объект Student. GetPercentile — это вспомогательная функция, которая вычисляет процент на основе средних результатов учащихся. Метод возвращает целое число в диапазоне от 0 до 10.
protected static int GetPercentile(Student s)
{doubleavg = s.ExamScores.Average();
returnavg> 0 ? (int)avg / 10 : 0;
}
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupByRange().
public void GroupByRange()
{
Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:");
var queryNumericRange =
from student in students
let percentile = GetPercentile(student)
group new { student.FirstName, student.LastName } by percentile into percentGroup
orderby percentGroup.Key
select percentGroup;
// Nestedforeach required to iterate over groups and group items.
foreach (varstudentGroupinqueryNumericRange)
{
Console.WriteLine("Key: {0}", (studentGroup.Key * 10));
foreach (var item instudentGroup)
{
Console.WriteLine("\t{0}, {1}", item.LastName, item.FirstName);
} } }
/* Output:
Group by numeric range and project into a new anonymous type:
Key: 60
Garcia, Debra
Key: 70
O'Donnell, Claire
Key: 80
Adams, Terry
Feng, Hanying
Garcia, Cesar
Garcia, Hugo
Mortensen, Sven
Omelchenko, Svetlana
Tucker, Lance
Zabokritski, Eugene
Key: 90
Fakhouri, Fadi
Tucker, Michael
*/
В следующем примере показывается группировка элементов источника с помощью выражения логического сравнения. В этом примере логическое выражение проверяет значение среднего экзаменационного результата учащегося, превосходит ли оно 75 баллов. Как и в предыдущих примерах, результаты передаются в анонимный тип, поскольку весь элемент источника не требуется. Обратите внимание на то, что свойства в анонимном типе становятся свойствами в члене Key, а при выполнении запроса доступ к ним можно получить по имени.
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupByBoolean().
publicvoidGroupByBoolean()
{
Console.WriteLine("\r\nGroup by a Boolean into two groups with string keys");
Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:");
varqueryGroupByAverages = from student in students
groupnew { student.FirstName, student.LastName }
bystudent.ExamScores.Average() > 75 intostudentGroup
selectstudentGroup;
foreach (varstudentGroupinqueryGroupByAverages)
{
Console.WriteLine("Key: {0}", studentGroup.Key);
foreach (var student instudentGroup)
Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName);
}
}
/* Output:
Group by a Boolean into two groups with string keys
"True" and "False" and project into a new anonymous type:
Key: True
Terry Adams
FadiFakhouri
HanyingFeng
Cesar Garcia
Hugo Garcia
Sven Mortensen
Svetlana Omelchenko
Lance Tucker
Michael Tucker
Eugene Zabokritski
Key: False
DebraGarcia
ClaireO'Donnell
*/
В следующем примере показано, как использовать анонимный тип для инкапсуляции ключа, содержащего несколько значений. В данном случае в качестве первого значения ключа используется первая буква фамилии учащегося. Второе значение ключа является логическим и указывает, набрал ли учащийся более 85 баллов на первом экзамене. Группы можно сортировать по любому из свойств в данном ключе.
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupByCompositeKey().
C#
publicvoidGroupByCompositeKey()
{
varqueryHighScoreGroups =
from student in students
group student bynew { FirstLetter = student.LastName[0],
Score = student.ExamScores[0] > 85 } intostudentGroup
orderbystudentGroup.Key.FirstLetter
selectstudentGroup;
Console.WriteLine("\r\nGroup and order by a compound key:");
foreach (varscoreGroupinqueryHighScoreGroups)
{
string s = scoreGroup.Key.Score == true ? "more than" : "less than";
Console.WriteLine("Name starts with {0} who scored {1} 85", scoreGroup.Key.FirstLetter, s);
foreach (var item inscoreGroup)
{
Console.WriteLine("\t{0} {1}", item.FirstName, item.LastName);
}
}
}
/* Output:
Group and order by a compound key:
Name starts with A who scored more than 85
Terry Adams
Name starts with F who scored more than 85
FadiFakhouri
HanyingFeng
Name starts with G who scored more than 85
Cesar Garcia
Hugo Garcia
Name starts with G who scored less than 85
Debra Garcia
Name starts with M who scored more than 85
Sven Mortensen
Name starts with O who scored less than 85
Claire O'Donnell
Name starts with O who scored more than 85
Svetlana Omelchenko
Name starts with T who scored less than 85
Lance Tucker
Name starts with T who scored more than 85
Michael Tucker
Name starts with Z who scored more than 85
EugeneZabokritski
*/
В терминах реляционных баз данных внутреннее соединение возвращает результирующий набор, в котором каждый элемент первой коллекции появляется один раз для каждого соответствующего элемента второй коллекции. Если для элемента первой коллекции нет соответствующего элемента, он не появляется в результирующем наборе.Метод Join, вызываемый предложением join в C#, реализует внутреннее соединение.
В этом разделе демонстрируется выполнение следующих четырех типов внутреннего соединения.
Простое внутреннее соединение, которое сопоставляет элементы из двух источников данных на основании простого ключа.
Внутреннее соединение, которое сопоставляет элементы из двух источников данных на основании составного ключа. Составной ключ (ключ, который содержит несколько значений) позволяет сопоставлять элементы на основании нескольких свойств.
При выполнении множественного соединения последовательные операции соединения применяются друг к другу.
Внутреннее соединение, которое реализуется с помощью группового соединения.
Пример
