Евгений Колесников 1 سال پیش
والد
کامیت
e60c3cb76d
1فایلهای تغییر یافته به همراه75 افزوده شده و 182 حذف شده
  1. 75 182
      articles/cs_mysql_connection3.md

+ 75 - 182
articles/cs_mysql_connection3.md

@@ -4,7 +4,6 @@
 
 
 # Создание подключения к БД MySQL. Получение данных с сервера.
 # Создание подключения к БД MySQL. Получение данных с сервера.
 
 
-* [Установка Rider](#установка-rider)
 * [Создание проекта, подключение пакетов для работы с БД](#создание-проекта-подключение-пакетов-для-работы-с-бд)
 * [Создание проекта, подключение пакетов для работы с БД](#создание-проекта-подключение-пакетов-для-работы-с-бд)
 * [Создание подключения к БД (контекст)](#создание-подключения-к-бд-контекст)
 * [Создание подключения к БД (контекст)](#создание-подключения-к-бд-контекст)
 * [Получение данных с сервера и вывод на экран](#получение-данных-с-сервера-и-вывод-на-экран)
 * [Получение данных с сервера и вывод на экран](#получение-данных-с-сервера-и-вывод-на-экран)
@@ -17,7 +16,7 @@
 >
 >
 >**Список продукции**
 >**Список продукции**
 >
 >
->Необходимо реализовать вывод продукции, которая хранится в базе данных, согласно предоставленному макету (файл `product_list_layout.jpg` находится в ресурсах). При отсутствии изображения необходимо вывести картинку-заглушку из ресурсов (picture.png).
+>Необходимо реализовать вывод продукции, которая хранится в базе данных, согласно предоставленному макету (файл `product_list_layout.jpg` находится в ресурсах). При отсутствии изображения необходимо вывести картинку-заглушку из ресурсов (`picture.png`).
 >
 >
 >![](../img/product_list_layout.jpg)
 >![](../img/product_list_layout.jpg)
 >
 >
@@ -29,219 +28,113 @@
 
 
 Есть несколько вариантов работы с данными:
 Есть несколько вариантов работы с данными:
 
 
-* ORM Фреймворки (библиотеки) - предпочтительный вариант, его мы и будем дальше использовать.
+* ORM Фреймворки (библиотеки).
 
 
-* Загрузака с помощью **DataAdapter** в наборы данных (**DataSet**). Для наборов данных можно даже установить связи между таблицами. Эта технология широко применялась в эпоху **Windows Forms** (этот способ рассматривался в [старых](./cs_mysql_connection.md#реализация-с-помощью-dataadapter) версиях лекций)
+    **ORM** (Object-Relational Mapping, объектно-реляционное отображение) — технология программирования, суть которой заключается в создании «виртуальной объектной базы данных».
 
 
-* Ещё один устаревший [вариант лекций](./cs_mysql_connection2.md) для **WPF** "заточен" на работу с объектами, в котором модели создавались вручную и данные в них грузились с помощью SQL-запросов и **DataReader**-a
+    Вариантов таких фреймворков много:
+    
+    1. **EntityFramework**
 
 
-**ORM** (Object-Relational Mapping, объектно-реляционное отображение) — технология программирования, суть которой заключается в создании «виртуальной объектной базы данных».
+        Благодаря этой технологии разработчики могут использовать язык программирования, с которым им удобно работать с базой данных, вместо написания операторов SQL или хранимых процедур. Это может значительно ускорить разработку приложений, особенно на начальном этапе. ORM также позволяет переключать приложение между различными реляционными базами данных. Например, приложение может быть переключено с MySQL на PostgreSQL с минимальными изменениями кода.
 
 
-Благодаря этой технологии разработчики могут использовать язык программирования, с которым им удобно работать с базой данных, вместо написания операторов SQL или хранимых процедур. Это может значительно ускорить разработку приложений, особенно на начальном этапе. ORM также позволяет переключать приложение между различными реляционными базами данных. Например, приложение может быть переключено с MySQL на PostgreSQL с минимальными изменениями кода.
+    1. **Dapper**
 
 
-Ранее мы использовали **.NET Framework** + **WPF**, но с этого года перейдем на **.NET Core** + **Avalonia**.
+        Как показал опыт, **EntityFramework** очень громоздкий и не всегда понятный.
 
 
-**Во-первых**, **.NET Framework** работает только под ОС Windows и больше не поддерживается (последняя поддерживаемая версия C# - 4.7.2).
+        Есть более легковесные альтернативы, например **Dapper**. Это микро-фреймворк, который тоже может результат SQL-запроса поместить в модель, но при этом модель мы должны "нарисовать" сами и знать SQL-синтаксис.
 
 
-**Во-вторых**, ORM EntityFramewok не "дружит" с последними версиями MySQL коннектора
+* Загрузака с помощью **DataAdapter** в наборы данных (**DataSet**). Для наборов данных можно даже установить связи между таблицами. Эта технология широко применялась в эпоху **Windows Forms**
 
 
-**.NET Core** - кроссплатформенная библиотека, поддерживает последние версии языка и ORM EntityFramewokCore нормально работает с MySQL.
+## Примеры работы с Dapper
 
 
-**WPF** заменим на **Avalonia**, пока ограничимся ОС Windows, но в перспективе перейдём и на Linux/MacOS.
+1. В приложении прописать строку подключения (где-нибудь в глобальном статическом классе)
 
 
-В качестве **IDE** будем использовать **JetBarains Rider**. Продует платный. Теоретически для неё есть бесплатные образовательные лицензии, но нам они недоступны. Активируйте ключём (гуглится по запросу "rider activation key", ключи действуют год и могут быть отозваны, поэтому конкретный ключ не указываю).
-
-## Установка Rider
-
-Ссылка для скачивания [Rider](https://www.jetbrains.com/ru-ru/rider/)
-
-Установка Avalonia на Rider
-
-1. Запустить собственно сам Rider
-
-1. В основном окне выбрать вкладку *Configure -> Plugins*
-
-    ![](../img/rider001.png)
-
-1. Установить AvaloniaRider (в строке поиска введите "avalonia")
-
-    ![](../img/rider002.png)
-
-    При установке может выдать сообщение, что плагин разработан не в JetBrains и использовать на свой страх - соглашаемся (Accept)
-
-1. После установки плагина перезагрузите IDE и установите шаблоны проектов для Avalonia. В консоли выполните команду: 
-
-    ```
-    dotnet new install Avalonia.Templates
+    ```cs
+    static string connectionString = "Server=kolei.ru; User ID=esmirnov; Password=111103; Database=esmirnov";
     ```
     ```
 
 
-    >Для .NET 6.0 и более ранних версий замените install на --install.
-    >Версию .NET можно узнать выполнив в консоли команду `dotnet --version`
-
-    Если шаблоны установлены нормально, то в окне создания нового проекта появится секция *Other*:
-
-    ![](../img/rider003.png)    
-
-## Создание проекта, подключение пакетов для работы с БД.
-
-Итак, создадим проект на C# по шаблону *Avalonia .NET Core App*
-
-В *контекстном меню* проекта выберите "Manage NuGet Packages"
-
-![](../img/rider004.png)
-
-* Установите пакет "Microsoft.EntityFrameworkCore.Design"
-
-    ![](../img/rider005.png)
-
-    >Версия пакета должна соответствовать версии .NET. У меня стоит 6-я версия, вам нужно смотреть командой `dotnet --version`
-
-* Установите пакет "MySql.EntityFrameworkCore"
-
-    ![](../img/rider006.png)
-
-    >Версия пакета должна соответствовать версии .NET.
-
-## Создание подключения к БД (контекст).
-
-Для подключения к базе данных нужно создать модели и контекст подключения (адрес сервера, название БД, логин и пароль). Тут есть два варианта:
-
-1. **Application First** (сначала приложение) - в этом варианте модели и контекст подключения создаются руками в приложении, а **ORM** автоматически создает по ним одноимённые сущности в БД. (пример такого подхода расписан в [метаните](https://metanit.com/sharp/entityframeworkcore/7.2.php))
-
-1. **DB First** (сначала база). В этом варианте база уже создана и нам нужен обратный процесс - [реконструировать](https://metanit.com/sharp/entityframeworkcore/1.3.php) модели и контекст по имеющейся БД. Мы будем использовать этот вариант.
-
-В терминале (можно и в системной консоли, но обязательно в каталоге проекта) 
-
-![](../img/rider007.png)
-
-выполните команду (естественно, вписав свои базы и пароли):
-
->Перед этим, возможно, нужно выполнить команду `dotnet tool install --global dotnet-ef` (спасибо Андрею Кропинову).
-
-```
-dotnet ef dbcontext scaffold "server=kolei.ru;database=esmirnov;uid=esmirnov;password=111103;" MySql.EntityFrameworkCore -o esmirnov -f
-```
-параметры команды:
-
-* "строка подключения": здесь вариант для MySQL
-
-    - **server**: доменное имя или IP-адрес севрера (на демо экзамене будет что-то типа 192.168.1.32)
-    - **database**: название базы данных на сервере MySQL. Для лабораторных работ выдаёт преподаватель, на демо экзамене `userXX`
-    - **uid**: логин пользователя (обычно то же, что и название БД, но если у вас свой сервер MySQL, то там вы рулите сами)
-    - **password**: пароль пользователя
+1. Создать модели для нужных таблиц (мне лень все поля переписывать, смысл должен быть понятен).
 
 
-* используемый провайдер БД (MySql.EntityFrameworkCore)
-
-* **-o "Название каталога"**: название каталога в вашем проекте, в котором будут созданы контекст и модели БД
-
-* **-f**: перезаписывать модели (при повторном запуске команды)
-
-Эта команда создает контекст подключения (параметры в кавычках) к провайдеру (второй параметр) MySQL и модели сущностей в папке `esmirnov` вашего проекта.
-
-![](../img/rider008.png)
-
-Например, рассмотрим таблицу **Product**:
-
-```cs
-public partial class Product
-{
-    public Product()
+    ```cs
+    public class Product
     {
     {
-        ProductCostHistories = new HashSet<ProductCostHistory>();
-        ProductMaterials = new HashSet<ProductMaterial>();
-        ProductSales = new HashSet<ProductSale>();
+        public int ID { get; set; }
+        public string Title { get; set; }
     }
     }
+    ```
 
 
-    public int Id { get; set; }
-    public string Title { get; set; } = null!;
-    public int ProductTypeId { get; set; }
-    public string ArticleNumber { get; set; } = null!;
-    public string? Description { get; set; }
-    public string? Image { get; set; }
-    public int? ProductionPersonCount { get; set; }
-    public int? ProductionWorkshopNumber { get; set; }
-    public decimal MinCostForAgent { get; set; }
-
-    public virtual ProductType ProductType { get; set; } = null!;
-    public virtual ICollection<ProductCostHistory> ProductCostHistories { get; set; }
-    public virtual ICollection<ProductMaterial> ProductMaterials { get; set; }
-    public virtual ICollection<ProductSale> ProductSales { get; set; }
-}
-```
-
-1. Для всех полей таблицы созданы свойства класса (*Id*, *Title*...), причём необязательные поля имеют "нуллабельные" типы (**int? ProductionPersonCount**).
-1. Для связей созданы виртуальные свойства или коллекции (в зависимости от направленности связи). 
-
-    * **virtual ProductType ProductType** - тип продукции (ссылка на словарь), отношение много (продуктов) к одному (типу продукта)
-    * **virtual ICollection<ProductMaterial> ProductMaterials** - материалы продукта - коллекция (список) материалов, используемых в этом продукте. Отношение один (продукт) ко многим (материалам продукта) 
-
-## Получение данных с сервера и вывод на экран.
-
-**DataGrid** в **Avalonia** не установлен по умолчанию. Установить можно двумя вариантами:
+1. Чтение данных:
 
 
-* выполнив команду `dotnet add package Avalonia.Controls.DataGrid`. Тут может возникнуть ситуация, когда версия **DataGrid** больше чем версия **Avalonia**. В этом случае проект не *соберется*. Удалите пакет командой `dotnet remove package Avalonia.Controls.DataGrid` и воспользуйтесь вторым вариантом
+    ```cs
+    public List<Product> productList { get; set; }
 
 
-* Добавить ссылку в файл проекта:
+    ...
 
 
-    1. Переключите *Explorer* в режим *File System*:
+    using (MySqlConnection db = new MySqlConnection(connectionString))
+    {
+            productList = db.Query<Product>(
+            "SELECT ID,Title FROM Product")
+            .ToList();
+    }
+    ```
 
 
-        ![](../img/rider009.png) 
+    То есть мы считываем данные сразу в список продукции. **Обратите внимание**, названия полей в БД должны соответствовать свойствам модели. В этом ничего страшного нет, названия полей можно поменять при выборке используя конструкцию `AS`, например: `SELECT id AS ID FROM Product`.
 
 
-    1. Откройте файл проекта *.csproj, найдите строчку `<PackageReference Include="Avalonia" Version="11.0.2" />` (версия у вас может быть другая)
+1. Получение одной записи (здесь и далее непроверенный код из гугла)
 
 
-    1. Добавьте следующей строкой пакет `<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.2" />`
+    ```cs
+    db.Query<User>(
+        "SELECT * FROM Users WHERE Id = @id", new { id })
+        .FirstOrDefault();
+    ```
 
 
-        ![](../img/rider010.png)
+    Тут видно, как выполнять запросы с параметрами, вторым параметром в методе **Query** передаётся объект с параметрами, которые заменяют одноименные литералы в тексте запроса.
 
 
-        >При редактировании версии пакета можно нажать комбинацию клавишь `Ctrl+Пробел` и выйдет подсказка с возможными вариантами версий
+    Естественно, в качестве такого объекта может быть экземпляр соответствующей модели (см. следующий запрос)
 
 
-После добавления **DataGrid** нужно добавить стили в файл `App.axaml` (без них *контрол* не работает):
+1. Добавление записи в таблицу (в этом и последующих запросах результат не нужен, поэтому используется метод **Execute**)
 
 
-```xml
-<Application.Styles>
-    <FluentTheme />
-    <!-- стиль для DataGrid -->
-    <StyleInclude 
-        Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
-</Application.Styles>
-``` 
+    ```cs
+    db.Execute(
+        "INSERT INTO Users (Name, Age) VALUES (@Name, @Age)", 
+        user);
+    ```
 
 
-**ВАЖНО!**
+    Здесь данные для запроса берутся сразу из модели (*user* это экземпляр класса **User**)
 
 
-В последней версии **Авалонии** (11) привязка (*binding*) не работает, если не указать тип данных используемых в контроле (DataGrid, ListBox ...). Выдаёт ошибку *"Unable to infer DataContext type for compiled binding"*
+1. Редактирование записи:
 
 
-Типы данных у нас находятся в другом пространстве имён и для того, чтобы они стали доступны в текущем окне нужно добавить использование нужного пространства имён:
+    ```cs
+    db.Execute(
+        "UPDATE Users SET Name = @Name, Age = @Age WHERE Id = @Id", 
+        user);
+    ```
 
 
-```xml
-<Window
-   xmlns:model="using:AvaloniaApplication1.esmirnov"
-```
+1. Удаление записи:
 
 
-После чего, добавить в контрол аттрибут `x:DataType`:
+    ```cs
+    db.Execute(
+        "DELETE FROM Users WHERE Id = @id", 
+        new { id });
+    ```
 
 
-```xml
-<DataGrid 
-    x:DataType="model:Product"
-```
+## Создание проекта, подключение пакетов для работы с БД.
 
 
-После этого у нас ломается текущий контекст и выдаёт ошибку, что не видит свойство _productList_: _"Unable to resolve field or property 'productList' in data context of type 'AvaloniaApplication1.esmirnov.Product'"_
+Рассмотрим реализацию задания по выводу списка продукции.
 
 
-Чтобы это поправить, нужно указывать путь от корня окна. Для этого в окно добавляем аттрибут _Name_:
+1. Создайте **WPF** проект.
 
 
-```xml
-<Window
-   xmlns:model="using:AvaloniaApplication1.esmirnov"
-   Name="root"
-```
+1. Через **NuGet** или в консоли установить пакеты: **MySqlConnector** и **Dapper**
 
 
-И в **DataGrid**-e в привязке указываем путь от корня окна:
+    ```
+    dotnet add package MySqlConnector
+    dotnet add package Dapper
+    ```
 
 
-```xml
-x:DataType="model:Product"
-ItemsSource="{Binding #root.productList}"
-```
+<!-- ## Получение данных с сервера и вывод на экран. -->
 
 
-Для демонстрации работы в `MainWindow.axaml` добавим DataGrid:
+<!-- Для демонстрации работы в `MainWindow.xaml` добавим DataGrid:
 
 
 ```xml
 ```xml
 <DataGrid 
 <DataGrid 
@@ -265,9 +158,9 @@ ItemsSource="{Binding #root.productList}"
         />
         />
     </DataGrid.Columns>
     </DataGrid.Columns>
 </DataGrid>
 </DataGrid>
-```
+``` -->
 
 
-и в коде проекта (`MainWindow.axaml.cs`) получим данные из БД:
+<!-- Полученик данные из БД:
 
 
 ```cs
 ```cs
 public partial class MainWindow : Window
 public partial class MainWindow : Window
@@ -287,11 +180,11 @@ public partial class MainWindow : Window
 }
 }
 ```
 ```
 
 
->Обратите внимание, *context.Products* это не модель, а виртуальный **DbSet** (коллекция сущностей), объявленный в классе контекста: `public virtual DbSet<Product> Products { get; set; }`. При чтении этой коллекции как раз и происходит обращение к БД (посылка SQL-команд)
+>Обратите внимание, *context.Products* это не модель, а виртуальный **DbSet** (коллекция сущностей), объявленный в классе контекста: `public virtual DbSet<Product> Products { get; set; }`. При чтении этой коллекции как раз и происходит обращение к БД (посылка SQL-команд) -->
 
 
-**Всё работает!!!**
+<!-- **Всё работает!!!**
 
 
-![](../img/rider011.png)
+![](../img/rider011.png) -->
 
 
 ---
 ---