Разработка программы демонстрации использования запросов на языке 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.

02_02_00_018_01_

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

 

3. Настройка элементов управления формы.

Настроить следующие элементы управления на форме:

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

После настройки форма будет иметь вид, как показано на рисунке 2.

02_02_00_018_02r

Рис. 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» в класс Form1 нужно ввести два метода:

  • Read_Workers();
  • Read_Salary().

Листинг метода 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» имеет вид:

// идентификационный код сотрудника с наибольшей зарплатой
// за II полугодие
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. Запуск программы на выполнение.

После программирования всех обработчиков событий можно запускать программу на выполнение.