|
Создание базы данных. Импорт данных.
|
Содержание
|
---
|
Создание подключения к БД MySQL. Получение данных с сервера.
Дальше мы продолжим разбор задания прошлогоднего демо-экзамена.
Базу мы развернули и данные в неё импортировали, теперь начнём разбор второй сессии: создание desktop-приложения.
Разработка desktop-приложений
Список продукции
Необходимо реализовать вывод продукции, которая хранится в базе данных, согласно предоставленному макету (файл product_list_layout.jpg находится в ресурсах). При отсутствии изображения необходимо вывести картинку-заглушку из ресурсов (picture.png).

...
Стоимость продукта должна быть рассчитана исходя из используемых материалов.
По макету видно, что на первом экране уже нужны все данные, которые мы импортировали ранее: список продуктов (Product), список материалов (Material) продукта (через таблицу ProductMaterial).
Сразу оговорюсь, что получать данные с сервера можно по-разному: можно через DataAdapter загрузить данные в DataSet и привязать его к компоненту отображающему данные:
private DataSet MyDataSet;
...
MySqlDataAdapter mda = new MySqlDataAdapter(
"SELECT * FROM Product",
Connection);
productAdapter.Fill(MyDataSet, "Product");
ProductListView.DataContext = MyDataSet.Tables["Product"].DefaultView;
А можно используя DataReader заполнять список моделей , что мы и будем дальше делать Мы попробуем реализовать оба варианта.
В рамках демо-экзамена требуется работать с моделями ("Основные сущности представлены отдельными классами", но стоит это всего 0,2 балла).
Реализация с помощью моделей и DataReader-а.
Шаблон приложения берём из лекций прошлого года.
Первым делом рисуем модели для данных. Если в прошлом году вы их разрабатывали сами, то сейчас придумывать ничего не надо - просто смотрим на структуру таблиц:
Модель "Продукт"

public class Product
{
public int ID { get; set; }
public string Title { get; set; }
public int ProductTypeID { get; set; }
public string ArticleNumber { get; set; }
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 class Material
{
public int ID { get; set; }
public string Title { get; set; }
public int CountInPack { get; set; }
public string Unit { get; set; }
public double CountInStock { get; set; }
public double MinCount { get; set; }
public string Description { get; set; }
public decimal Cost { get; set; }
public string Image { get; set; }
public int MaterialTypeID { get; set; }
}
Получение данных из базы
Создаем интерфейс поставщика данных (пока только для продукции)
interface IDataProvider
{
IEnumerable<Product> GetProducts();
}
Создаем класс MySqlDataProvider, реализующий этот интерфейс
class MySQLDataProvider: IDataProvider
{
// соединение с базой данных
private MySqlConnection Connection;

// в конструкторе создаём подключение и сразу его открываем
public MySQLDataProvider()
{
try
{
Connection = new MySqlConnection("Server=kolei.ru;Database=ТУТ ВАША БАЗА;port=3306;UserId=ТУТ ВАШ ЛОГИН;password=ТУТ ПАРОЛЬ;");
Connection.Open();
}
catch (Exception)
{
}
}
// в деструкторе закрываем соединение
~MySQLDataProvider()
{
Connection.Close();
}
// реализуем метод получения списка продукции
public IEnumerable<Product> GetProducts()
{
List<Product> ProductList = new List<Product>();
// выбираем ВСЕ записи
// в реальных приложенияъ этого делать, конечно нельзя
// но у нас базы маленькие, поэтому условиями не заморачиваемся
MySqlCommand Command = new MySqlCommand(
"SELECT * FROM Product",
Connection);
// создаем DataReader, который и будет читать данные из базы
MySqlDataReader Reader = Command.ExecuteReader();
try
{
while(Reader.Read())
{
// для каждой строки таблицы Product создаем экземпляр
// соответствующей модели, заполняем её
Product NewProduct = new Product();
NewProduct.ID = Reader.GetInt32("ID");
NewProduct.Title = Reader.GetString("Title");
NewProduct.ProductTypeID = Reader.GetInt32("ID");
NewProduct.ArticleNumber = Reader.GetInt32("ID");
NewProduct.ProductionPersonCount = Reader.GetInt32("ID");
NewProduct.ProductionWorkshopNumber = Reader.GetInt32("ID");
NewProduct.MinCostForAgent = Reader.GetInt32("ID");
// Методы Get<T> не поддерживают работу с NULL
// для полей, в которых может встретиться NULL (а лучше для всех)
// используйте следующий синтаксис
NewProduct.Description = Reader["Description"].ToString();
NewProduct.Image = Reader["Image"].ToString();
// и сохраняем в списке
ProductList.Add(NewProduct);
}
}
catch (Exception)
{
}
return ProductList;
}
В конструкторе главного окна создаем поставщика данных и получаем с помощью него список продукции
public MainWindow()
{
InitializeComponent();
DataContext = this;
Globals.DataProvider = new MySQLDataProvider();
var ProductList = Globals.DataProvider.GetProducts();
...
В вёрстке главного окна пока выведем обычный DataGrid, чтобы проверить, всё-ли нормально
<DataGrid
Grid.Row="1"
Grid.Column="1"
CanUserAddRows="False"
AutoGenerateColumns="False"
ItemsSource="{Binding ProductList}">
<DataGrid.Columns>
<DataGridTextColumn
Header="Название"
Binding="{Binding Title}"/>
<DataGridTextColumn
Header="Артикул"
Binding="{Binding ArticleNumber}"/>
<DataGridTextColumn
Header="Описание"
Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>

Реализация с помощью DataAdapter
Модели в этом варианте рисовать не надо, сразу делаем получение данных
Интерфейс поставщика данных
interface IDataProvider2
{
DataView GetProducts();
}
Класс MySqlDataProvider2, реализующий этот интерфейс.
Конструктор и деструктор не отличаются, а вот получение данных намного проще:
class MySQLDataProvider2 : IDataProvider2
{
private MySqlConnection Connection;
private DataSet MyDataSet;
public MySQLDataProvider2()
{
try
{
Connection = new MySqlConnection("Server=kolei.ru;Database=ТУТ ВАША БАЗА;port=3306;UserId=ТУТ ВАШ ЛОГИН;password=ТУТ ПАРОЛЬ;");
MyDataSet = new DataSet();
}
catch (Exception)
{
}
}
public DataView GetProducts()
{
try
{
Connection.Open();
MySqlDataAdapter productAdapter = new MySqlDataAdapter(
"SELECT FROM Product", Connection);
productAdapter.Fill(MyDataSet, "Product");
return MyDataSet.Tables["Product"].DefaultView;
}
finally
{
Connection.Close();
}
}
}
Конструктор главного экрана
public MainWindow()
{
InitializeComponent();
DataContext = this;
Globals.DataProvider2 = new MySQLDataProvider2();
// данные привязываем к контексту визуального компонента
ProductListGrid.DataContext = Globals.DataProvider2.GetProducts();
}
Вёрстка практически не отличается, только в ItemsSource используем свойство Table класса DataView
<DataGrid
Grid.Row="1"
Grid.Column="1"
CanUserAddRows="False"
AutoGenerateColumns="False"
Name="ProductListGrid"
ItemsSource="{Binding Table}">
<DataGrid.Columns>
<DataGridTextColumn
Header="Название"
Binding="{Binding Title}"/>
<DataGridTextColumn
Header="Артикул"
Binding="{Binding ArticleNumber}"/>
<DataGridTextColumn
Header="Описание"
Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>
Получение связанных данных (словари и связи многие-ко-многим)
Нам нужно:
- вывести тип продукта
- подсчитать сумму материалов
- вывести список материалов
Для получения связанных данных из DataSet-а я нашел два варианта:
- создание связей в таблицах набора данных и использование вычисляемых полей
- использование конвертера данных
Cоздание связей в таблицах набора данных и использование вычисляемых полей
На примере таблицы ProductType
Загружаем таблицу типов продуктов в набор данных
MySqlDataAdapter productTypeAdapter = new MySqlDataAdapter(
"SELECT * FROM ProductType",
Connection);
productTypeAdapter.Fill(MyDataSet, "ProductType");
У нас теперь в наборе данных есть таблицы Product и ProductType, добавляем связь между ними:
В глобальном классе создаем свойство для хранения созданной связи
// Globals.cs
public static DataRelation ProductTypeRelation;
В конструкторе поставщика данных после создания набора данных добавляем связь
Globals.ProductTypeRelation = MyDataSet.Relations.Add(
// название связи
"ProductTypeRelation",
// родительское поле
MyDataSet.Tables["ProductType"].Columns["ID"],
// потомок (child)
MyDataSet.Tables["Product"].Columns["ProductTypeID"]);
И в таблицу продукции добавляем вычисляемый столбец
DataColumn ProductTypeTitle = new DataColumn();
ProductTypeTitle.DataType = Type.GetType("System.String");
ProductTypeTitle.ColumnName = "ProductTypeTitle";
ProductTypeTitle.Expression = "Parent.Title";
^^^^^^^^^^^^ - связь с "родителем"
MyDataSet.Tables["Product"].Columns.Add(ProductTypeTitle);
В разметке таблицы добавляем отображение вычисляемого поля
<DataGridTextColumn
Header="Тип продукта"
Binding="{Binding ProductTypeTitle}"/>
Для словарей код ещё не очень сложный, но для того, чтобы добраться до суммы материалов нужно добавлять две связи (напомню, что там у нас связь многие-ко-многим) через промежуточную таблицу. И если для суммы материалов ещё более менее понятно как достать данные (хотя и муторно), то как получить список материалов я пока не представляю.
Использование конвертера данных
В разметке таблицы добавляем поле с конвертером
Сначала описываем ресурс:
<Window.Resources>
<local:ProductTypeConverter x:Key="myConverter"/>
</Window.Resources>
Здесь ProductTypeConverter класс, который будет использоваться для конвертирования данных, myConverter - алиас этого конвертера для использования в разметке
И для вывода типа продукта используем конвертер
<DataGridTextColumn
Header="Описание"
Binding="{Binding ProductTypeID,Converter={StaticResource myConverter}}"/>
Реализуем конвертер данных
public class ProductTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// тут старый вариант, использующий связи
// return (value as DataRowView).Row.GetParentRow(Globals.ProductTypeRelation)["Title"];
// LINQ-запрос для получения названия типа продукта по его ID
var Title = Globals.MyDataSet.Tables["ProductType"].AsEnumerable()
.Where(t => t.Field<int>("ID") == (int)value)
.Select(t => t.Field<string>("Title"))
.ToArray();
if (Title.Count() > 0) return Title[0];
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
И так для каждого вычисляемого поля...
Например, так выглядит получение списка материалов (естественно, в набор данных надо загрузить таблицы ProductMaterial и Material)
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// выбираем идентификаторы материалов
var MT = Globals.MyDataSet.Tables["ProductMaterial"].AsEnumerable()
.Where(t => t.Field<int>("ProductID") == (int)value)
.Select(t => t.Field<int>("MaterialID"))
.ToList();
// выбираем названия материалов
var Materials = Globals.MyDataSet.Tables["Material"].AsEnumerable()
.Where(t => MT.Contains(t.Field<int>("ID")))
.Select(t => t.Field<string>("Title"));
var res = "";
foreach (string m in Materials)
res += "," + m;
return res;
}

Для получения суммы материалов вместо Select надо использовать метод Sum
При реализации через классы, достаточно объявить атрибут класса, в геттере которого и реализовать LINQ-запрос для получения связанных данных.
select
p.ID, p.Title,
pt.Title as ProductTypeTitle,
(select GROUP_CONCAT(m.Title separator ', ')
from
Material m,
ProductMaterial pm
where m.ID=pm.MaterialID and p.ID=pm.ProductID) as materials
from
Product p,
ProductType pt
where
p.ProductTypeID = pt.ID ;