Розробка програми демонстрації використання запитів на мові LINQ

Розробка програми демонстрації використання запитів на мові LINQ

У даній темі розглядається покроковий процес розв’язку задачі з використанням запитів на мові LINQ. Використовуючи даний приклад можна розв’язувати подібні задачі.

У темі також показано:

  • читання даних з файлу з використанням класу StreamReader;
  • представлення даних у вигляді узагальненого динамічного масиву List<T>;
  • приклади деяких запитів на мові LINQ, що розв’язують дану задачу.

Дана тема висвітлює виконання лабораторної роботи в одному з навчальних закладів.


Зміст



Умова задачі

Файл “Workers.txt містить наступну інформацію про співробітників:

  • ідентифікаційний код;
  • прізвище та ініціали;
  • вид освіти;
  • фах;
  • рік народження.

Файл “Salary.txt містить:

  • ідентифікаційний код;
  • зарплату за перше півріччя;
  • зарплату за друге півріччя.

Завантажити файл “Workers.txt” можна тут. Завантажити файл “Salary.txt” можна тут.

Розв’язати наступні задачі:

  1. Вивести прізвища та ініціали співробітників вище 35 років.
  2. Вивести ідентифікаційний код співробітника з найбільшою зарплатою за друге півріччя.
  3. Вивести прізвища, ініціали та вид освіти тих співробітників, зарплата яких за рік нижче середньої за рік.

Завдання мають бути виконані з використанням запитів 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.

C#. Шаблон Windows Forms Application. Основна форма програми

Рис. 1. Основна форма програми

 

3. Налаштування елементів управління форми

Налаштувати такі елементи управління на формі:

  • виділити елемент управління label1 (з допомогою мишки). В елементі управління label1 властивість Text = “Файл Workers.txt“;
  • в елементі управління label2 властивість Text = “Файл Salary.txt“;
  • в елементі управління label3 властивість Text = “Результат“;
  • в елементі управління button1 властивість Text = “Завдання 1“;
  • в елементі управління button2 властивість Text = “Завдання 2“;
  • в елементі управління button3 властивість Text = “Завдання 3“.

Після налаштування форма матиме вигляд, як показано на рисунку 2.

C#. Додаток типу Windows Forms Application. Форма після налаштування елементів управління

Рис. 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. Запуск програми на виконання

Після програмування усіх обробників подій можна запускати програму на виконання.