Розробка програми демонстрації використання запитів на мові LINQ
У даній темі розглядається покроковий процес розв’язку задачі з використанням запитів на мові LINQ. Використовуючи даний приклад можна розв’язувати подібні задачі.
У темі також показано:
- читання даних з файлу з використанням класу StreamReader;
- представлення даних у вигляді узагальненого динамічного масиву List<T>;
- приклади деяких запитів на мові LINQ, що розв’язують дану задачу.
Дана тема висвітлює виконання лабораторної роботи в одному з навчальних закладів.
Зміст
- Умова задачі
- Додаткові міркування
- Виконання
- 1. Створення проекту типу Windows Forms Application
- 2. Розробка основної форми програми
- 3. Налаштування елементів управління форми
- 4. Підключення простору імен System.Linq
- 5. Побудова внутрішніх структур даних, що відповідають файлам “Workers.txt” та “Salary.txt“
- 6. Підключення простору імен System.IO
- 7. Створення методів Read_Workers() та Read_Salary() для читання даних з файлів “Workers.txt” та “Salary.txt“
- 8. Програмування конструктора Form1() основної форми програми. Читання даних з файлів
- 9. Програмування події кліку на кнопці “Завдання 1“
- 10. Програмування події кліку на кнопці “Завдання 2“
- 11. Програмування події кліку на кнопці “Завдання 3“
- 12. Запуск програми на виконання
Пошук на інших ресурсах:
Умова задачі
Файл “Workers.txt“ містить наступну інформацію про співробітників:
- ідентифікаційний код;
- прізвище та ініціали;
- вид освіти;
- фах;
- рік народження.
Файл “Salary.txt“ містить:
- ідентифікаційний код;
- зарплату за перше півріччя;
- зарплату за друге півріччя.
Завантажити файл “Workers.txt” можна тут. Завантажити файл “Salary.txt” можна тут.
Розв’язати наступні задачі:
- Вивести прізвища та ініціали співробітників вище 35 років.
- Вивести ідентифікаційний код співробітника з найбільшою зарплатою за друге півріччя.
- Вивести прізвища, ініціали та вид освіти тих співробітників, зарплата яких за рік нижче середньої за рік.
Завдання мають бути виконані з використанням запитів LINQ.
⇑
Додаткові міркування
Як видно з структур файлів “Workers.txt” та “Salary.txt“, ці файли мають однакове поле “ідентифікаційний код”. Це означає, що при вводі даних у файли потрібно бути уважним. Ідентифікаційні коди мають бути унікальними в одному файлі та обов’язково повторюватись у різних файлах.
Вміст файлу “Workers.txt“:
1; Ivanov I.I.; Bachelor; Programmer Engineer; 1900 2; Petrov P.P.; Master; Team Lead; 1950 3; Sidorov S.S.; Super Master; Software Architect; 1990 4; Johnson J.J.; Super Bachelor; HTML-coder; 1997 5; Nicolson J.J.; Bachelor; DevOps engineer; 1992
Вміст файлу “Salary.txt“:
1; 23550; 26580 2; 26800; 28390 3; 24660; 27777 4; 35880; 44444 5; 55555; 39938
⇑
Виконання
1. Створення проекту типу Windows Forms Application
Створити проект як Windows Forms Application.
Приклад створення проекту типу Windows Forms Application наведено тут.
Зберегти файли проекту в деякий каталог. Скопіювати файли Workers.txt та Salary.txt в той самий каталог, де має бути виконавчий (.exe) файл програми.
⇑
2. Розробка основної форми програми
Побудувати форму як показано на рисунку 1. На формі розміщуються такі елементи управління:
- три елементи управління типу Label. Автоматично створюється три об’єкти (екземпляри класів) з іменами label1, label2, label3;
- три елементи управління типу Button. Автоматично створюється три об’єкти з іменами button1, button2, button3;
- три елементи управління типу ListBox. Автоматично створюється три об’єкти з іменами listBox1, listBox2, listBox3.
Рис. 1. Основна форма програми
⇑
3. Налаштування елементів управління форми
Налаштувати такі елементи управління на формі:
- виділити елемент управління label1 (з допомогою мишки). В елементі управління label1 властивість Text = “Файл Workers.txt“;
- в елементі управління label2 властивість Text = “Файл Salary.txt“;
- в елементі управління label3 властивість Text = “Результат“;
- в елементі управління button1 властивість Text = “Завдання 1“;
- в елементі управління button2 властивість Text = “Завдання 2“;
- в елементі управління button3 властивість Text = “Завдання 3“.
Після налаштування форма матиме вигляд, як показано на рисунку 2.
Рис. 2. Форма після налаштування елементів управління
⇑
4. Підключення простору імен System.Linq.
Щоб використовувати запити на мові LINQ потрібно підключити простір імен System.Linq. Як правило, простір імен System.Linq підключається автоматично при створенні додатку типу Windows Forms Application.
У файлі Form1.cs рядок підключення має вигляд:
using System.Linq;
⇑
5. Побудова внутрішніх структур даних, що відповідають файлам “Workers.txt” та “Salary.txt“.
Дані, що відповідають одному рядку файлу Workers.txt доцільно представити у вигляді структури Workers, лістинг якої наступний:
struct Workers { public string code; // ідентифікаційний код public string name; // прізвище та ініціали public string education; // вид освіти public string profession; // фах public int year; // рік народження }
Так само дані, що відповідають одному рядку файлу Salary.txt доцільно представити у структурі Salary:
struct Salary { public string code; // ідентифікаційний код public float salary1; // зарплата за 1 півріччя public float salary2; // зарплата за 2 півріччя }
Оскільки, рядків у файлах може бути багато, то всі дані можна розмістити у вигляді узагальнених динамічних масивів типу List<T>.
Для структур Workers та Salary динамічні масиви мають такий опис:
List<Workers> lw = null; // список структур типу Workers List<Salary> ls = null; // список структур типу Salary
Після введення структур, лістинг класу Form1 має вигляд:
... public partial class Form1 : Form { struct Workers { public string code; // ідентифікаційний код public string name; // прізвище та ініціали public string education; // вид освіти public string profession; // фах public int year; // рік народження } struct Salary { public string code; // ідентифікаційний код public float salary1; // зарплата за 1 півріччя public float salary2; // зарплата за 2 півріччя } List<Workers> lw = null; // список структур типу Workers List<Salary> ls = null; // список структур типу Salary public Form1() { InitializeComponent(); } } ...
⇑
6. Підключення простору імен System.IO
Для читання даних з файлів у програмі буде використано можливості класу StreamReader з бібліотеки класів .NET Framework. Тому, для використання методів цих класів на початку файлу “Form1.cs” потрібно додати рядок:
using System.IO;
⇑
7. Створення методів Read_Workers() та Read_Salary() для читання даних з файлів “Workers.txt” та “Salary.txt“
Для читання даних з файлів “Workers.txt” та “Salary.txt” потрібно ввести два методи:
- Read_Workers();
- Read_Salary().
Реалізація цих методів розміщується у класі форми Form1. Лістинг методу Read_Workers() наступний:
// читання даних з файлу "Workers.txt" public void Read_Workers() { // створити об'єкт класу StreamReader, який відповідає файлу "Workers.txt" StreamReader sr = File.OpenText("Workers.txt"); string[] fields; // змінна, що відповідає полям структури Workers string line = null; Workers w; // прочитати рядок line = sr.ReadLine(); while (line != null) { // розбити рядок на підрядки - розділювачем є символ ';' fields = line.Split(';'); // побудова структури типу Workers w.code = fields[0]; w.name = fields[1]; w.education = fields[2]; w.profession = fields[3]; w.year = Int32.Parse(fields[4]); // додавання структури типу Workers у список List<Workers> lw.Add(w); // додавання рядка в список listBox1 listBox1.Items.Add(line); // прочитати наступний рядок line = sr.ReadLine(); } }
Метод Read_Workers() читає дані з файлу “Workers.txt” та записує їх у:
- динамічний масив lw типу List<Workers>;
- елемент управління listBox1 для відображення на формі.
Лістинг методу Read_Salary() наступний:
// читання даних з файлу "Salary.txt" public void Read_Salary() { // створити об'єкт класу StreamReader, який відповідає файлу "Salary.txt" StreamReader sr = File.OpenText("Salary.txt"); string[] fields; // змінна, що відповідає полям структури Workers string line = null; Salary s; // прочитати рядок line = sr.ReadLine(); while (line != null) { // розбити рядок на підрядки - розділювачем є символ ';' fields = line.Split(';'); // побудова структури типу Salary s.code = fields[0]; s.salary1 = (float)Convert.ToDouble(fields[1]); s.salary2 = (float)Double.Parse(fields[2]); // додавання структури типу Salary у список List<Salary> ls.Add(s); // додавання рядка в список listBox2 listBox2.Items.Add(line); // прочитати наступний рядок line = sr.ReadLine(); } }
Метод Read_Salary() читає дані з файлу “Salary.txt” та записує їх у:
- динамічний масив ls типу List<Salary>;
- елемент управління listBox2 для відображення на формі.
⇑
8. Програмування конструктора Form1() основної форми програми. Читання даних з файлів.
Після запуску програми на виконання дані з файлів повинні бути автоматично завантажені в елементи управління listBox1 та listBox2.
Тому, потрібно в конструктор класу Form1() додати методи Read_Workers() та Read_Salary().
Також у конструктор класу Form1() додається виділення пам’яті для динамічних масивів lw та ls.
Загальний лістинг конструктора основної форми має вигляд:
public Form1() { InitializeComponent(); // виділення пам'яті для списків lw = new List<Workers>(); ls = new List<Salary>(); // очистити компоненти типу ListBox listBox1.Items.Clear(); listBox2.Items.Clear(); listBox3.Items.Clear(); // прочитати дані з файлу "Workers.txt" Read_Workers(); Read_Salary(); }
⇑
9. Програмування події кліку на кнопці “Завдання 1“.
Виконання пункту 1 завдання реалізується з допомогою кліку на кнопці “Завдання 1“. У результаті буде викликано відповідний обробник події.
Приклад програмування події кліку на кнопці у додатку типу Windows Forms Application наведено тут.
Лістинг обробника події кліку на кнопці “Завдання 1“.
// Прізвища та ініціали співробітників вище 35 років private void button1_Click(object sender, EventArgs e) { // запит з іменем names var names = from nm in lw where nm.year < (2016-35) select nm.name; listBox3.Items.Clear(); // очистити список // додати в список listBox3 результат запиту names foreach (string s in names) listBox3.Items.Add(s); }
У наведеному лістингу формується запит на мові LINQ. Цей запит має ім’я names:
var names = from nm in lw where nm.year < (2016-35) select nm.name;
Запит починається з оператора from. В операторі from задається змінна діапазону, яка має ім’я nm. Джерелом даних в операторі from є динамічний масив lw типу Workers.
Наступним йде оператор where, який означає умову, якій повинен задовольняти елемент в джерелі даних, щоб його можна було б отримати за запитом.
Запит завершується оператором select. В операторі select задається, що саме повинно виводитись за запитом. У даному прикладі вибирається поле name структури Workers (прізвища та ініціали співробітників вище 35 років).
Для виконання запиту в програмі використовується цикл foreach.
foreach (string s in names) listBox3.Items.Add(s);
Оскільки, результатом LINQ запиту є тип string, то у циклі описується змінна s типу string.
⇑
10. Програмування події кліку на кнопці “Завдання 2“.
Обробник події кліку на кнопці “Завдання 2” має вигляд:
// ідентифікаційний код співробітника з найбільшою зарплатою // за ІІ півріччя private void button2_Click(object sender, EventArgs e) { // запит max_salary var max_salary = (from ms in ls select ms.salary2).Max(); // метод Max() повертає максимальне знач. listBox3.Items.Clear(); listBox3.Items.Add(max_salary); }
У цьому LINQ-запиті використовується метод Max(), що знаходить максимальне значення в переліку (списку). Цей перелік формується у запиті з іменем max_salary.
⇑
11. Програмування події кліку на кнопці “Завдання 3“.
Лістинг обробника події кліку на кнопці “Завдання 3” наступний:
// Прізвища, ініціали та вид освіти тих співробітників, // зарплата яких за рік нижче середньої за рік private void button3_Click(object sender, EventArgs e) { var result = from w in lw from sl in ls let avg = (from s in ls select (s.salary1 + s.salary2)).Average() where ((sl.salary1 + sl.salary2) < avg) && (w.code == sl.code) select w.name +" - " + w.profession; listBox3.Items.Clear(); foreach (string s in result) listBox3.Items.Add(s); }
У цьому обробнику формується запит з іменем result.
У запиті потрібно здійснити вибірку з двох джерел даних. У нашому випадку це джерела:
- динамічний масив lw типу List<Workers>. З цього масиву вибираються прізвище співробітника та його вид освіти;
- динамічний масив ls типу List<Salary>. З цього масиву вибираються зарплати за перше та друге півріччя.
Для того, щоб здійснити вибірку з двох джерел даних, запит містить два вкладені оператори from. Для того, щоб вибирати дані, що відповідають одному ідентифікаційному коду, в запиті (в операторі where) використовується рядок порівняння (w.code==sl.code):
where (...) && (w.code == sl.code)
Щоб порахувати середнє арифметичне використовується метод Average() середовища .NET Framework. Саме середнє арифметичне зберігається у змінній avg, що введена в запит з допомогою оператора let:
... let avg = (from s in ls select (s.salary1 + s.salary2)).Average() ...
⇑
12. Запуск програми на виконання
Після програмування усіх обробників подій можна запускати програму на виконання.
⇑