Евгений Колесников 1 rok pred
rodič
commit
f621393d60
2 zmenil súbory, kde vykonal 107 pridanie a 519 odobranie
  1. 0 414
      articles/cs_coloring.md
  2. 107 105
      articles/cs_coloring2.md

+ 0 - 414
articles/cs_coloring.md

@@ -1,414 +0,0 @@
-<table style="width: 100%;"><tr><td style="width: 40%;">
-<a href="../articles/cs_pagination.md">Пагинация, сортировка, фильтрация, поиск
-</a></td><td style="width: 20%;">
-<a href="../readme.md">Содержание
-</a></td><td style="width: 40%;">
-<a href="../articles/cs_edit_product.md">Создание, изменение продукции
-</a></td><tr></table>
-
-# Подсветка элементов по условию. Дополнительные выборки.
-
->В списке продукции необходимо подсвечивать светло-красным цветом те продукты, которые не продавались агентами в последний месяц.
-
-Критерий | Баллы
----------|:---:
-Реализовано выделение (любым образом) продуктов, которые не продавались агентами в последний месяц | 1.5
-Выделение реализовано в виде светло-красной подсветки элемента продукции | 0.5
-**Итого** | **2**
-
-В самом списке продукции данных о продажах нет. Судя по названиям таблиц данные эти должны быть в таблице **ProductSale**. Судя по связям этой таблицы, мы должны заполнить ещё таблицы **Agent** и **AgentType**.
-
-![](../img/01068.png)
-
-На демо-экзамене до этого вы вряд-ли дойдёте (хотя критерий достаточно жирный), но в рамках курсовой/дипломной работы реализовать дополнительный функционал надо.
-
-## Добавление данных вручную
-
-1. Добавляем типы агентов
-
-    Исходных данных для этой и последующих таблиц нет, поэтому вы можете писать туда что угодно (но близко к предметной области). Для типов агентов подойдут "индивидуальный предприниматель" и "Общество с ограниченной ответственностью" (для не обязательных таблиц много данных придумывать не надо, достаточно одной-двух записей)
-
-    И так как исходных данных нет, ~~а редактировать напрямую из **MySQL Workbench** нельзя, то пишем SQL-запрос вставки данных в основном его синтаксисе (с VALUE)~~ то добавляем данные прямо в таблицы.
-
-2. Добавляем агента
-
-    Тут заполняем только обязательные поля. *AgentTypeID* смотрим в таблице **AgentType**.
-
-3. Создание продаж продукции
-
-    Добавляем несколько записей. Поля *AgentID* и *ProductID* смотрим в соответствующих таблицах. Дата продажи заполняется в формате `YYYY-MM-DD`
-
-## Дополнительная выборка
-
-Сначала пишем и отлаживаем запрос получения количества дней с последней продажи (дата продажи сама по себе нам не нужна, достаточно знать сколько дней прошло с момента продажи)
-
-```sql
-SELECT 
-    ProductID, 
-    DATEDIFF(NOW(), max(SaleDate)) AS LastSaleDate
-FROM 
-    ProductSale
-GROUP BY 
-    ProductID
-```
-
-* Функция **DATEDIFF** вычисляет количество дней между датами. 
-* Функция **NOW** возвращает текущую дату.
-
-Теперь этот запрос можно вставить в нашу основную выборку ещё одним **LEFT JOIN**-ом
-
-```sql
-SELECT 
-    p.*,
-    pt.Title AS ProductTypeTitle,
-    pp.MaterialList, pp.Total,
-    Sales.DaysFromLastSale
---  ^^^^^^^^^^^^^^^^^^^^^^
-FROM
-    Product p
-LEFT JOIN
-    ProductType pt ON p.ProductTypeID = pt.ID
-LEFT JOIN
-    (
-    SELECT
-        pm.ProductID,
-        GROUP_CONCAT(m.Title SEPARATOR ', ') as MaterialList, 
-        SUM(pm.Count * m.Cost / m.CountInPack) as Total
-    FROM
-        Material m,
-        ProductMaterial pm
-    WHERE m.ID = pm.MaterialID
-    GROUP BY ProductID
-    ) pp ON pp.ProductID = p.ID
-LEFT JOIN 
-    (
-        select 
-            ProductID, 
-            DATEDIFF(NOW(), max(SaleDate)) as DaysFromLastSale
-        from 
-            ProductSale
-        group by ProductID
-    ) Sales on Sales.ProductID = p.ID ;
-```
-
-Атрибут *DaysFromLastSale* в модель пропишите сами.
-
-В методе получения данных с сервера надо вставить проверку на тип **NULL**
-
-```cs
-NewProduct.DaysFromLastSale = 
-    (Reader["DaysFromLastSale"] as int?) ?? 999;
-```
-
-Или просто завернуть чтение этого поля в `try...except` и, при возникновении исключения, подставлять данные по-умолчанию
-
-```cs
-try
-{
-    NewProduct.DaysFromLastSale = Convert.ToInt32(
-        Reader["DaysFromLastSale"].ToString());
-}
-catch (Exception)
-{
-    // нам не интересно всё, что больше 30
-    NewProduct.DaysFromLastSale = 999;
-}
-```
-
-## Раскраска по условию
-
-Тут элементарно. У элемента рамка (Border) задаем цвет фона:
-
-```cs
-<Border 
-    BorderThickness="1" 
-    BorderBrush="Black" 
-    Background="{Binding BackgroundColor}"
-                ^^^^^^^^^^^^^^^^^^^^^^^^^
-    CornerRadius="5">
-```
-
-И добавляем в модель геттер *BackgroundColor*
-
-```cs
-public string BackgroundColor {
-    get {
-        if(DaysFromLastSale>30) return "#fee";
-        return "#fff";
-    }
-}
-```
-
->Про цвета: их можно отдавать в формате **#RGB**. Причём, чем ближе к **F**, тем светлее (**#FFF** - белый)
-
-![](../img/01069.png)
-
-# Массовая смена цены продукции
-
->Необходимо добавить возможность изменения минимальной стоимости продукции для агента сразу для нескольких выбранных продуктов. Для этой цели реализуйте возможность выделения сразу нескольких элементов в списке продукции, после чего должна появиться кнопка “Изменить стоимость на ...”. При нажатии на кнопку необходимо отобразить модальное окно с возможностью ввода числового значения, на которое и будет увеличена стоимость выбранных продуктов. По умолчанию в поле должно быть введено среднее значение цены на продукцию для агента. После нажатия кнопки “Изменить” стоимость выделенных продуктов должна быть изменена в базе данных, а также обновлена в интерфейсе.
-
-Критерий | Баллы
----------|:---:
-Реализована возможность выделения сразу нескольких элементов в списке | 0.2
-После выделения элементов в списке появляется кнопка "Изменить стоимость на ..." | 0.3
-При нажатии на кнопку отображается модальное окно для изменения цены | 0.1
-В модальном окне есть возможность ввода числового значения | 0.1
-По умолчанию введено значение средней цены выбранных продуктов | 0.2
-Реализована проверка на ввод только числового значения | 0.2
-После нажатия кнопки "Изменить" стоимость всех выбранных продуктов изменяется в БД | 0.5
-После нажатия кнопки "Изменить" стоимость всех выбранных продуктов обновляется в списке | 0.2
-**Итого** | **1.8**
-
-## Выделение нескольких элементов
-
-Возможность выделения нескольких элементов в **ListView** есть, специально её реаизовывать не надо. Надо лишь поймать это событие и сосчитать количество выделенных элементов.
-
-В **ListView** нужно добавить название (мы потом будем по нему искать количество выделенных элементов) и обработчик события *SelectionChanged*
-
-```cs
-x:Name="ProductListView"
-SelectionChanged="ListView_SelectionChanged"
-```
-
-Реализуем обработчик *ListView_SelectionChanged*:
-
-```cs
-public int ProductsSelectedCount = 0;
-private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
-{
-    ProductsSelectedCount = ProductListView.SelectedItems.Count;
-}
-```
-
-## Отображение кнопки "Изменить стоимость на ..."
-
-Сначала просто добавляем эту кнопку в разметку
-
-```xml
-<Button
-    x:Name="CostChangeButton"
-    Visibility=""
-    Content="Изменить стоимость на..."
-    />
-```
-
-И запоминаем какие значения может прнимать атрибут *Visibility*
-
-![](../img/01070.png)
-
-* **Collapsed** - элемент не отображается и НЕ ЗАНИМАЕТ МЕСТО в контейнере
-* **Hidden** - элемент не отображается, но под него остается выделенное место
-* **Visible** - элемент отображается
-
-Теперь, чтобы видимость кнопки зависела от количества выделенных элементов, мы привязываем атрибут *Visibility* к свойству *CostChangeButtonVisible*
-
-```xml
-<Button
-    x:Name="CostChangeButton"
-    Visibility="{Binding CostChangeButtonVisible}"
-    Content="Изменить стоимость на..."
-    />
-```
-
-И реализуем это свойство в коде ОКНА
-
-```cs
-public string CostChangeButtonVisible {
-    get {
-        if (ProductsSelectedCount > 1) return "Visible";
-        return "Collapsed";
-    }
-}
-```
-
-Осталось в обработчик события *ListView_SelectionChanged* вставить вызов метода PropertyChanged для свойства *CostChangeButtonVisible*
-
-Чтобы не плодить кучу одинаковых методов, я добавил в параметры метода **Invalidate** имя свойства, которое изменилось
-
-```cs
-private void Invalidate(string ComponentName = "ProductList") {
-    if (PropertyChanged != null)
-        PropertyChanged(this, new PropertyChangedEventArgs(ComponentName));
-}
-
-private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
-{
-    ProductsSelectedCount = ProductListView.SelectedItems.Count;
-    Invalidate("CostChangeButtonVisible");
-}
-```
-
-## Вывод модального окна
-
-1. Добавляем кнопке обработчик события клика
-
-    ```xml
-    <Button
-        x:Name="CostChangeButton"
-        Visibility="{Binding CostChangeButtonVisible}"
-        Click="CostChangeButton_Click"
-        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-        Content="Изменить стоимость на..."
-    />
-    ```
-
-2. Создаем и показываем модальное окно
-
-    Во-первых, вспоминаем, что мы должны соблюдать файловую структуру проекта, т.е. все однотипные объекты распихивать по соответствующим папкам. 
-    
-    Создадим папку `Windows` и в неё добавим окно (WPF)
-
-    ![](../img/01071.png)
-
-    Название окна должно быть осмысленным и с суффиксом *Window*. У меня получилось *EnterMinCostForAgentWindow*
-
-    >Можно в каталог `Windows` перетащить и главное окно **MainWindow**. Только в этом случае надо в разметке приложения (App.xaml) добавить название каталога:
-    >
-    >```xml
-    ><Application x:Class="mysql.App"
-    >   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    >   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    >   xmlns:local="clr-namespace:mysql"
-    >   StartupUri="Windows/MainWindow.xaml">
-    >               ^^^^^^^^
-    >...
-
-    Опять же, все окна должны иметь нормальные заголовки. В разметке окна поменяйте атрибут *Title* элемента **Window** (это надо сделать и для основного окна)
-
-    Содержимое у этого окна элементарное (текстовое поле и кнопка):
-
-    ```xml
-    <Grid>
-        <StackPanel
-            Orientation="Vertical" Margin="0,50,0,0">
-
-            <TextBox
-                Name="CostTextBox"
-                />
-            <Button 
-                Content="Изменить"/>
-        </StackPanel>
-    </Grid>
-    ```
-
-    В коде этого окна меняем конструктор:
-
-    ```cs
-    public EnterMinCostForAgentWindow(decimal AvgCost)
-                                      ^^^^^^^^^^^^^^^    
-    {
-        InitializeComponent();
-        CostTextBox.Text = AvgCost.ToString();
-    }
-    ```
-
-    Во-первых, в конструктор добавляем параметр (средняя цена). Во-вторых, записываем эту цену в текстовое поле.
-
-3. По условиям задачи мы должны вычислить среднюю цену для выделенных элементов списка (я на вскидку не нашёл как преобразовать **IList** в **IEnumerable**, поэтому тупо перебираем список выбранных элементов, считаем сумму и, заодно, собираем список идентификаторов)
-
-    ```cs
-    private void CostChangeButton_Click(object sender, RoutedEventArgs e)
-    {
-        decimal Sum = 0;
-        List<int> idList = new List<int>();
-        foreach (Product item in ProductListView.SelectedItems){
-            Sum += item.MinCostForAgent;
-            idList.Add(item.ID);
-        }
-
-        // создаём окно, передавая ему среднюю цену    
-        var NewWindow = new EnterMinCostForAgentWindow(
-            AvgSum / ProductListView.SelectedItems.Count);
-
-        // показываем МОДАЛЬНОЕ окно    
-        NewWindow.ShowDialog();
-    }
-    ```
-
-4. Проверка на ввод только числового значения.
-
-    ```cs
-    // объявляем публичную переменную из которой
-    // будем считывать результат
-    public decimal Result;
-    private void Button_Click(object sender, RoutedEventArgs e)
-    {
-        try
-        {
-            // пробуем сконвертировать в число
-            Result = Convert.ToDecimal(CostTextBox.Text);
-            // при присвоении результата свойству DialogResult модальное окно закрывается
-            DialogResult = true;
-        }
-        catch (Exception)
-        {
-            MessageBox.Show("Стоимость должна быть числом");
-        }
-    }
-    ```
-
-5. Запись новой цены в БД и обновление списка.
-
-    * редактируем пункт 3, добавляя анализ результата
-
-        ```cs
-        if((bool)NewWindow.ShowDialog())
-        {
-            // вся работа с БД у нас в DataProvider-e, 
-            // передаем список идентификаторов и новую цену
-            Globals.DataProvider.SetAverageCostForAgent(idList, NewWindow.Result);
-            // для перерисовки списка продукции просто перечитываем его
-            ProductList = Globals.DataProvider.GetProducts();
-        }
-        ```
-
-    * Описываем метод *SetAverageCostForAgent* в интерфейсе **IDataProvider**:
-
-        ```cs
-        void SetAverageCostForAgent(List<int> ProductIds, decimal NewCost);
-        ```
-
-    * И реализуем его в **MySQLDataProvider**-e:
-
-        ```cs
-        public void SetAverageCostForAgent(List<int> ProductIds, decimal NewCost)
-        {
-            try
-            {
-                Connection.Open();
-                try
-                {
-                    // вместо значений записываем шаблоны
-                    string Query = @"UPDATE 
-                        Product 
-                    SET MinCostForAgent=@MinCostForAgent 
-                    WHERE ID=@ID";
-
-                    // перебираем список идентификаторов
-                    foreach (int item in ProductIds)
-                    {
-                        MySqlCommand Command = new MySqlCommand(Query, Connection);
-                        // заменяем шаблоны параметрами
-                        Command.Parameters.AddWithValue("@MinCostForAgent", NewCost);
-                        Command.Parameters.AddWithValue("@ID", item);
-                        // и выполняем запрос
-                        Command.ExecuteNonQuery();
-                    }
-                }
-                finally
-                {
-                    Connection.Close();
-                }
-            }
-            catch (Exception)
-            {
-            }
-        }
-        ```
-<table style="width: 100%;"><tr><td style="width: 40%;">
-<a href="../articles/cs_pagination.md">Пагинация, сортировка, фильтрация, поиск
-</a></td><td style="width: 20%;">
-<a href="../readme.md">Содержание
-</a></td><td style="width: 40%;">
-<a href="../articles/cs_edit_product.md">Создание, изменение продукции
-</a></td><tr></table>

+ 107 - 105
articles/cs_coloring2.md

@@ -43,7 +43,65 @@
 
 ## Получение данных
 
-Можно в методе получения данных с сервера добавить **Include** для виртуального списка **ProductSales**, но я уже упоминал в лекциях, что списки лучше загружать динамически. Мы это сделаем на следующем шаге, при получении цвета ячейки.
+Для получения количества продаж за последний месяц пишем SQL запрос:
+
+```sql
+SELECT 
+    ProductID, count(*) as qty
+FROM 
+    ProductSale ps
+WHERE 
+    ps.SaleDate > DATE_SUB(NOW(), INTERVAL 1 MONTH) 
+GROUP BY 
+    ProductID;
+```
+
+Функция **DATE_SUB** вычитает из указанной даты (**NOW** - сейчас) нужное количество месяцев (можно в интервале указать дни, минуты и т.д.)
+
+И добаляем этот подзапрос в наш **ProductView**:
+
+```sql
+CREATE VIEW ProductView AS
+SELECT 
+    -- выбираю все поля из Product
+	p.*,
+	pt.TitleType AS ProductTypeTitle,
+	qq.MaterialCost, 
+    qq.MaterialString,
+    -- добавляю поле "количество продаж за последний месяц"
+	ps.LastMonthSaleQuantity
+FROM 
+	Product p
+LEFT JOIN 
+    ProductType pt ON p.ProductTypeID = pt.ID
+LEFT JOIN 
+    (
+        SELECT 
+            pm.ProductID, 
+            SUM(pm.`Count` * m.Cost) AS MaterialCost,
+            GROUP_CONCAT(m.Title, ',') AS MaterialString
+        FROM 
+            ProductMaterial pm,
+            Material m 
+        WHERE
+            pm.MaterialID=m.ID
+        GROUP BY 
+            pm.ProductID	
+    ) qq ON qq.ProductID=p.ID
+-- добавляю подзапрос для выборки количества продаж за последний месяц    
+LEFT JOIN 
+    (
+        SELECT 
+            ProductID, 
+            count(*) as LastMonthSaleQuantity
+        FROM 
+            ProductSale
+        WHERE 
+            SaleDate > DATE_SUB(NOW(), INTERVAL 1 MONTH) 
+        GROUP BY 
+            ProductID
+    ) ps ON ps.ProductID=p.ID;
+```
 
 ## Раскраска по условию
 
@@ -61,25 +119,14 @@
 И добавляем в модель **Product** геттер *BackgroundColor*
 
 ```cs
+public int? LastMonthSaleQuantity { get; set; }
 public string BackgroundColor
 {
     get
     {
-        // вычисляем дату для сравнения ("сегодня" минус 30 дней)
-        var compareDate = DateTime.Now.AddDays(-30);
-        
-        using (var context = new esmirnovContext())
-        {
-            // ищем количество продаж, совершённых позже указанной даты
-            // фильтруя продажи по Id продукта
-            var saleCount = context.ProductSales
-                .Where(ps => ps.ProductId==this.Id)
-                .Count(ps => ps.SaleDate >= compareDate);
-
-            // возвращаем цвет, в зависимости от количества продаж 
-            if (saleCount > 0) return "#fff"; // белый
-            return "#fee"; // розовый
-        }
+        // возвращаем цвет, в зависимости от количества продаж 
+        if (LastMonthSaleQuantity == null || LastMonthSaleQuantity == 0) return "#fee"; // белый
+        return "#fff"; // розовый
     }
 }
 ```
@@ -133,33 +180,23 @@ private void ProductListBox_OnSelectionChanged(
 
 ### Отображение кнопки "Изменить стоимость на ..."
 
-Сначала просто добавляем эту кнопку в разметку (можно в панель управления, можно в левую панель)
+Сначала просто добавляем эту кнопку в разметку (можно в панель управления, можно в левую панель) и, чтобы видимость кнопки зависела от количества выделенных элементов, мы привязываем атрибут *Visibility* к свойству *costChangeButtonVisible*
 
 ```xml
 <Button
     x:Name="CostChangeButton"
-    IsVisible="True"
-    Content="Изменить стоимость на..."
-    />
-```
-
-Теперь, чтобы видимость кнопки зависела от количества выделенных элементов, мы привязываем атрибут *IsVisible* к свойству *costChangeButtonVisible* (дописав аттрибут `DataType`)
-
-```xml
-<Button
-    x:Name="CostChangeButton"
-    x:DataType="system:Boolean"
-    IsVisible="{Binding #root.costChangeButtonVisible}"
-    Content="Изменить стоимость на..."
-    />
+    Visibility="{Binding costChangeButtonVisible}"     Content="Изменить стоимость на..."
+/>
 ```
 
 И реализуем это свойство в коде ОКНА
 
 ```cs
-public string costChangeButtonVisible {
-    get {
-        return productsSelectedCount > 1;
+public string costChangeButtonVisible
+{
+    get
+    {
+        return productsSelectedCount > 1 ? "Visible" : "Hidden";
     }
 }
 ```
@@ -173,11 +210,10 @@ public string costChangeButtonVisible {
     ```xml
     <Button
         x:Name="CostChangeButton"
-        x:DataType="system:Boolean"
-        IsVisible="{Binding #root.costChangeButtonVisible}"
-        Click="CostChangeButton_OnClick"
-        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+        Visibility="{Binding costChangeButtonVisible}"
         Content="Изменить стоимость на..."
+        Click="CostChangeButton_Click"
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     />
     ```
 
@@ -222,18 +258,36 @@ public string costChangeButtonVisible {
 
     **Во-первых**, в конструктор добавляем параметр (средняя цена). **Во-вторых**, записываем эту цену в текстовое поле.
 
-1. По условиям задачи мы должны вычислить среднюю цену для выделенных элементов списка (я на вскидку не нашёл как преобразовать **IList** в **IEnumerable**, поэтому тупо перебираем список выбранных элементов, считаем сумму и, заодно, собираем список идентификаторов для последующего переопределения цены)
+    И пишем обработчик клика на кнопку "Изменить":
+
+    ```cs
+    // объявляем публичную переменную из которой
+    // будем считывать результат
+    public decimal Result;
+
+    private void Button_Click(object sender, RoutedEventArgs e)
+    {
+        try
+        {
+            // пробуем сконвертировать в число
+            Result = Convert.ToDecimal(CostTextBox.Text);
+            // при присвоении результата свойству DialogResult модальное окно закрывается
+            DialogResult = true;
+        }
+        catch (Exception)
+        {
+            MessageBox.Show("Стоимость должна быть числом");
+        }
+    }
+    ```
 
-    >В **авалонии** окна запускаются асинхронно, поэтому если мы хотим получить результат выполнения диалога (а нам нужна сумма), то нужно использовать конструкцию *async/await*: метод, в котором нужно ждать выполнения асинхронной задачи должен иметь модификатор **async**, а перед функцией открытия окна должен быть модификатор **await**
+1. По условиям задачи мы должны вычислить среднюю цену для выделенных элементов списка (я на вскидку не нашёл как преобразовать **IList** в **IEnumerable**, поэтому тупо перебираем список выбранных элементов, считаем сумму и, заодно, собираем список идентификаторов для последующего переопределения цены)
 
     ```cs
     /* 
         обработчик события клика по кнопке "Изменить стоимость на..." в главном окне
-        так как нам нужно дождаться результата ввода, то запускаем асинхронно
     */
-    async private void CostChangeButton_OnClick(
-        object? sender, 
-        RoutedEventArgs e)
+    private void CostChangeButton_Click(object sender, RoutedEventArgs e)    
     {
         decimal sum = 0;
         List<int> idList = new List<int>();
@@ -246,32 +300,8 @@ public string costChangeButtonVisible {
         var newWindow = new EnterMinCostForAgentWindow(
             sum / ProductListBox.SelectedItems.Count);
 
-        // показываем МОДАЛЬНОЕ окно и ЖДЕМ результат
-        var res = await newWindow.ShowDialog<decimal?>(this);
-    }
-    ```
-
-    >Обратите внимание, в угловых скобках после *ShowDialog* указывается тип результата, возвращаемый окном. Результата может и не быть (хакрыли окно крестиком), поэтому тип нуллабельный.
-
-1. Проверка на ввод только числового значения.
-
-    ```cs
-    private void Button_OnClick(
-        object? sender, 
-        RoutedEventArgs e)
-    {
-        try
-        {
-            // пробуем сконвертировать в число
-            decimal Result = Convert.ToDecimal(CostTextBox.Text);
-            // при присвоении результата свойству DialogResult модальное окно закрывается
-            Close(Result);
-        }
-        catch (Exception)
-        {
-            // в авлонии нет MessageBox, поэтому его надо реализовать как обычное окно
-            (new MessageBox("Стоимость должна быть числом")).ShowDialog(this);
-        }
+        // показываем МОДАЛЬНОЕ окно
+        newWindow.ShowDialog();
     }
     ```
 
@@ -279,47 +309,19 @@ public string costChangeButtonVisible {
 
 1. Запись новой цены в БД и обновление списка.
 
-    Редактируем пункт 3, добавляя анализ результата модального окна
+    Добавляем анализ результата модального окна
 
     ```cs
-    ...
-    var res = await newWindow.ShowDialog<decimal?>(this);
-    if (res != null)
+    if ((bool)newWindow.ShowDialog())
     {
-        using (var context = new esmirnovContext())
-        {
-            try
-            {
-                // перебираем выделенные продукты
-                foreach (var id in idList)
-                {
-                    var product = context.Products.Where(p => p.Id == id).FirstOrDefault();
-                    if (product != null)
-                    {
-                        // меняем сумму для агента
-                        product.MinCostForAgent = res ?? 0;
-                    }
-                }
-                // сохраняем изменения
-                context.SaveChanges();
-            }
-            catch (Exception exception)
-            {
-                (new MessageBox(exception.Message)).ShowDialog(this);
-            }
-
-            /* 
-                перечитываем список продукции (так как эта операция повторяется, 
-                то лучше её вынести в отдельный метод)
-            */
-            productList = context.Products
-                .Include(product => product.ProductType)
-                .Include(product => product.ProductMaterials)
-                .ToList();
-        }
+        Globals.dataProvider.setMinCostForAgent(
+            newWindow.Result, 
+            idList.ToArray());
     }
     ```
 
+    Этот код **во-первых** стоит завернуть в блок **try..catch**, чтобы приложение не падало при ошибках и, **во-вторых** после сохранения суммы вывести MessageBox с сообщением, что сумма успешно изменена.
+
 ---
 
 Предыдущая лекция |  | Следующая лекция