83
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ федеральное государственное бюджетное образовательное учреждение высшего образования «УЛЬЯНОВСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» А.В. Лылова, Р.С. Молотов, А.А. Сахнов ИСПОЛЬЗОВАНИЕ ENTITY FRAMEWORK В ПРОЕКТЕ С POSTGRESQL Практикум по дисциплине «Базы данных» для студентов направления 09.03.01 «Информатика и вычислительная техника» Ульяновск УлГТУ 2016

ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

  • Upload
    others

  • View
    20

  • Download
    0

Embed Size (px)

Citation preview

Page 1: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ федеральное государственное бюджетное образовательное

учреждение высшего образования «УЛЬЯНОВСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ»

А.В. Лылова, Р.С. Молотов, А.А. Сахнов

ИСПОЛЬЗОВАНИЕ

ENTITY FRAMEWORK

В ПРОЕКТЕ С POSTGRESQL

Практикум

по дисциплине «Базы данных»

для студентов направления 09.03.01

«Информатика и вычислительная техника»

Ульяновск УлГТУ

2016

Page 2: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

УДК 004.657 (076) ББК 32.97 я7 Л 88

Рецензент: кандидат технических наук, доцент кафедры «Информационные системы» факультета информационных систем и технологий Ульяновского государственного технического университета В. В. Воронина.

Рекомендовано научно-методическим советом факультета

информационных систем и технологий в качестве практикума

Лылова, А. В.

Л 88 Использование Entity Framework в проекте с PostgreSQL: Практикум по дисциплине «Базы данных» для студентов направления 09.03.01 «Информатика и вычислительная техника» / А. В. Лылова, Р. С. Молотов, А. А. Сахнов. – Ульяновск : УлГТУ, 2016. – 82 с.

Рассматривается применение Entity Framework – объектно-ориентированной технологии доступа к данным (ORM-решения) для разработки клиентского приложения к СУБД PostgreSQL. Шаг за шагом описываются этапы работы: от создания модели БД до дизайна GUI и реализации экспорта данных.

Данный практикум предназначен для выполнения заключительных лабораторных работ по дисциплине «Базы данных» бакалаврами направления 09.03.01 «Информатика и вычислительная техника», а также студентами других направлений, изучающих данную дисциплину.

Работа подготовлена на кафедре «Вычислительная техника».

УДК 004.657 (076) ББК 32.97 я7

© Лылова А. В., Молотов Р. С., Сахнов А. А., 2016 © Оформление. УлГТУ, 2016

Page 3: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

ОГЛАВЛЕНИЕ Введение ...................................................................................................... 4

Глоссарий .................................................................................................... 7

1. Обзор Entity Framework ....................................................................... 14

2. Серверное приложение и соединение с ним...................................... 19

2.1. Описание структуры исходной базы данных .............................. 19

2.2. Добавление EF в проект ................................................................ 22

2.3. Создание модели EDM .................................................................. 24

Задание по варианту .............................................................................. 27

Контрольные вопросы .......................................................................... 27

3. Клиентское приложение ...................................................................... 28

3.1. Организация ограничений доступа .............................................. 28

3.2. Реализация работы со списком пациентов .................................. 34

3.3. Реализация работы со списком лекарств ..................................... 46

3.4. Реализация работы со списком назначений ................................ 53

3.4.1. Получение данных из представления .................................... 55

3.4.2. Получение данных с помощью LINQ-запроса ..................... 58

Задание по варианту .............................................................................. 68

Контрольные вопросы .......................................................................... 68

4. Формирование отчетов ........................................................................ 70

4.1. Экспорт данных в XLS формат ..................................................... 70

Задание по варианту .............................................................................. 77

Контрольные вопросы .......................................................................... 77

5. Задания к лабораторным работам ....................................................... 78

Заключение ................................................................................................ 80

Библиографический список ..................................................................... 81

Основная литература............................................................................. 81

Дополнительная литература ................................................................. 81

Интернет-ресурсы ................................................................................. 81

3

Page 4: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

ВВЕДЕНИЕ

В ходе выполнения первых шести лабораторных работ по дисциплине «Базы данных» рассматривалась исключительно серверная часть клиент-серверной информационной системы (ИС). Для того чтобы конечный пользователь мог иметь доступ к информации, хранящейся на сервере необходимо специальное приложение, генерирующее на основе действий пользователя запросы к базе данных (БД) и предоставляющее ему данные, пришедшие в ответ. В реальных проектах серверное и клиентское приложения могут разрабатываться не одновременно. Зачастую, как и в случае с ходом лабораторных работ к данной дисциплине, требуется разработать клиентское приложение для уже существующей и определенное время функционирующей БД.

Поэтому в контексте данного учебного практикума как методических указаний к соответствующим лабораторным работам по дисциплине «Базы данных» будет рассмотрен подход Database First, подразумевающий написание клиентского приложения для уже существующей базы данных. И, на основе БД, полученной в результате выполнения первых шести лабораторных работ по данной дисциплине, шаг за шагом, на примере типового варианта, будет реализовано клиентское приложение для работы с этой БД.

Разработка клиентского приложения действительно заслуживает особого внимания: именно его будет видеть конечный пользователь, именно его проектирование и разработка занимают основную часть времени работы над проектом. Как упростить эту задачу и сократить количество «человеко-часов»? Какие технологии и средства использовать?

4

Page 5: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Выделим три основных пункта, к которым мы будем стремиться при разработке указанного выше типа программного обеспечения (ПО):

1) упрощение проектирования и разработки приложения для обслуживания существующей базы данных;

2) облегчение сопровождения проекта; 3) создание удобного для пользователя GUI (Graphical user interface,

графический пользовательский интерфейс).

Исходя из этих пунктов, логично выбрать объектно-ориентированный язык и популярную платформу разработки. Также будет иметь смысл отойти от оперирования прямыми SQL-запросами – такой способ хоть и самый производительный, но он гораздо сложнее в плане отладки и сопровождения кода. Куда проще оперировать не таблицами, индексами и ключами, а объектами. Это позволит осуществлять поддержку и разработку приложения на уровне языка, практически не касаясь самой базы данных и языка SQL.

Далее предлагается рассмотреть платформу .NET и объектно-ориентированную технологию для работы с данными Entity Framework (EF). EF в значительной степени упрощает доступ к данным, избавляет разработчика от необходимости написания связанной с этим части кода, насыщенную SQL-запросами. Неоспоримым преимуществом EF является возможность экономии времени разработки проекта за счет автоматической генерации сущностных классов по имеющейся БД. Кроме того, он позволяет манипулировать объектами БД точно также, как объектами языка программирования. Для разработчиков это дает дополнительные удобства. В сочетании же с прочими возможностями платформы .NET использование Entity Framework позволяет в разы повысить эффективность разработки проектов. Поэтому рассмотрение студентами данной технологии является актуальной задачей.

5

Page 6: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Данный практикум ориентирован на знакомство с ORM-технологиями и применение EF в ходе освоения дисциплины «Базы данных». В связи с этим структура практикума соответствует декомпозиции задания на соответствующие лабораторные работы.

6

Page 7: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

ГЛОССАРИЙ

.NET – платформа разработки общего назначения для любых приложений или рабочей нагрузки, представляющая ключевые возможности для создания приложений высокого качества и включающая в себя автоматическое управление памятью и поддержку современных языков программирования.

Active record (AR) – шаблон проектирования, реализация которого заключается в создании для каждой таблицы специального класса AR, являющегося отражением (представлением) таблицы, таким образом, что:

каждый экземпляр данного класса соответствует одной записи таблицы;

при создании нового экземпляра класса (и заполнении соответствующих полей) в таблицу добавляется новая запись;

при чтении полей объекта считываются соответствующие значения записи таблицы баз данных;

при изменении (удалении) какого-либо объекта изменяется (удаляется) соответствующая ему запись.

API – набор готовых классов, процедур, функций, структур и констант, предоставляемых приложением (библиотекой, сервисом) для использования во внешних программных продуктах.

CASE-средства – набор инструментов и методов программной инженерии для проектирования программного обеспечения, который помогает обеспечить высокое качество программ, отсутствие ошибок и простоту в обслуживании программных продуктов.

Entity Framework – объектно-ориентированная технология доступа к данным (ORM-решение).

7

Page 8: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Entity-relationship model (ERM) – модель данных, позволяющая описывать концептуальные схемы предметной области.

ER-диаграмма – графическая нотация, способ визуализации ERM.

Graphical user interface (GUI) – графический пользовательский интерфейс, элементы которого представлены в виде графических изображений.

Integrated development environment (IDE) – комплекс программных средств, используемый программистами для разработки программного обеспечения.

IntelliSense – технология автодополнения. Дописывает название функции при вводе начальных букв. Кроме прямого назначения используется для доступа к документации и для устранения неоднозначности в именах переменных, функций и методов, используя рефлексию.

LINQ-запрос – выражение, написанное на языке интегрированных запросов, извлекающее данные из источника данных.

Npgsql – поставщик данных ADO.NET для PostgreSQL. Позволяет .NET-разработчикам получать доступ к данным и серверу PostgreSQL.

NPOI – .NET-реализация библиотеки Java Apache POI (the Java API for Microsoft Documents), позволяющей работать с документами Word и Excel.

NuGet – менеджер пакетов, расширение для Visual Studio.

Object Linking and Embedding (OLE) – технология связывания и внедрения объектов в другие документы и объекты.

Object-relational mapping (ORM) – технология программирования, которая связывает базы данных с концепциями объектно-

8

Page 9: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

ориентированных языков программирования, создавая «виртуальную объектную базу данных».

OpenXML (DOCX) – серия форматов файлов для хранения электронных документов пакетов офисных приложений — в частности, Microsoft Office. Формат представляет собой zip-архив, содержащий текст в виде XML, графику и другие данные.

Repository – паттерн проектирования. Посредничает между уровнями области определения и распределения данных (domain and data mapping layers), используя интерфейс, схожий с коллекциями для доступа к объектам области определения.

SQL-запрос – команда, которая передается серверу базы данных, и которая сообщает ему, что нужно вывести определенную информацию из таблиц в память.

Unit of Work – паттерн проектирования. Обслуживает набор объектов, изменяемых в бизнес-транзакции (бизнес-действии) и управляет записью изменений и разрешением проблем конкуренции данных.

Анонимные типы – позволяют легко инкапсулировать свойства только для чтения в один объект без необходимости предварительного определения типа. Имя типа создается компилятором и недоступно на уровне исходного кода. Тип каждого свойства выводится компилятором.

База данных – организованная в соответствии с определенными правилами и поддерживаемая в памяти компьютера совокупность сведений об объектах, процессах, событиях или явлениях, относящихся к некоторой предметной области, теме или задаче. Она организована таким образом, чтобы обеспечить информационные потребности пользователей, а также удобное хранение этой совокупности данных, как в целом, так и любой ее части [4].

9

Page 10: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Клиент-сервер – вычислительная или сетевая архитектура, в которой задания или сетевая нагрузка распределены между поставщиками услуг, называемыми серверами, и заказчиками услуг, называемыми клиентами. Физически клиент и сервер — это программное обеспечение. Обычно они взаимодействуют через компьютерную сеть посредством сетевых протоколов и находятся на разных вычислительных машинах, но могут выполняться также и на одной машине.

Клиент сущности. Поставщик EntityClient – это поставщик данных, используемый приложениями платформы Entity Framework для доступа к данным, описанным в Entity Data Model (EDM). В EntityClient для доступа к источнику данных используются другие поставщики данных .NET Framework. Поставщик EntityClient реализован в пространстве имен System.Data.EntityClient.

Концептуальная модель – абстрактная спецификация для типов сущностей, сложных типов, ассоциаций, контейнеров сущностей, наборов сущностей и наборов ассоциаций в домене приложения, построенная на основе модели Entity Data Model. Концептуальная модель определяется на языке CSDL в файле концептуальной модели.

Кросс-запрос (также перекрестный запрос, кросс табличное выражение) – операция для выборки данных из двух таблиц. В SQL и LINQ используется оператор (метод) JOIN. При необходимости соединения не двух, а нескольких таблиц, операция соединения применяется последовательно.

Лямбда-выражение – способ записи анонимного метода, включающий в себя список параметров, лексему «=>» и набор операторов, которые будут обрабатывать параметры [3]. Если параметров несколько, то они указываются в скобках, через запятую. Например: (a, b, c) => b * b – 4 * a * c.

10

Page 11: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Метод – функция или процедура, принадлежащая к какому-либо классу или объекту. Могут быть статическими (static), т. е. не иметь доступа к данным объекта, но и не требовать создания его экземпляра для выполнения кода метода.

Модальная форма – форма, которая приостанавливает пользование приложением и пользовательским интерфейсом (выполнение вызвавшей функции, переключение фокуса) до момента своего закрытия, или выполнения пользователем установленных манипуляций.

Объект – некоторая сущность в виртуальном пространстве, обладающая определенным состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов). Как правило, понятие объекта равносильно понятию экземпляра класса.

Паттерн проектирования – повторимая архитектурная конструкция, представляющая собой решение проблемы проектирования в рамках некоторого часто возникающего контекста. Отражает архитектуру решения некоторой задачи в рамках проекта, или всего приложения в целом.

Перегрузка (конструктора) – свойство языка, позволяющее определять собственные конструкторы для объектов. Например, передавать в них необходимые параметры.

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

Печатная форма (отчет) – файл, представляющий собой офисный документ, отчет определенного формата (расширения), который может быть открыт в офисной программе (табличный или текстовый редактор, и т. п.) и выведен на печать.

11

Page 12: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

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

Представление – виртуальная (логическая) таблица, представляющая собой поименованный запрос (синоним к запросу), который будет подставлен как подзапрос при использовании представления.

Роль базы данных – пользователь или группа, обладающая определенными привилегиями – правами на объекты базы данных.

Система управления базами данных (СУБД) – совокупность программных и лингвистических средств общего или специального назначения, обеспечивающих управление созданием и использованием баз данных.

Службы объектов – это компонент платформы Entity Framework, позволяющий выполнять запросы, вставлять, обновлять и удалять данные, представленные как строго типизированные объекты CLR, которые являются экземплярами типов сущности. Службы объектов поддерживают как запросы LINQ, так и запросы Entity SQL к типам, которые определены в модели Entity Data Model (EDM). Службы объектов материализуют возвращаемые данные в виде объектов и переносят изменения объектов обратно в источник данных. Кроме того, они предоставляют средства для отслеживания изменений, привязки объектов к элементам управления и обработки параллелизма. Службы объектов реализуются с помощью классов в пространствах имен System.Data.Objects и System.Data.Objects.DataClasses [7].

Сущностные классы – классы, которые отображаются (ORM) на базу данных с использованием LINQ to SQL. Каждый сущностный класс соответствует определенному отношению (сущности, таблице) в базе данных.

12

Page 13: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Сущность – любой различимый объект (объект, который мы можем отличить от другого), информацию о котором необходимо хранить в базе данных. Сущность (отношение) хранится в БД в виде двумерных таблиц, состоящих из описания атрибутов сущности (названия столбцов) – типа сущности и записей, кортежей – экземпляров сущности.

Форма (элемент интерфейса) – окно или диалоговое окно, составляющее пользовательский интерфейс приложения.

Фреймворк – программная платформа, определяющая структуру программной системы и облегчающая ее разработку. В отличие от библиотеки определяет не только содержание (набор методов, подпрограмм), но и архитектуру решения.

13

Page 14: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

1. ОБЗОР ENTITY FRAMEWORK

Платформа ADO.NET Entity Framework – программная модель, которая позволяет заполнить пробел между конструкциями базы данных и объектно-ориентированными конструкциями. EF позволяет взаимодействовать с реляционными базами данных, не имея дела с кодом SQL: исполняющая среда EF генерирует подходящие операторы SQL, когда разработчик применяет LINQ-запросы к строго типизированным классам. Такие строго типизированные классы называются сущностями. Сущности – это концептуальная модель (модель сущностных данных, EDM - Entity Data Model) физической базы данных, которая отображается на предметную область. EDM представляет собой набор классов клиентской стороны, которые отображаются на физическую базу данных [1]. Однако сущности не обязаны напрямую отображаться на схему базы данных – сущностные классы можно реструктурировать для соответствия существующим потребностям, и исполняющая среда EF отобразит эти уникальные имена на конкретную схему.

Например, существующей в БД таблице пользователей users, созданной соответствующим кодом

Листинг 1.1 CREATE TABLE users

(

Id integer NOT NULL, --Идентификатор пользователя

Name character varying(50) NOT NULL, --Имя пользователя

Age integer NOT NULL, --Возраст пользователя

CONSTRAINT users_pkey PRIMARY KEY (Id) –-Id - первичный

ключ

)

будет соответствовать следующий сущностный класс: Листинг 1.2

public class users {

public int Id { get; set; }

public string Name { get; set; }

public int Age { get; set; } }

14

Page 15: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Важно отметить, что здесь, как и во многих случаях, название класса соответствует названию связанной с ним таблицы. Стоит помнить, что всегда можно изменить сущность для более точного соответствия конкретной ситуации.

Для дальнейшей работы с Entity Framework важно также понимать его структуру, из чего состоит та прослойка между базой данных и кодом C#. Подобно любому взаимодействию ADO.NET, EF использует поставщик данных (DDEX provider) ADO.NET для взаимодействия с хранилищем данных (см. рис. 1). Для отдельных систем управления базами данных (СУБД), вероятно, может потребоваться установка соответствующего DDEX provider. Например, для рассматриваемого далее PostgreSQL необходим NpgsqlDdexProvider. Помимо этого, API-интерфейс EF содержит службы объектов и клиент сущности. Службы объектов управляют сущностями клиентской стороны при работе с ними в коде: отслеживают изменения сущности, управляют отношениями между ними, обеспечивают сохранение изменений в БД и сохранение состояния сущности с помощью сериализации. Клиент сущности отвечает за работу с поставщиком данных для установки соединений, генерации SQL-операторов на основе LINQ-запросов и отображения извлеченных данных на корректные формы сущностей [3].

15

Page 16: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Рис. 1.1. Основные компоненты ADO.NET Entity Framework

Отдельного упоминания заслуживает модель сущностных данных (EDM). Так как EF – ORM (object-relational mapping – объектно-реляционное отображение) решение, то в контексте его API-интерфейса для корректного отображения данных сущностных классов на данные таблиц требуется правильное определение логики отображения. В любой системе, управляемой моделью данных, уровни сущностей, реальной БД и отображения разделены на три связанных части, среди которых:

концептуальная модель, определяющая сущности и отношения между ними;

логическая модель, отображающая сущности и отношения на таблицы с любыми необходимыми ограничениями внешних ключей;

16

Page 17: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

физическая модель, представляющая возможности конкретного механизма данных, указывающая детали хранилища (табличная схема, индексация, разбиение на разделы).

В EF каждый из этих уровней фиксируется в XML-файле.

В результате использования конструктора получается файл с расширением *.edmx, содержащий XML-описания сущностей, физической БД и инструкций по отображению этой информации между концептуальной и физической моделями. При компиляции проекта этот файл применяется для генерации трех отдельных XML-файлов: для концептуальной (*.csdl), физической (*.ssdl) моделей и одного уровня отображения (*.msl). Данные из этих файлов затем объединяются с приложением в виде двоичных ресурсов. При компиляции сборка .NET имеет все необходимые данные для вызовов API-интерфейса EF, имеющихся в коде.

Генерация файла *.edmx дает в результате сущностные классы, которые отображаются на таблицы БД, и класс, расширяющий DbContext из System.Data.Entity, представляющий собой сочетание паттернов Unit Of Work и Repository, которое используется для запросов из БД и группировки вносимых изменений, которые будут добавлены в хранилище как единое целое. DbContext концептуально схож с ObjectContext из System.Data.Objects.

Некоторые рассмотренные выше компоненты можно объединить под именем «Модель», тогда рис. 1.1 можно представить следующим образом (рис. 1.2):

17

Page 18: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Рис. 1.2. Упрощенное представление структуры решения

В связи с этим проще понять предлагаемые способы взаимодействия с базой данных:

1. Database First: Entity Framework создает набор классов, которые отражают модель конкретной существующей базы данных.

2. Model First: сначала разработчик создает модель базы данных, по которой затем Entity Framework создает реальную базу данных на сервере.

3. Code First: разработчик создает класс модели данных, которые будут храниться в БД, а затем Entity Framework по этой модели генерирует базу данных и ее таблицы.

Таким образом, Entity Framework упрощает разработку клиентского приложения, восполняя пробел между отношениями БД и объектами языка программирования. Возможность генерации любого из трех уровней клиент-серверного приложения на основе другого позволяет говорить о EF как о CASE-инструменте. Автоматизация разработки необходимых для взаимодействия с БД структур данных - сущностных классов – позволяет не только сэкономить усилия разработчика, но и упростить его задачу в области реализации GUI как визуального отображения данных.

18

Page 19: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

2. СЕРВЕРНОЕ ПРИЛОЖЕНИЕ И СОЕДИНЕНИЕ С НИМ

Теперь, после рассмотрения технологии «клиент-сервер» и обзора основного инструмента разработки (EF), необходимо понять, как применить их в ходе выполнения лабораторных работ при разработке конкретного клиентского приложения. Важным шагом здесь будет обсуждение результатов проектирования серверной части ИС, ведь клиент является, по сути, программным интерфейсом, оболочкой для тех процессов, которые в нем происходят.

2.1. Описание структуры исходной базы данных

Рассмотрение реализации клиентского приложения, изложенное в последующих разделах, подразумевает, что на стороне сервера все уже реализовано: ведь даже процесс разворачивания EF в случае подхода Database First требует, прежде всего, подключения к базе данных. Для облегчения понимания последующих шагов стоит разъяснить структуру БД, использовавшуюся в данном примере. ER-диаграмма представлена на рис. 2.1.

Здесь рассмотрено задание варианта №8 (см. раздел 5 данного практикума), а именно БД для деятельности больницы, позволяющая вести учет больных и лекарств, отслеживать назначения. Первая таблица clients содержит поля с информацией о номере уникальной карты больного (number : Int32), его адресе (address : String), датах поступления и выписки (income_date : DateTime, departure_date :

DateTime), состоянии (condition : String), ФИО (name : String) и дате рождения (birthday : DateTime). Каждая запись однозначно идентифицируется по номеру карты. Таблица meds содержит список лекарств, информация о которых содержит название (name : String), цену (price : Decimal), остаток на складе (remainder : Int32), срок

19

Page 20: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

годности (best_before : DateTime), инструкцию (instruction : String) и уникальный идентификатор (id : Int32). Связаны эти две таблицы через таблицу назначений recipes, каждая запись которой содержит идентификатор номер карты) пациента, идентификатор лекарства, текстовое поле диагноза (diagnosis : String) и уникальный идентификатор (id : Int32) самого назначения. Одному пациенту может быть выписано несколько назначений с различными лекарствами и диагнозами, т. е. может существовать несколько записей с любыми сочетаниями этих трех полей.

Рис. 2.1. Структура БД

20

Page 21: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Отдельно существует также журнал таблицы лекарств, позволяющий администратору отслеживать изменения. Для работы с данными таблицами предполагается организовать три группы пользователей:

1) регистраторы (managers), ответственные за учет больных – им доступно только изменение и наполнение таблицы clients;

2) фармацевты (pharms), ответственные за учет лекарств – им доступна только таблица meds и обновление журнала meds_journal;

3) доктора (docs), которые могут назначать лекарства больным и вносить изменения в уже существующие рецепты – они могут работать с таблицей recipes, а списки пациентов и лекарств доступны только для чтения.

Оговорено, что все записи хранятся в архиве, т. е. никто из вышеназванных пользователей не может удалять записи в подконтрольных им таблицах. Также существуют последовательности meds_id_seq, meds_journal_sec и recipes_seq, обеспечивающие генерацию уникальных первичных ключей соответствующих таблиц. Первичный ключ таблицы clients – номер карты – вводится вручную при регистрации нового пациента.

Важно отметить, что данная БД не является полноценным продуктом, так как может быть усовершенствована путем, например, следующих дополнений: разбиение поля ФИО и адреса пациента на несколько полей, хранение диагнозов пациентов и инструкций к лекарствам в отдельных файлах, а не в строковых полях, расчет расхода лекарства при назначении его пациенту и т. д., что позволит сделать таблицы более информативными. Однако в рамках лабораторного практикума по дисциплине «Базы данных» описанной структуры более чем достаточно: существуют сущности «пациент» и

21

Page 22: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

«лекарство» с описанием основных свойств, в наличии связующая сущность «назначение».

Далее будем считать, что на стороне сервера все реализовано должным образом, и можно перейти к разработке клиента. Как было сказано в разделе 1 данного пособия, для того, чтобы связать сторону клиента с существующей БД, необходимо чтобы EF выполнил генерацию модели – создал сущностные классы. Однако, перед тем как перейти к рассмотрению этого вопроса, крайне полезно обсудить добавление EF в проект WindowsForms в Visual Studio и отметить тонкости его взаимодействия с СУБД.

2.2. Добавление EF в проект

Для добавления EF в проект, помимо базовых знаний С#, .NET и LINQ, понадобятся и навыки работы со следующими программными средствами:

IDE: Visual Studio 2013 Professional или выше с обновлением 4 (update 4);

.NET Framework 4.5 – на момент написания пособия являлся новейшей версией, поддерживающий остальные компоненты;

DDEX provider – поставщик данных, позволяющий .NET Framework получить доступ к серверу БД PostgreSQL. Можно получить по адресу github.com/npgsql/npgsql/releases;

Entity Framework: рассматриваемый пример основан на версии 6.1.3.

При наличии всех вышеуказанных средств и ресурсов, добавление EF в проект сводится к последовательности действий, описанных ниже. После установки VS 2013, платформы .NET и Npgsql DDEX provider необходимо создать новое приложение Windows Forms (.NET 4.5) и добавить некоторые пакеты NuGet. Для этого следует

22

Page 23: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

перейти в пункт меню «Сервис \ Диспетчер пакетов NuGet \ Управление пакетами NuGet для решения». Затем установить EF и поставщик данных Npgsql к нему (Npqsql и Npgsql for Entity

Framework). Важно, чтобы версия установленных поставщиков данных соответствовала DDEX provider, установленному ранее. Чтобы установить нужную версию, можно воспользоваться консолью диспетчера пакетов («Сервис \ Диспетчер пакетов NuGet \ Консоль диспетчера пакетов»): Install-Package EntityFramework -Version 6.1.3-beta1 -Pre

Install-Package Npgsql -Version 2.2.5.0

Install-Package Npgsql.EntityFramework -Version 2.2.5.0

Выполнение данных команд повлечет установку необходимых компонентов указанных версий. При разработке приложения, рассматриваемого в данном практикуме, использовался Npgsql версии 2.2.5.0. Версии пакетов в диспетчере NuGet могут быть и выше (например, на момент написания пособия была доступна версия Npgsql 3.0.0 RC), чем версия доступного DDEX provider. Важно отметить, что пакеты указанных версий поддерживают PostgreSQL 9.x, что должно удержать разработчика от использования неактуальных версий СУБД. Окно диспетчера пакетов NuGet показано на рис. 2.2.

Рис. 2.2. Необходимые пакеты NuGet

23

Page 24: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

После успешной установки необходимо собрать решение, нажав Ctrl+Shift+B. После чего можно приступать к созданию модели EDM.

2.3. Создание модели EDM

После добавления EF к проекту необходимо подключиться к существующей базе данных. Для этого нужно перейти в пункт меню «Проект \ Добавить компонент…» и в разделе «Данные» выбрать пункт «Модель ADO.NET EDM» (рис. 2.3).

Рис. 2.3. Добавление EDM в проект

Так как уже есть существующая БД, то в следующем окне следует выбрать пункт «Создать из базы данных» (рис. 2.4):

Рис. 2.4. Генерация модели из существующей БД

24

Page 25: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Далее при выборе подключения необходимо нажать кнопку «Создать соединение», выбрав поставщик данных PostgreSQL

Database (рис. 2.5):

Рис. 2.5. Создание подключения к БД

После этого требуется прописать необходимые параметры в строке подключения (раздел «Данные»):

Листинг 2.1 PORT=5432;

TIMEOUT=15;

POOLING=True;

MINPOOLSIZE=1;

MAXPOOLSIZE=20;

COMMANDTIMEOUT=20;

COMPATIBLE=2.2.5.0;

HOST=localhost;

DATABASE=hospital;

PASSWORD=123;

USER ID=postgres;

PRELOADREADER=True

Для генерации модели используется профиль администратора БД – на данном этапе это не существенно с точки зрения разграничения

25

Page 26: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

прав доступа в приложении, это будет реализовано позже, при формировании DbContext. Далее нужно выбрать вариант «Включить конфиденциальные данные в строку подключения» и сохранить параметры соединения в App.Config как

hospitalEntities

После чего это имя будет использоваться при формировании DbContext. В качестве имени можно указать и любое другое название, подходящее по смыслу к содержанию базы данных. Убедившись в следующем окне, что все необходимые таблицы (clients, meds, recipes) отмечены для включения в модель, требуется подтвердить выбор нажатием кнопки «Готово». После этого в обозревателе решений появится созданная модель (рис. 2.6):

Рис. 2.6. Содержимое модели EDM

Схема модели была использована ранее для визуализации структуры БД и представлена на рис. 2.1.

26

Page 27: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Задание по варианту

1. Добавьте Entity Framework к проекту в Visual Studio. 2. Подключитесь к серверному приложению, полученному в ходе

выполнения лабораторных работ №1-6 по дисциплине «Базы данных» [2].

3. Сгенерируйте модель EDM и сущностные классы из подключенной БД.

Контрольные вопросы

1. С помощью какой технологии возможен переход от отношений к объектам? Как она реализована?

2. Перечислите основные ORM-решения для Java, .NET. Сравните их с Entity Framework.

3. Какие подходы к разработке клиент-серверных приложений существуют в Entity Framework? В каких случаях какой из них используется?

4. Модель EDM. Как в ней описаны сущности? 5. Объясните структуру сгенерированных сущностных классов. 6. Что генерируется помимо сущностных классов?

27

Page 28: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

3. КЛИЕНТСКОЕ ПРИЛОЖЕНИЕ

Теперь, когда EF включен в проект, сформирована EDM-модель и, по сути, решена задача доступа к данным, можно перейти к написанию логики взаимодействия пользователя с этими данными. Далее будут рассмотрены задачи ограничения доступа пользователя к данным на уровне приложения, получение данных из БД и поиск, изменение, добавление и удаление данных в БД, экспорт данных в файл.

3.1. Организация ограничений доступа

В исходных условиях имеется три группы пользователей, обладающих различными правами (как было определено в подразделе 2.1). Поэтому, прежде чем перейти к работе с данными, необходимо реализовать разграничение доступа пользователей на уровне приложения. Для этого в разрабатываемом приложении будет существовать отдельная форма авторизации Form1; в зависимости от введенных на ней данных будет загружаться одна из трех форм – основное рабочее окно для соответствующей группы пользователей. Элементы управления будут соответствовать той таблице, к которой у пользователя есть доступ, – в этом основное различие и сходство между формами.

Перейдем к работе с GUI, для чего создадим на форме два текстовых поля для ввода логина и пароля – textBox1 и textBox2 соответственно. Добавим текст с необходимыми указаниями как label1, кнопку button1 для обработки введенных данных и элемент, информирующий о результатах авторизации. Если делать его на основе textbox, как в примере, то необходимо запретить редактирование текста:

textBox3.ReadOnly = true

28

Page 29: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Внешний вид формы авторизации отражен ниже на рис. 3.1:

Рис. 3.1. Окно авторизации

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

1) сбрасывается содержимое надписи состояния; 2) в случае, если поля «логин» и «пароль» не пустые, – происходит

подключение к БД с указанными в них данными пользователя; 3) определяется группа (групповая роль), к которой принадлежит

данный пользователь; 4) загружается соответствующая форма; 5) окно авторизации скрывается – пользователь работает с

основным рабочим окном, соответствующим его группе; 6) в случае ошибки подключения к БД (не верно указаны данные

пользователя) – становится видна надпись состояния с соответствующим текстом.

Стоит отметить, что в рассматриваемом примере подразумевается, что каждый пользователь, зарегистрированный в БД

29

Page 30: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

принадлежит к одной из трех групп (docs, managers, pharms), как указывалось ранее. Иными словами, необходимо условиться, что администратор БД добросовестно выполняет свою работу, и пользователь, зарегистрированный в базе, сразу же назначается в одну из групп, ситуация неопределенности невозможна и не требует обработки в логике приложения.

Из вышеупомянутого списка сложность может вызвать подключение к БД и определение групповой роли пользователя, однако это не так сложно, как может показаться на первый взгляд. Для выполнения этих действий добавим в Form1.cs строку

using Npgsql;

и откроем новое подключение: Листинг 3.1

string connectionString = string.Format("

Server=localhost;

User={0};

Password={1};

Database=hospital",

textBox1.Text,

textBox2.Text

);

NpgsqlConnection conn = new

NpgsqlConnection(connectionString);

conn.Open();

Проверка принадлежности пользователя к одной из групп осуществляется выполнением следующего запроса к БД:

Листинг 3.2 var tmp = new NpgsqlCommand(string.Format("

select rolname from pg_user

join pg_auth_members on

(pg_user.usesysid=pg_auth_members.member)

join pg_roles on (pg_roles.oid=pg_auth_members.roleid)

where pg_user.usename='{0}';

", textBox1.Text), conn);

var tmp1 = tmp.ExecuteReader();

tmp1.Read();

string role = tmp1.GetString(0);

30

Page 31: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Здесь задачу облегчают неявно типизированные переменные, ведь указание типов переменных tmp и tmp1 излишне, эти переменные служат временным, «служебным» целям, а их тип и функции понятны из контекста. В данном случае формируем новую Npgsql-команду с указанием запроса и подключения, выполняем её, перемещаемся на первую запись (строку) результата и считываем значение первого столбца. Строковая переменная role будет содержать название групповой роли, к которой принадлежит пользователь. Например, при вводе «manager1» в поле формы авторизации, запрос вернет значение «managers» (рис. 3.2):

Рис. 3.2. Проверка корректности запроса в pgAdmin III

В приложении, которое рассматривается в данном практикуме, и авторизация, и определение группы пользователя определяется один раз в начале сеанса каждого пользователя – при загрузке основного рабочего окна приложения. В случае, если потребуется многократная идентификация пользователя в различных частях приложения, целесообразным будет вынести данный функционал в отдельный метод. Еще одним интересным решением будет реализация паттерна ActiveRecord (AR), т. е. будет существовать класс User, содержащий необходимые поля и в котором реализован шаблон AR и определены

31

Page 32: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

некоторые методы. Тогда процедура идентификации будет выглядеть примерно следующим образом:

Листинг 3.3 User user = new User();

user.name = admin;

user.password = adminPass;

user.Authorize();

string role = user.GetRole();

Так или иначе, в итоге получаем строковую переменную role, в зависимости от которой и будем выбирать форму (основное рабочее окно) для загрузки. Условимся, что здесь и в дальнейшем параметры элементов формы, не влияющих на функционал приложения (цвет, размер, шрифт и т. п.) – вопрос дизайна GUI, будут задаваться в конструкторе, а параметры, так или иначе важные с точки зрения функционала, будут описаны в коде приложения. Тогда, с учетом всех необходимых проверок вводимых данных и логики отображения надписи состояния, код кнопки button1_Click в Окне авторизации Form1.cs будет выглядеть так:

Листинг 3.4 textBox3.Text = "";

textBox3.BackColor = Color.White;

if (textBox1.Text != "" & textBox2.Text != "") {

connectionString =

string.Format("Server=localhost;

User={0};

Password={1};

Database=hospital",

textBox1.Text,

textBox2.Text);

try {

conn = new NpgsqlConnection(connectionString);

conn.Open();

textBox3.BackColor = Color.FromArgb(255,128,255,128);

textBox3.Text = "✔"; var tmp = new NpgsqlCommand(string.Format("

select rolname from pg_user

join pg_auth_members on

(pg_user.usesysid=pg_auth_members.member)

join pg_roles on

(pg_roles.oid=pg_auth_members.roleid)

32

Page 33: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.4. Окончание where pg_user.usename='{0}';",

textBox1.Text), conn);

var tmp1 = tmp.ExecuteReader();

tmp1.Read();

string role = tmp1.GetString(0);

Form mainForm = new Form();

switch (role) {

case "managers":

mainForm = new Form2(connectionString);

break;

case "docs":

mainForm = new Form3(connectionString);

break;

case "pharms":

mainForm = new Form4(connectionString);

break;

}

mainForm.Owner = this.Owner;

mainForm.Show();

this.Hide();

}

catch (Npgsql.NpgsqlException) {

textBox3.BackColor = Color.IndianRed;

textBox3.Text = "Неверный логин или пароль!";

}

}

А внутри класса Form1 объявим:

public string connectionString;

NpgsqlConnection conn = null;

Последний момент, касающийся разграничения прав

пользователей, доступ к DbContext. В дальнейшем, основным

инструментом работы с данными в приложении будет являться класс

hospitalEntities в сформированной модели, наследуемый от DbContext.

Поэтому первой строкой в коде каждой формы Основанного рабочего

окна будет следующее:

hospitalEntities db = new hospitalEntities();

33

Page 34: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Чтобы ограничить доступ к данным на уровне контекста,

перегрузим конструктор в Model1.Context.cs:

Листинг 3.5 public hospitalEntities(string connectionString) {

this.Database.Connection.ConnectionString =

connectionString;

}

Теперь параметр, передаваемый в конструкторы Form2, Form3 и Form4 в форме авторизации, будет передаваться дальше, в конструктор контекста:

Листинг 3.5. Окончание public Form2(string conStr) {

hospitalEntities db = new hospitalEntities(conStr);

}

3.2. Реализация работы со списком пациентов

Ранее, в Form1.cs было обозначено создание Form2 с параметром connectionString. Теперь необходимо добавить на нее все нужные элементы. Перейдем в конструктор и расположим на форме следующие элементы (рис. 3.3):

DataGridView dataGridView1 для отображения таблицы – списка пациентов;

TextBox textBox1 для ввода слова поиска; ComboBox comboBox1 для выбора поля (параметра) поиска; Button button1 – кнопку поиска; Button button2 – кнопку редактирования выделенной записи; Button button3 – кнопку добавления новой записи; Label label1 – отображаемую в том случае, если список пуст.

34

Page 35: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Рис. 3.3. Вид основного рабочего окна регистратора в Редакторе форм

Настроив внешний вид элементов согласно общему дизайну приложения с помощью Конструктора форм, сделаем label1 и dataGridView1 доступными из других форм, указав модификатор Public (поле Modifiers в панели свойств элемента), а comboBox1 заполним следующими строками (поле Items раздела «Данные» в панели свойств элемента):

Листинг 3.6 № карты

ФИО

Состояние

Дата рождения

Адрес

Дата поступления

Дата выписки

С помощью конструктора можно настроить шрифт и цвет кнопок при определенных событиях, определить цветовое оформление таблицы, скрыть номера строк. Дизайн приложения при выполнении лабораторных работ – личное дело разработчика, поэтому оставим его без особых обсуждений и перейдем непосредственно к работе с данными. Для этого определим новый объект типа DbContext (хоть теперь он и называется hospitalEntities) и список из объектов,

35

Page 36: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

соответствующий таблице clients в базе данных. Из объекта hospitalEntities «вытащим» данные в список и свяжем его с dataGridView1 следующим образом:

Листинг 3.7 public partial class Form2 : Form {

public List<clients> sheetclients;

public hospitalEntities db;

public Form2(string conStr) {

InitializeComponent();

db = new hospitalEntities(conStr);

sheetclients = db.clients

.OrderBy(o => o.number).ToList();

dataGridView1.DataSource = sheetclients;

dataGridView1.ReadOnly = true;

if (dataGridView1.RowCount == 0) label1.Visible =

true;

else label1.Visible = false;

}

}

Данный код обеспечивает получение и обработку (а именно, сортировку таблицы по номеру карты пациента) данных, первоначальную установку отображения компонентов. Уже на данном этапе используются LINQ-запросы и лямбда-выражения. Также необходимо помнить, что окно авторизации при загрузке Form2 скрыто, и, в случае закрытия последнего (завершения сеанса пользователя), должно быть снова отображено. С этой целью добавим новый обработчик Form2_FormClosed события FormClosed («Свойства элемента \ События \ Поведение») и опишим его логику:

Листинг 3.8 private void Form2_FormClosed(object sender,

FormClosedEventArgs e) {

Application.OpenForms[0].Show();

Application.OpenForms[0].Controls["textBox1"].Text = "";

Application.OpenForms[0].Controls["textBox2"].Text = "";

Application.OpenForms[0].Controls["textBox3"].Text = "";

Application.OpenForms[0].Controls["textBox3"].BackColor =

Color.White;

}

36

Page 37: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Прежде чем собрать приложение и посмотреть результат, стоит обратить внимание на определение сущностного класса clients, которому будет соответствовать каждая строка в dataGridView1:

Листинг 3.9 public partial class clients {

public clients() {

this.recipes = new HashSet<recipes>();

}

public int number { get; set; }

public string address { get; set; }

public System.DateTime income_date { get; set; }

public System.DateTime departure_date { get; set; }

public string condition { get; set; }

public string name { get; set; }

public System.DateTime birthday { get; set; }

public virtual ICollection<recipes> recipes { get; set; }

}

Как можно заметить, все поля соответствуют столбцам таблицы в БД, за исключением recipes, сгенерированное при построении модели и служащее для связи с таблицей recipes, к которой у пользователя данной части приложения доступа нет. Поэтому при попытке заполнения таблицы в такой форме вызовет исключение

Npgsql.NpgsqlException: ОШИБКА 42501: нет доступа к отношению

recipes

Поскольку данный столбец ни разработчику, ни пользователю абсолютно не нужен, то проблема решается просто – не отображать его:

dataGridView1.Columns[7].Visible = false;

Похожее исключение можно получить, если в конструктор DbContext передать connectionString с данными пользователя другой группы, например, pharms. Поэтому важно либо реализовать обработку подобных исключений непосредственно в коде приложения, либо ограничить доступ на более ранних «подступах» к DbContext. Интересно заметить, что если в конструктор hospitalEntities не

37

Page 38: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

передавать вообще никаких параметров, то он будет формироваться исходя из тех данных, которые были указаны при создании EDM модели (в рассматриваемом примере это была администраторская учетная запись postgres).

Теперь заполним базу данных несколькими тестовыми записями (пользуясь пока pgAdmin III) и посмотрим, как выглядит их отображение в приложении (рис. 3.4):

Рис. 3.4. Вид списка пациентов в приложении

Теперь со стороны GUI обнаруживаются следующие проблемы:

1. Названия столбцов указаны так, как они существуют в базе данных и модели. Для пользователя русскоязычного приложения это будет несколько неудобно. Если же учитывать то, что при проектировании БД названия столбцов очень часто указываются с помощью транслитерации русских названий, тогда ситуация, с точки зрения семантики, может быть еще хуже.

2. Размер колонок не соответствует их содержимому: должны, как минимум, умещаться их названия, как максимум – ширина

38

Page 39: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

должна соответствовать среднестатистическому размеру содержимого.

3. Порядок колонок снижает удобство пользования и информативность визуализации данных. Так как пользователь читает слева направо, то и столбцы он ожидает увидеть в порядке убывания важности: номер карты, ФИО, адрес, дату рождения…

Исправим это следующим кодом:

Листинг 3.10 dataGridView1.Columns[0].HeaderText = "№";

dataGridView1.Columns[1].HeaderText = "Адрес";

dataGridView1.Columns[1].DisplayIndex= 4;

dataGridView1.Columns[2].HeaderText = "Д. поступления";

dataGridView1.Columns[2].DisplayIndex = 5;

dataGridView1.Columns[3].HeaderText = "Д. выписки";

dataGridView1.Columns[3].DisplayIndex = 6;

dataGridView1.Columns[4].HeaderText = "Состояние";

dataGridView1.Columns[4].DisplayIndex = 3;

dataGridView1.Columns[5].HeaderText = "ФИО";

dataGridView1.Columns[5].DisplayIndex = 1;

dataGridView1.Columns[6].HeaderText = "Д. рождения";

dataGridView1.Columns[6].DisplayIndex = 2;

dataGridView1.Columns[1].Width = 400;

dataGridView1.Columns[5].Width = 400;

dataGridView1.Columns[0].Width = 50;

dataGridView1.Columns[2].Width = 170;

dataGridView1.Columns[3].Width = 130;

dataGridView1.Columns[6].Width = 140;

dataGridView1.Columns[4].Width = 200;

Теперь перейдем к реализации поиска по таблице. Если поле для ввода не пустое, тогда при нажатии кнопки поиска будем передавать в dataGridView1 данные из sheetclients, отфильтрованные по значениям одного из столбцов, выбор которого осуществляется через comboBox1, и обновлять состояние label1. Сброс фильтра будет происходить по нажатию кнопки поиска с пустым полем ввода:

39

Page 40: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.11 if (textBox1.Text != "") {

switch (comboBox1.SelectedIndex) {

case 0: dataGridView1.DataSource =

sheetclients.Where(p => p.number.ToString() ==

textBox1.Text.ToString())

.ToList(); break;

case 1: dataGridView1.DataSource =

sheetclients.Where(p =>

p.name.ToString().Contains(textBox1.Text.ToString()))

.ToList(); break;

case 2: dataGridView1.DataSource =

sheetclients.Where(p => p.condition.ToString() ==

textBox1.Text.ToString())

.ToList(); break;

case 3: dataGridView1.DataSource =

sheetclients.Where(p => p.birthday.ToString() ==

textBox1.Text.ToString()

.ToList(); break;

case 4: dataGridView1.DataSource =

sheetclients.Where(p =>

p.address.ToString().Contains(textBox1.Text.ToString()))

.ToList(); break;

case 5: dataGridView1.DataSource =

sheetclients.Where(p => p.income_date.ToString() ==

textBox1.Text.ToString()) .ToList(); break;

case 6: dataGridView1.DataSource =

sheetclients.Where(p => p.departure_date.ToString() ==

textBox1.Text.ToString()) .ToList(); break;

}

}

else {

dataGridView1.DataSource = sheetclients;

}

dataGridView1.Update();

if (dataGridView1.RowCount == 0) label1.Visible = true;

else label1.Visible = false;

В итоге имеется часть приложения, в которой реализованы функции поиска и просмотра записей о пациентах больницы. Осталось добавить возможность редактирования и добавления записей. Для этого создадим две отдельные формы FormEditClient и FormAddClient. Для добавления нового клиента будем просто создавать новую форму FormAddClient addCl. Понятно, что такая форма может быть открыта

40

Page 41: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

только одна. Можно сделать ее модальной, т. е. блокировать работу с начальной формой:

addCl.ShowDialog();

Однако считается, что пользователю, во время ее заполнения, может потребоваться просмотр информации из списка пациентов, поэтому необходимо проверять, сколько форм уже открыто, и, если уже есть одна – передавать управление ей. В таком случае button3_Click выглядит следующим образом:

Листинг 3.12

if (Application.OpenForms.Count == 2) {

FormAddClient addCl = new FormAddClient();

addCl.Owner = this; addCl.Show();

}

else Application.OpenForms[2].Focus();

Передача данных между формами осуществляется с использованием свойства «Родитель». Важно помнить, что форма с индексом 0 – скрытое окно авторизации, а с индексом 1 – основное рабочее окно регистратора. Отличие формы редактирования в том, что в нее мы должны передать объект типа clients, поля которого будем редактировать:

Листинг 3.13 if (dataGridView1.SelectedCells.Count == 1) {

if (Application.OpenForms.Count == 2) {

clients item = sheetclients

.First(w => w.number.ToString() == dataGridView1

.SelectedCells[0]

.OwningRow

.Cells[0]

.Value

.ToString());

FormEditClient edCl = new FormEditClient(item);

edCl.Owner = this;

edCl.Show();

}

else Application.OpenForms[2].Focus(); }

41

Page 42: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

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

Теперь перейдем к дизайну форм и их логике. Две данные формы будут абсолютно идентичны по дизайну, поэтому можно просто скопировать элементы из конструктора одной в конструктор другой. Единственное ограничение – номер карты пациента в уже существующей записи редактировать запрещено, это первичный ключ. Добавим на форму семь текстовых полей с поясняющими надписями, соответствующие столбцам таблицы (полям записи, объекта типа clients), одну кнопку и lable1 для вывода ошибок (рис. 3.5):

Рис. 3.5. Форма редактирования записи пациента

Определим в классе формы объект clients item и дополним конструктор:

Листинг 3.14 public partial class FormEditClient : Form {

clients item;

public FormEditClient(clients itm) {

item = itm;

InitializeComponent();

label8.Visible = false;

textBox1.Text = item.number.ToString();

textBox2.Text = item.name.ToString();

textBox3.Text = item.birthday.ToShortDateString();

textBox4.Text = item.address.ToString();

textBox5.Text = item.income_date.ToShortDateString();

42

Page 43: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.14. Окончание textBox6.Text =

item.departure_date.ToShortDateString();

textBox7.Text = item.condition.ToString();

}

}

В код, обрабатывающий нажатие кнопки ОК, добавим необходимые проверки ввода. В данном случае это:

Листинг 3.15 if (textBox2.Text.Trim() == "" || textBox4.Text.Trim() == ""

|| textBox7.Text.Trim() == "") {

label8.Visible = true;

label8.Text = "Заполните поля \'ФИО\', \'Адрес\' и

\'Состояние\'";

}

else if (textBox2.Text.ToCharArray()

.All(w => ((w >= 'а' && w <= 'я')

|| (w >= 'А' && w <= 'Я') || w == ' ')) == false) {

label8.Visible = true;

label8.Text = "Поле 'ФИО' может содержать только кириллицу

и пробелы";

} else {…}

Внутрь оператора else добавляем следующий код, «обернутый» в конструкцию try-catch:

Листинг 3.16 var result = ((Form2)Owner).db.clients

.SingleOrDefault(w => w.number == item.number);

result.name = textBox2.Text.ToString();

DateTime bd = DateTime.Parse(textBox3.Text);

result.birthday = bd;

result.address = textBox4.Text.ToString();

DateTime ind = DateTime.Parse(textBox5.Text);

result.income_date = ind;

DateTime outd = DateTime.Parse(textBox6.Text);

result.departure_date = outd;

result.condition = textBox7.Text.ToString();

((Form2)Owner).db.SaveChanges();

((Form2)Owner).sheetclients = ((Form2)Owner).db.clients

.OrderBy(o => o.number).ToList();

((Form2)Owner).dataGridView1.DataSource =

((Form2)Owner).sheetclients;

this.Close();

43

Page 44: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Здесь создается новый объект result, который на самом деле будет иметь тип clients, и присваиваются его полям соответствующие значения из текстовых полей формы. После этого сохраняются изменения в экземпляре DbContext, обновляются данные в таблице Form2 и закрывается текущая форма. Использование неявной типизации переменной result здесь не является обязательным. Помимо оператора try с таким кодом, у также будет два оператора catch, отлавливающих ошибки запросов к базе данных и неверный формат введенной даты:

Листинг 3.17 catch (Npgsql.NpgsqlException err) {

label8.Visible = true;

label8.Text = "Ошибка ввода! " + err.Message;

}

catch (System.FormatException err) {

label8.Visible = true;

label8.Text = "Ошибка ввода! " + err.Message;

}

В форме добавления новой карточки пациента код поменяется незначительно. Не будет создаваться объект clients item, а сразу result:

Листинг 3.18 public partial class FormAddClient : Form {

clients result = new clients();

public FormAddClient() {

InitializeComponent();

label8.Visible = false;

}

}

Проверка вводимых данных будет иметь следующий вид:

Листинг 3.19 try {

Int32.Parse(textBox1.Text);

}

catch (System.FormatException err) {

label8.Visible = true;

label8.Text = "Ошибка ввода № карты! " + err.Message;

return;

44

Page 45: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.19. Окончание }

if (textBox1.Text.Trim() == ""

|| textBox2.Text.Trim() == ""

|| textBox4.Text.Trim() == ""

|| textBox7.Text.Trim() == "") {

label8.Visible = true;

label8.Text = "Заполните поля \'№ Карты\', \'ФИО\',

\'Адрес\' и \'Состояние\'";

}

else if (textBox2.Text.ToCharArray()

.All(w => ((w >= 'а' && w <= 'я')

|| (w >= 'А' && w <= 'Я')

|| w == ' ')) == false) {

label8.Visible = true;

label8.Text = "Поле 'ФИО' может содержать только кириллицу

и пробелы";

}

else if (((Form2)Owner).db.clients

.Count(c => c.number.ToString() == textBox1.Text) > 0) {

label8.Visible = true;

label8.Text = "Этот номер карты уже занят!";

}

Внутри конструкции try-catch добавится заполнение поля number:

result.number = Int32.Parse(textBox1.Text);

А после заполнения всех остальных – добавление объекта clients result в экземпляр класса hospitalEntities:

((Form2)Owner).db.clients.Add(result);

Заполнение всех остальных полей result, сохранение изменений в DbContext, обновление списка пациентов и код внутри операторов catch идентичны рассмотренному ранее в форме редактирования карточки пациента.

Хотя в рассматриваемом примере не рассматривается возможность удаления записей, все же приведем код, позволяющий это сделать:

45

Page 46: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.20 var result = new clients { number = param };

db.clients.Attach(result);

db.clients.Remove(result);

db.SaveChanges();

Если используется Entity Framework 6 или выше и можно определить первичный ключ удаляемой записи (в примере его значение передается переменной param) – этот способ сработает. Тем более, что из любой таблицы приложения легко получить первичный ключ записи из значения столбца (информативного и открытого, как в Form2, или служебного и скрытого, как в будущих частях приложения). Также важно помнить, что для удаления записей, связанных с другими таблицами внешними ключами, должно быть реализовано каскадное удаление на уровне БД. Чуть более удобный вариант есть в EF.Extended (установка командой Install-Package

EntityFramework.Extended):

using EntityFramework.Extensions

...

db.clients.Delete(c => c.recipes.Count > 10);

Наряду с «улучшенным» удалением в данном пакете есть кэширование для частых запросов к редко изменяемым данным:

var clients = db.clients

.Where(с => с.Name == param).FromCache();

Еще одним вариантом является выполнение прямых SQL-запросов к БД, как это было с определением группы пользователя. Какой подход использовать – решать разработчику.

3.3. Реализация работы со списком лекарств

Так как в таблицах clients и meds нет внешних ключей, они довольно простые, то и обрабатывать данные в этих таблицах очень просто. Структура решения, даже дизайн форм, будут для Form2 и Form4 довольно схожими. Создадим новую форму и скопируем все

46

Page 47: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

элементы из формы Form2 с помощью Конструктора форм. В этой форме элемент comboBox1 заполним следующими строками:

Листинг 3.21 Название

Цена

Остаток

Срок годности

Инструкция

Теперь перейдем к коду. Основное отличие – в таблице dataGridView1, элемент будет связан с List<meds> sheetmeds, соответственно нужно будет изменить инициализацию таблицы при загрузке формы:

Листинг 3.22 public partial class Form4 : Form {

public List<meds> sheetmeds;

public hospitalEntities db;

public Form4(string conStr) {

InitializeComponent();

db = new hospitalEntities(conStr);

sheetmeds = db.meds.OrderBy(o => o.name).ToList();

dataGridView1.DataSource = sheetmeds;

dataGridView1.ReadOnly = true;

if (dataGridView1.RowCount == 0) label1.Visible = true;

else label1.Visible = false;

dataGridView1.Columns[0].HeaderText = "Название";

dataGridView1.Columns[0].Width = 200;

dataGridView1.Columns[1].HeaderText = "Цена";

dataGridView1.Columns[2].HeaderText = "Остаток";

dataGridView1.Columns[3].HeaderText = "Срок годности";

dataGridView1.Columns[3].Width = 170;

dataGridView1.Columns[4].HeaderText = "Инструкция";

dataGridView1.Columns[4].Width = 350;

dataGridView1.Columns[5].Visible = false;

dataGridView1.Columns[6].Visible = false;

}}

Таким образом, опять «вытащим» данные из hospitalEntities db (на этот раз - сущность meds), поместим отсортированные по имени записи в список sheetmeds и свяжем его с элементом GUI. Однако в данном случае необходимо скрыть также и столбец id – он содержит первичный ключ, использующийся исключительно в служебных

47

Page 48: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

целях. Имена и порядок столбцов будут ясны из определения meds в сущностном классе «Model1.edmx \ Model1.tt \ meds.cs»:

Листинг 3.23 public partial class meds {

public meds() {

this.recipes = new HashSet<recipes>();

}

public string name { get; set; }

public Nullable<decimal> price { get; set; }

public int remainder { get; set; }

public System.DateTime best_before { get; set; }

public string instruction { get; set; }

public int id { get; set; }

public virtual ICollection<recipes> recipes { get; set; }

}

Затем добавим с помощью pgAdmin III несколько записей в таблицу meds нашей базы данных. Запустим приложение и войдем в систему как pharm1 – участник группы pharms. В результате отображение данных на форме будет выглядеть следующим образом (рис. 3.6):

Рис. 3.6. Вид списка пациентов в приложении

Перейдем к «оживлению» кнопок формы. Реализация поиска – тривиальная задача, решение которой аналогично подобной в Form2. Изменится только код операторов case:

48

Page 49: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.24 case 0: dataGridView1.DataSource = sheetmeds

.Where(p => p.name.ToString()

.Contains(textBox1.Text.ToString()))

.ToList(); break;

case 1: dataGridView1.DataSource = sheetmeds

.Where(p => p.price.ToString() ==

textBox1.Text.ToString())

.ToList(); break;

case 2: dataGridView1.DataSource = sheetmeds

.Where(p => p.remainder.ToString() ==

textBox1.Text.ToString())

.ToList(); break;

case 3: dataGridView1.DataSource = sheetmeds

.Where(p => p.best_before.ToString() ==

textBox1.Text.ToString())

.ToList(); break;

case 4: dataGridView1.DataSource = sheetmeds

.Where(p => p.instruction.ToString()

.Contains(textBox1.Text.ToString()))

.ToList(); break;

Далее создадим две формы: для добавления – FormAddMed, с кодом кнопки button3_Click:

Листинг 3.25 if (Application.OpenForms.Count == 2) {

FormAddMed addMed = new FormAddMed();

addMed.Owner = this;

addMed.Show();

}

else Application.OpenForms[2].Focus();

и для редактирования записи в таблице – FormEditMed (button2_Click):

Листинг 3.25. Окончание if (dataGridView1.SelectedCells.Count == 1) {

if (Application.OpenForms.Count == 2) {

meds item = sheetmeds

.First(w => w.id.ToString() == dataGridView1

.SelectedCells[0]

.OwningRow.Cells[5].Value.ToString());

FormEditMed edMed = new FormEditMed(item);

edMed.Owner = this;

edMed.Show();

}

else Application.OpenForms[2].Focus();

}

49

Page 50: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Чтобы однозначно идентифицировать запись в БД по строке таблицы, необходимо выбрать столбец, значение которого будет уникально (т. е. id) – это шестой (по индексу – пятый, от нуля) скрытый столбец. Теперь перейдем непосредственно к разработке этих форм: дизайну и логике. Для чего добавим на формы необходимые поля с поясняющими надписями, соответствующие столбцам таблицы (полям записи, объекта типа meds), одну кнопку и label1 для вывода ошибок (рис. 3.7):

Рис. 3.7. Форма добавления лекарства в БД

Эти две формы будут абсолютно идентичны по дизайну. Однако в отличие от подобных форм, описанных в предыдущем разделе, они не имеют поля для ввода первичного ключа (id) – значение данного поля при добавлении новой записи генерируется на основе последовательности, записанной в БД. Соответственно, «защищать» его от редактирования не нужно – оно везде скрыто от пользователя. Так как заполнение поля Инструкция не обязательно, то единственной проверкой при заполнении обеих форм будет проверка указания названия лекарства. Остальные ошибки при нажатии кнопки ОК обработаются конструкцией try-catch как System.FormatException:

Листинг 3.26 if (textBox1.Text.Trim() == "") {

label8.Visible = true;

label8.Text = "Укажите наименование!";

}

else {

50

Page 51: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.26. Окончание try {

...

}

catch (Npgsql.NpgsqlException err) {

label8.Visible = true;

label8.Text = "Ошибка ввода! " + err.Message;

}

catch (System.FormatException err) {

label8.Visible = true;

label8.Text = "Ошибка ввода! " + err.Message;

}

}

В случае добавления лекарства, класс формы будет содержать следующее:

Листинг 3.27 public partial class FormAddMed : Form {

meds result = new meds ();

public FormAddMed() {

InitializeComponent();

label8.Visible = false;

}

...

}

А внутри оператора try будет следующий код: Листинг 3.28

result.name = textBox1.Text.ToString();

result.price = Int64.Parse(textBox2.Text);

result.instruction = textBox5.Text.ToString();

result.remainder = Int32.Parse(textBox3.Text);

DateTime bbd = DateTime.Parse(textBox4.Text);

result.best_before = bbd;

((Form4)Owner).db.meds.Add(result);

((Form4)Owner).db.SaveChanges();

((Form4)Owner).sheetmeds = ((Form4)Owner).db.meds

.OrderBy(o => o.name).ToList();

((Form4)Owner).label1.Visible = false;

((Form4)Owner).dataGridView1.DataSource =

((Form4)Owner).sheetmeds;

this.Close();

51

Page 52: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

В случае же редактирования уже существующей записи, вместо инициализации meds result, будет объявление meds item, а в конструктор формы добавятся следующие строки:

Листинг 3.29 item = itm;

InitializeComponent();

label8.Visible = false;

textBox1.Text = item.name.ToString();

textBox2.Text = item.price.ToString();

textBox3.Text = item.remainder.ToString();

textBox4.Text = item.best_before.ToShortDateString();

textBox5.Text = item.instruction.ToString();

Как можно заметить, структура решения схожа с таковой в Form2, а различия между добавлением и редактированием записей минимальны в обеих случаях. Это подтверждается и кодом внутри оператора try формы редактирования записи:

Листинг 3.30 var result = ((Form4)Owner).db.meds

.SingleOrDefault(w => w.id == item.id);

result.name = textBox1.Text.ToString();

result.price = Int64.Parse(textBox2.Text);

result.instruction = textBox5.Text.ToString();

result.remainder = Int32.Parse(textBox3.Text);

DateTime bbd = DateTime.Parse(textBox4.Text);

result.best_before = bbd;

((Form4)Owner).db.SaveChanges();

((Form4)Owner).sheetmeds = ((Form4)Owner).db.meds

.OrderBy(o => o.name).ToList();

((Form4)Owner).dataGridView1.DataSource =

((Form4)Owner).sheetmeds;

this.Close();

На этом реализация части приложения для фармацевта закончена. Благодаря тому, что таблицы имели простую схожую структуру, а задачи по работе с ними были абсолютно идентичны, код зачастую повторялся. Поэтому стоит подумать о том, как лучше организовать решение, возможно, некоторые блоки кода необходимо вынести в отдельные методы, что, однако, выходит за рамки лабораторных работ и данного практикума.

52

Page 53: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

3.4. Реализация работы со списком назначений

Создадим новую форму Form3 и с помощью Конструктора форм поместим на нее все необходимые элементы: dataGridView1 для отображения таблицы, элементы GUI для поиска и кнопки добавления и редактирования записей – все как в предыдущих формах (Form2.cs, Form3.cs). Затем перейдем к заполнению таблицы данными.

Проблема здесь заключается в том, что в базе данных сущность recipes имеет поля client и med – внешние ключи, содержащие лишь id (номер карты и id лекарства соответственно) записей. Этой таблице соответствует сущностный класс recipes:

Листинг 3.31 public partial class recipes {

public int client { get; set; }

public string diagnosis { get; set; }

public int id { get; set; }

public int med { get; set; }

public virtual clients clients { get; set; }

public virtual meds meds { get; set; }

}

Как можно заметить, с помощью списка, состоящего из таких объектов (List<recipes> sheetrecipes), представить данные в наглядной и удобной для пользователя форме не получится. Необходимо «выдернуть» из внешних таблиц данные о ФИО пациента и названии лекарства и затем добавить их в дополнительные поля каждой записи в таблице. Таким образом, возникает необходимость выполнения кросс-запроса к трем таблицам и определения пользовательского типа данных (объекта) view, содержащим необходимые поля. Все изменения будут производиться только в таблице recipes, доступ у другим двум (clients, meds) врачам (групповая роль docs) будет ограничен на уровне «только чтение». Поэтому необходимо проверить, какими правами обладает данная группа пользователей и, если необходимо, внести соответствующие дополнения в БД:

53

Page 54: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.32 GRANT USAGE

ON SCHEMA hospital

TO docs;

GRANT SELECT, INSERT, UPDATE

ON hospital.recipes

TO docs;

GRANT USAGE

ON SEQUENCE hospital.recipes_id_seq

TO docs;

GRANT SELECT

ON hospital.meds

TO docs;

GRANT SELECT

ON hospital.clients

TO docs;

Далее возможны два подхода, различающиеся тем, где будет определяться кросс-запрос:

1. Использование представления – кросс-запрос формируется на уровне базы данных.

2. Объединение таблиц с помощью LINQ-запроса, на уровне кода приложения.

Однако в любом из этих случаев в коде формы будет следующий блок кода:

Листинг 3.33 public partial class Form3 : Form {

public List<view> viewrecipes = new List<view>();

public hospitalEntities db;

public string cn;

public Form3(string conStr) {

cn = conStr;

InitializeComponent();

db = new hospitalEntities(cn);

}

}

В любом случае создается список назначений, состоящий из объектов типа view, который определим позже, и экземпляр контекста hospitalEntities db, использующий строку подключения.

54

Page 55: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

3.4.1. Получение данных из представления

Данный способ подразумевает использование представления, созданного, вероятнее всего, в ходе выполнения Лабораторной работы №3. Согласно заданию данной лабораторной работы, было создано представление, расширяющее отображение назначений пациента: вместо идентификатора лекарства отображалось его название, а кроме номера карты пациента – еще и его ФИО. В рассматриваемом примере оно было создано следующим запросом:

Листинг 3.34 CREATE MATERIALIZED VIEW hospital.extendrecipes AS

SELECT c.number,

c.name AS r_client_name,

m.name AS r_med_name,

r.diagnosis,

r.id,

r.client,

r.med,

m.id AS med_id

FROM hospital.recipes r

LEFT JOIN hospital.clients c

ON r.client = c.number

LEFT JOIN hospital.meds m

ON r.med = m.id

WITH DATA;

Это позволит, просматривая назначения, видеть не только идентификаторы лекарств и пациентов, но и дополнительную информацию (рис. 3.8):

Рис. 3.8. Просмотр данных внутри представления

55

Page 56: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Однако существует проблема: для того, чтобы представление попало, как и все таблицы, в модель EDM, в нем должен быть определен первичный ключ, которого в нем, разумеется, нет. Поэтому решить проблему так легко, как хотелось бы не получится. В любом случае необходимо выполнение SQL-запроса к БД на выборку данных. Структура блока кода, ответственного за инициализацию, следующая:

Листинг 3.35 NpgsqlConnection conn = new NpgsqlConnection(cn);

conn.Open();

var tmp0 = new NpgsqlCommand("

refresh materialized view hospital.extendrecipes

", conn);

tmp0.ExecuteReader();

var tmp1 = new NpgsqlCommand("

SELECT * FROM hospital.extendrecipes

", conn);

var tmp = tmp1.ExecuteReader();

while (tmp.Read()) {

viewrecipes.Add(new view(

Int32.Parse(tmp[4].ToString()),

Int32.Parse(tmp[0].ToString()),

Int32.Parse(tmp[6].ToString()),

tmp[1].ToString(),

tmp[2].ToString(),

tmp[3].ToString()));

}

Соответственно код сущностного класса view будет таким: Листинг 3.36

public class view {

public view(int id, int cid, int mid, string cn, string

mn, string d) {

this.c_number = cid;

this.m_id = mid;

this.id = id;

this.m_name = mn;

this.c_name = cn;

this.diagnosis = d;

}

public int id { get; set; }

public int c_number { get; set; }

public int m_id { get; set; }

public string c_name { get; set; }

public string m_name { get; set; }

public string diagnosis { get; set; } }

56

Page 57: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Строки из предыдущего блока кода, ответственные за инициализацию списка и его обновление, можно вынести в отдельные методы. Еще лучше, организовать добавление назначения отдельным методом, принадлежащим данному классу:

Листинг 3.37 public void LoadToList() {

this.Clear();

NpgsqlConnection conn = new NpgsqlConnection(cn);

conn.Open();

var tmp1 = new NpgsqlCommand("

SELECT * FROM hospital.extendrecipes

", conn);

var tmp = tmp1.ExecuteReader();

while (tmp.Read()) {

this.Add(new view(

Int32.Parse(tmp[4].ToString()),

Int32.Parse(tmp[0].ToString()),

Int32.Parse(tmp[6].ToString()),

tmp[1].ToString(),

tmp[2].ToString(),

tmp[3].ToString()));

}

}

public void AddItem(view item) {

((Form3)Owner).db.recipes.Add(new recipes {

client = item.c_number;

med = item.m_id

diagnosis = item.diagnosis;

})

((Form3)Owner).db.SaveChanges();

Refresh();

this.LoadToList();

((Form3)Owner).dataGridView1.DataSource = this;

}

public void Refresh() {

NpgsqlConnection conn = new NpgsqlConnection(cn);

conn.Open();

var tmp0 = new NpgsqlCommand("

refresh materialized view hospital.extendrecipes

", conn);

tmp0.ExecuteReader();

}

Соответственно, создав экземпляр такого класса и вызывая нужные методы при соответствующих операциях с данными, можно

57

Page 58: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

реализовать работу со списком назначений. И порядок столбцов в dataGridView1 будет следующим:

Листинг 3.38 dataGridView1.Columns[1].HeaderText = "№ карты";

dataGridView1.Columns[3].HeaderText = "ФИО пациента";

dataGridView1.Columns[4].HeaderText = "Лекарство";

dataGridView1.Columns[5].HeaderText = "Диагноз";

dataGridView1.Columns[1].Width = 150;

dataGridView1.Columns[3].Width = 400;

dataGridView1.Columns[4].Width = 400;

dataGridView1.Columns[5].Width = 250;

dataGridView1.Columns[0].Visible = false;

dataGridView1.Columns[2].Visible = false;

Минусы такого подхода – лишнее представление в БД и «чистый» SQL в объектном коде. Поэтому рассмотрим более подробно второй способ.

3.4.2. Получение данных с помощью LINQ-запроса

Так или иначе, для вывода данных из нескольких таблиц приходится писать сложный запрос. Полагаем, что это удобнее делать в форме LINQ-запроса на уровне кода приложения: IntelliSense обеспечивает автодополнение и доступ к документации, что значительно упрощает разработку и отладку программы, снижая тем самым количество возможных ошибок. В случае такого подхода, можно несколько упростить сущностный класс view:

Листинг 3.39 public class view {

public view(int id, int cid, string cn, string mn, string

d)

{

this.c_number = cid;

this.id = id;

this.m_name = mn;

this.c_name = cn;

this.diagnosis = d;

}

public int id { get; set; }

58

Page 59: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.39. Окончание public int c_number { get; set; }

public string c_name { get; set; }

public string m_name { get; set; }

public string diagnosis { get; set; }

}

А конструктор формы будет содержать следующие строки, реализующие инициализацию данных:

Листинг 3.40 viewrecipes = db.clients

.Join(db.recipes, c => c.number, rc => rc.client, (c, rc)

=>

new { c, rc })

.Join(db.meds, rcm => rcm.rc.med, m => m.id, (rcm, m) =>

new { rcm, m })

.AsEnumerable()

.Select(x => new view (

x.rcm.rc.id,

x.rcm.c.number,

x.rcm.c.name,

x.m.name,

x.rcm.rc.diagnosis))

.OrderBy(o => o.c_number).ToList();

dataGridView1.DataSource = viewrecipes.ToList();

Можно с легкостью проследить аналогии между данным блоком кода и SQL-запросом представления, описанным ранее. Однако здесь есть несколько интересных моментов. Во-первых, приводим анонимный тип, возвращенный в результате выполнения Join() к «перечисляемому» с помощью метода AsEnumerable(). Во-вторых, проецируем элементы полученной последовательности в список объектов типа view с помощью Select(). И, так как поля объекта view уже расположены в нужном порядке, остается только:

Листинг 3.41 dataGridView1.ReadOnly = true;

if (dataGridView1.RowCount == 0) label1.Visible = true;

else label1.Visible = false;

dataGridView1.Columns[1].HeaderText = "№ карты";

dataGridView1.Columns[2].HeaderText = "ФИО пациента";

dataGridView1.Columns[3].HeaderText = "Лекарство";

dataGridView1.Columns[4].HeaderText = "Диагноз";

59

Page 60: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.41. Окончание dataGridView1.Columns[1].Width = 150;

dataGridView1.Columns[2].Width = 400;

dataGridView1.Columns[3].Width = 400;

dataGridView1.Columns[4].Width = 250;

dataGridView1.Columns[0].Visible = false;

Столбец с индексом 0 – здесь идентификатор назначения, совершенно не интересный пользователю. Далее очень просто реализовать поиск записей: внесем в свойство Items элемента формы comboBox1 следующие строки:

Листинг 3.42 № карты

Пациент

Лекарство

Диагноз

А внутри блока кода оператора switch, уже знакомого нам по предыдущим формам, пропишем следующее:

Листинг 3.43 case 0: dataGridView1.DataSource = viewrecipes

.Where(p => p.c_number.ToString() == textBox1.Text)

.ToList(); break;

case 1: dataGridView1.DataSource = viewrecipes

.Where(p => p.c_name.Contains(textBox1.Text))

.ToList(); break;

case 2: dataGridView1.DataSource = viewrecipes

.Where(p => p.m_name.Contains(textBox1.Text))

.ToList(); break;

case 3: dataGridView1.DataSource = viewrecipes

.Where(p => p.diagnosis.Contains(textBox1.Text))

.ToList(); break;

Не забыв добавить код обработки закрытия формы, также добавим, по аналогии с предыдущими (Form2, Form4) формами, код кнопок добавления и редактирования записи:

60

Page 61: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.44 private void button3_Click(object sender, EventArgs e) {

if (Application.OpenForms.Count == 2) {

FormAddRec addRec = new FormAddRec(cn);

addRec.Owner = this;

addRec.Show();

}

else Application.OpenForms[2].Focus();

}

private void button2_Click(object sender, EventArgs e) {

if (dataGridView1.SelectedCells.Count == 1) {

if (Application.OpenForms.Count == 2) {

view item = viewrecipes

.First(w => w.id.ToString() ==

dataGridView1

.SelectedCells[0].OwningRow

.Cells[0].Value.ToString());

FormEditRec edRec = new FormEditRec(item);

edRec.Owner = this;

edRec.Show();

}

else Application.OpenForms[2].Focus();

}

}

В результате интерфейс основного рабочего окна врача выглядит так (рис. 3.9):

Рис. 3.9. Вид списка назначений в приложении

Для редактирования записи создадим форму edRec с уже привычным набором элементов: четыре текстовых поля с поясняющими надписями, кнопка и label1 для отображения сообщения об ошибке. Важно отметить, что в любом назначении основная информация – это связанные с ним пациент и лекарство. Диагноз назначения – скорее пояснение, чем свойство. Поэтому, при изменении

61

Page 62: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

основной информации, фактически произойдет удаление старого назначения и добавление нового, чего по условиям задачи (хранение всех записей «в архиве») быть не должно. Таким образом, редактировать возможно только диагноз. С точки зрения дизайна формы это значит:

Листинг 3.45 textBox1.Readonly = true;

textBox2.Readonly = true;

textBox3.Readonly = true;

Код формы тоже достаточно тривиален:

Листинг 3.46 public partial class FormEditRec : Form {

view item;

public FormEditRec(view itm) {

InitializeComponent();

item = itm;

label8.Visible = false;

textBox1.Text = item.c_number.ToString();

textBox2.Text = item.c_name.ToString();

textBox3.Text = item.m_name.ToString();

textBox4.Text = item.diagnosis.ToString();

}

private void button1_Click(object sender, EventArgs e) {

if (textBox4.Text.Trim() == "") {

label8.Visible = true;

label8.Text = "Заполните поле \'Диагноз\'";

}

else {

try { ... }

catch (Npgsql.NpgsqlException err) {

label8.Visible = true;

label8.Text = "Ошибка ввода! " +

err.Message;

}

}

}

}

62

Page 63: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Интерфейс формы выглядит следующим образом (рис. 3.10):

Рис. 3.10. Форма редактирования назначения

А внутри оператора try будет следующий код:

Листинг 3.47 var result = ((Form3)Owner).db.recipes.SingleOrDefault(w =>

w.id == item.id);

result.diagnosis = textBox4.Text.ToString();

((Form3)Owner).db.SaveChanges();

((Form3)Owner).viewrecipes = ((Form3)Owner).db.clients

.Join(

((Form3)Owner).db.recipes,

c => c.number,

rc => rc.client,

(c, rc) => new { c, rc })

.Join(((

Form3)Owner).db.meds,

rcm => rcm.rc.med,

m => m.id,

(rcm, m) => new { rcm, m })

.AsEnumerable()

.Select(x => new view (

x.rcm.rc.id,

x.rcm.c.number,

x.rcm.c.name,

x.m.name,

x.rcm.rc.diagnosis))

.OrderBy(o => o.c_number).ToList();

((Form3)Owner).dataGridView1.DataSource =

((Form3)Owner).viewrecipes;

((Form3)Owner).dataGridView1.Refresh();

this.Close();

Здесь важно понимать, что хоть данные отображаются в таблице с помощью viewrecipes, но все изменения необходимо производить с

63

Page 64: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

таблицей recipes нашей базы данных. И при изменении данных в ней потребуется и обновление списка, следовательно, и выполнение сложного запроса (в предыдущих формах, к слову, он был просто Select()). Так как меняем (или далее добавляем) всего один элемент, и точно знаем, какой, когда и где, то можно обойтись без кросс-запроса, обновляя параллельно и список viewrecipes, что гораздо проще с точки зрения кода. Однако это создает потенциальный «разрыв» между контекстом (БД) и списком, что чревато ошибками при последующих модификациях кода. Вместо этого лучше вынести LINQ-запрос и сопутствующую логику в отдельный метод, подобно тому, как это описывалось в предыдущем разделе.

С добавлением назначения не все так просто. Пользователь должен выбрать одного пациента, одно лекарство и указать диагноз. Понятно, что выбирать лучше всего из списка, для чего пользователю потребуется наличие в нем функции поиска. Пользуясь написанным ранее кодом, сделать это легко. С этой целью создадим новую форму и добавим на неё два элемента типа dataGridView для просмотра списка пациентов и списка лекарств. Под ними разместим необходимые для поиска элементы (два «комплекта», по одному на таблицу), поле для ввода диагноза и кнопку. В класс формы и в конструктор формы добавим следующий код:

Листинг 3.48 public partial class FormAddRec : Form {

public hospitalEntities db;

public List<clients> sheetclients;

public List<meds> sheetmeds;

public FormAddRec(string conStr) {

InitializeComponent();

db = new hospitalEntities(conStr);

label8.Visible = false;

// настройка таблицы пациентов

sheetclients=db.clients.OrderBy(o=>o.number).ToList();

dataGridView1.DataSource = sheetclients;

64

Page 65: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.48. Окончание dataGridView1.ReadOnly = true;

if (dataGridView1.RowCount == 0) label1.Visible =

true;

else label1.Visible = false;

dataGridView1.Columns[0].HeaderText = "№";

dataGridView1.Columns[5].HeaderText = "ФИО";

dataGridView1.Columns[5].DisplayIndex = 1;

dataGridView1.Columns[1].Visible = false;

dataGridView1.Columns[2].Visible = false;

dataGridView1.Columns[3].Visible = false;

dataGridView1.Columns[4].Visible = false;

dataGridView1.Columns[6].Visible = false;

dataGridView1.Columns[7].Visible = false;

dataGridView1.Columns[0].Width = 50;

dataGridView1.Columns[5].Width = 434;

// настройка таблицы лекарств

sheetmeds = db.meds.OrderBy(o => o.name).ToList();

dataGridView2.DataSource = sheetmeds;

dataGridView2.ReadOnly = true;

if (dataGridView2.RowCount == 0) label2.Visible =

true;

else label2.Visible = false;

dataGridView2.Columns[0].HeaderText = "Название";

dataGridView2.Columns[0].Width = 134;

dataGridView2.Columns[4].HeaderText = "Инструкция";

dataGridView2.Columns[4].Width = 350;

dataGridView2.Columns[1].Visible = false;

dataGridView2.Columns[2].Visible = false;

dataGridView2.Columns[3].Visible = false;

dataGridView2.Columns[5].Visible = false;

dataGridView2.Columns[6].Visible = false;

}

}

Для реализации поиска по таблицам необходимы два блока уже знакомого кода. В случае со списком пациентов оператор switch будет содержать:

Листинг 3.49 case 0: dataGridView1.DataSource = sheetclients

.Where(p => p.number.ToString() == textBox1.Text)

.ToList(); break;

case 1: dataGridView1.DataSource = sheetclients

.Where(p => p.name.ToString().Contains(textBox1.Text.))

.ToList(); break;

а в случае со списком лекарств:

65

Page 66: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.49. Окончание case 0: dataGridView2.DataSource = sheetmeds

.Where(p => p.name.ToString().Contains(textBox2.Text))

.ToList(); break;

case 1: dataGridView2.DataSource = sheetmeds

.Where(p => p.instruction.Contains(textBox2.Text))

.ToList(); break;

Важно также помнить, что за «пустоту» списка пациентов «отвечает» label1, а за список лекарств – label2. Интерфейс формы представлен на рисунке ниже.

Рис. 3.11. Форма добавления назначения в БД

Что касается кода добавления записи, то он следующий: Листинг 3.50

private void button3_Click(object sender, EventArgs e) {

if (dataGridView1.SelectedCells.Count == 1 &&

dataGridView2.SelectedCells.Count == 1) {

if (textBox3.Text.Trim() != "") {

try { ... }

catch (Npgsql.NpgsqlException err) {

label8.Visible = true;

label8.Text = "Ошибка ввода! " +

err.Message;

}

catch (System.FormatException err) {

label8.Visible = true;

label8.Text = "Ошибка ввода! " +

err.Message;

}

66

Page 67: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 3.50. Окончание }

else {

label8.Visible = true;

label8.Text = "Укажите диагноз!";

}

}

else {

label8.Visible = true;

label8.Text = "Выберите ровно одного пациента и ровно

одно лекарство!";

} }

Внутри оператора try будет следующий код, по структуре своей схожий с аналогичными блоками в предыдущих формах добавления записей:

Листинг 3.51 recipes result = new recipes();

result.client = Int32.Parse(dataGridView1

.SelectedCells[0].OwningRow.Cells[0].Value.ToString());

result.med = Int32.Parse(dataGridView2

.SelectedCells[0].OwningRow.Cells[5].Value.ToString());

result.diagnosis = textBox3.Text;

((Form3)Owner).db.recipes.Add(result);

((Form3)Owner).db.SaveChanges();

// блок кода для обновления таблицы

((Form3)Owner).viewrecipes = ((Form3)Owner).db.clients

.Join(((Form3)Owner).db.recipes,

c => c.number,

rc => rc.client,

(c, rc) => new { c, rc })

.Join(((Form3)Owner).db.meds,

rcm => rcm.rc.med,

m => m.id,

(rcm, m) => new { rcm, m })

.AsEnumerable()

.Select(x => new view(

x.rcm.rc.id,

x.rcm.c.number,

x.rcm.c.name,

x.m.name,

x.rcm.rc.diagnosis))

.OrderBy(o => o.c_number).ToList();

((Form3)Owner).label1.Visible = false;

((Form3)Owner).dataGridView1.DataSource =

((Form3)Owner).viewrecipes;

((Form3)Owner).dataGridView1.Refresh();

this.Close();

67

Page 68: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Изменение видимости label1 на форме Form3 здесь необходимо потому, что запись может добавляться в пустую таблицу recipes, следовательно, в таблице dataGridView1 формы Form3 появятся данные, и отображение этой надписи уже не требуется. Остается снова напомнить, что вынесение повторяющихся блоков кода с их «универсализацией» (таких, как кросс-запрос) будет правильным решением с точки зрения архитектуры приложения.

Задание по варианту

1. Реализуйте в клиентском приложении подключение к БД и авторизацию на сервере. Учтите обработку ошибок подключения и доступа к объектам БД.

2. Организуйте доступ к данным, хранящимся в БД [2]. Реализуйте как отображение отдельной таблицы в список, так и формирование объекта на основе содержимого нескольких таблиц – кросс-запрос.

3. Спроектируйте дизайн графического интерфейса приложения. Добавьте на форму необходимые таблицы, кнопки и текстовые поля. Свяжите их с соответствующими данными и событиями.

4. Реализуйте возможность навигации (поиска) по таблицам в приложении.

5. Реализуйте функционал редактирования, добавления и удаления данных для тех таблиц, где это необходимо. Учтите проверку данных, вводимых пользователем.

Контрольные вопросы

1. Ограничение доступа на уровне приложения. Основные этапы авторизации.

2. В чем состоят минусы использования SQL-запросов в коде приложения?

68

Page 69: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

3. Неявно типизированные переменные. Каковы преимущества и недостатки их использования?

4. Как получить данные из БД? Как сохранить изменения? Каким, по-вашему, должно быть время жизни контекста?

5. Как запретить попадание недоступных пользователю данных в контекст?

6. Зачем данные из контекста перемещаются в список объектов? Что будет, если каждый раз получать их из нового контекста?

7. Как можно работать со списком объектов? Как производится фильтрация и сортировка записей? В чем преимущества лямбда-выражений?

8. С помощью каких элементов интерфейса визуализируется содержимое таблиц БД?

9. Какое исключение возникнет, если попытаться получить закрытые пользователю данные? Правами на какие объекты БД, помимо таблиц, должен обладать пользователь?

10. Как отслеживать изменения в БД? Когда необходимо обновлять данные в элементах интерфейса?

11. Что такое модальная форма? Где их необходимо использовать? 12. Как проверять введенные пользователем данные? Какая

конструкция необходима для обработки исключений? 13. Как можно удалять записи из БД, используя Entity Framework? 14. Что такое транзакции? Какой паттерн «навязывается» Entity

Framework? 15. Получение данных из нескольких таблиц. Представления. Как

привести анонимный тип к перечисляемому? Подумайте, какие существуют альтернативы относительно предлагаемых вариантов реализации.

16. Паттерны проектирования. Как можно улучшить архитектуру приложения?

69

Page 70: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

4. ФОРМИРОВАНИЕ ОТЧЕТОВ

Выполнение лабораторных работ №7-8 по дисциплине «Базы данных» подразумевает решение еще одной задачи. Эта задача не связана с доступом к данным, но напрямую следует из него. Речь пойдет об автоматизированном формировании (генерации) отчетов. Отчет (в формулировке 1С – печатная форма) – документ, содержащий информацию в структурированном удобочитаемом виде. Ранее уже организовывались данные из базы данных для представления в таблице. Далее будем понимать отчет как офисный документ, содержащий выдержку из некоторой таблицы, как описание ситуации, и элементы статистики, как предпосылку к выводам и принятию решений по сложившийся ситуации.

4.1. Экспорт данных в XLS формат

Рассмотрим формирование отчета на примере списка пациентов. Сформируем документ Microsoft Office (таблицу Excel), в котором представим следующую информацию:

количество записей в данной таблице БД (список всех пациентов);

количество и список пациентов, находящихся в больнице в данный момент;

количество пациентов, состояние которых сейчас оценивается как тяжелое;

количество несовершеннолетних пациентов, находящихся в больнице;

дата формирования отчета.

Обязанности по подготовке такого документа возложим на регистраторов больницы. Для этого добавим в Основное рабочее окно регистратора еще одну кнопку, при нажатии которой будет

70

Page 71: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

отображаться диалоговое окно выбора места сохранения отчета и выполняться генерация отчета (рис. 4.1):

Рис. 4.1. Создание отчета в формате XLS

Логика работы будет следующая:

Листинг 4.1 private void button4_Click(object sender, EventArgs e) {

SaveFileDialog dialog = new SaveFileDialog();

dialog.InitialDirectory = Environment

.GetFolderPath(Environment.SpecialFolder.Desktop);

dialog.DefaultExt = ".xls";

dialog.Filter =

"Таблицы Excel (*.xls)|*.xls|Все файлы (*.*)|*.*";

dialog.FilterIndex = 1;

dialog.FileName = "Отчет";

if (dialog.ShowDialog() == DialogResult.OK) {

var file = new FileStream(dialog.FileName,

FileMode.Create,

FileAccess.ReadWrite);

...

}

}

Здесь создается новое диалоговое окно SaveFileDialog dialog, указывается, что по умолчанию необходимо сохранять файл Отчет.xls на Рабочем столе как Лист MS Excel 97-2003, и создается соответствующий FileStream. Осталось написать логику самого генератора, которая будет реализована с помощью библиотеки

71

Page 72: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

NPOI - .NET-версии Java Apache POI (the Java API for Microsoft Documents), позволяющей работать с документами Word и Excel. Получить библиотеку также просто, как и EF, – через Диспетчер пакетов NuGet (ИД: NPOI) (рис. 4.2).

Рис. 4.2. Пакет NPOI, установленный в проект

Apache POI содержит следующие компоненты API (таблица 4.1) для работы с MS Office-документами (цветом выделены компоненты, доступные в NPOI):

Таблица 4.1. Состав NPOI

Тип документа Компоненты API

Office 97-2003 Office 2007+

Excel HSSF XSSF Word HWPF XWPF

PowerPoint HSLF XSLF Outlook HSMF

Visio HDGF Publisher HPBF

72

Page 73: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Помимо них и в Apache POI, и в NPOI включены компоненты для работы с OpenXml и OLE2.

Стоит оговорить, что .docx и .xlsx – это zip-архивы с содержимым документа в формате XML, графическими файлами (если есть в документе) и XML-описанием стилей, разметки и отношениями между элементами контейнера. Поэтому альтернативой NPOI будет работа именно c XML-содержимым. Для организации чтения и записи xls-файлов, помимо Systrm.IO необходимо подключить пространство имен, соответствующее компоненту API HSSF:

using System.IO;

using NPOI.HSSF.UserModel;

Далее будем генерировать документ на основе xls-шаблона (в терминах 1С - макета печатной формы). Для этого создадим новую таблицу Excel и заполним ее, как показано на рисунке 4.3:

Рис. 4.3. Шаблон XLS-файла

Здесь шрифт выделенных ячеек – полужирный, а выравнивание текста в них – по центру. В эти ячейки будут вноситься значения, генерируемые кодом приложения. Строки 1-12 - header (1С – «шапка») шаблона, 13-16 – footer («подвал» в 1С), а между строками 12 и 13 будут вставляться новые, содержащие информацию о пациентах.

73

Page 74: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Упоминание «1С: Предприятие» здесь не случайно, структура решения здесь абсолютно идентична, поэтому при острой необходимости и небольших неоправданных усилиях возможна в некотором роде интеграция клиентского приложения с этой системой, так популярной в СНГ.

Чтобы уберечь файл шаблона от пользователя, необходимо спрятать его в ресурсах приложения (благо его размер в 63 КБ не сильно увеличит размер исполняемого файла): «Проект \ Свойства… \ Ресурсы \ Добавить ресурс \ Добавить существующий файл». Дополните приведенный выше код кнопки следующими строками:

Листинг 4.2 var template = new MemoryStream(

Properties.Resources.template, true);

var workbook = new HSSFWorkbook(template);

var sheet = workbook.GetSheetAt(0);

sheet.GetRow(2).GetCell(7)

.SetCellValue(DateTime.Today.ToShortDateString());

sheet.GetRow(8).GetCell(5).SetCellValue(sheetclients.Count());

sheet.GetRow(9).GetCell(5).SetCellValue(sheetclients

.Count(w => w.departure_date >= DateTime.Today));

sheet.GetRow(14).GetCell(5).SetCellValue(sheetclients

.Count(w => w.departure_date >= DateTime.Today &&

(DateTime.Today - w.birthday).Days / 365.2425 <=

18));

sheet.GetRow(15).GetCell(5).SetCellValue(sheetclients

.Count(w => w.departure_date >= DateTime.Today &&

w.condition == "тяжелое"));

Здесь создается новая рабочая книга HSSFWorkbook workbook на основе шаблона (если бы работа шла с xlsx-файлами, тогда тип был бы XSSFWorkbook). Далее получаем первый (нумерация с нуля) лист таблицы. Затем заполняем ячейки с датой составления отчета и количествами пациентов на основе LINQ-запросов. Здесь важно помнить, что индекс ячейки – это пара чисел, а нумерация начинается с нуля. Получаем строку листа GetRow(), затем ячейку GetCell() и устанавливаем в ней нужное значение методом SetCellValue(). Стиль ячейки при этом не меняется, а формат зависит от содержимого: при

74

Page 75: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

внесении значения типа string – общий, а при int – числовой. Является ли пациент совершеннолетним, можно проверить с помощью несложной формулы и LINQ-запроса. Даты перед записью на лист необходимо привести к короткому формату с помощью ToShortDateString().

Теперь сместим уже заполненный footer шаблона на необходимое (строки с 12 до конца, на sheetclients.Count(…) строк) количество строк вниз и организуем перебор всех нужных записей:

Листинг 4.3 sheet.ShiftRows(12, sheet.LastRowNum, sheetclients

.Count(w => w.departure_date >= DateTime.Today),

true,true);

int row = 12;

foreach (var item in sheetclients.OrderBy(o => o.name)

.Where(w => w.departure_date >= DateTime.Today)) {

...

}

Где каждая запись вносится в таблицу файла: Листинг 4.3 Продолжение

var rowInsert = sheet.CreateRow(row);

rowInsert.CreateCell(0).SetCellValue(row - 11);

rowInsert.GetCell(0).CellStyle =

sheet.GetRow(11).GetCell(0).CellStyle;

rowInsert.CreateCell(1).SetCellValue(item.number);

rowInsert.GetCell(1).CellStyle =

sheet.GetRow(11).GetCell(0).CellStyle;

rowInsert.CreateCell(2).SetCellValue(item.name);

rowInsert.GetCell(2).CellStyle =

sheet.GetRow(11).GetCell(0).CellStyle;

rowInsert.CreateCell(3)

.SetCellValue(item.birthday.ToShortDateString());

rowInsert.GetCell(3).CellStyle =

sheet.GetRow(11).GetCell(0).CellStyle;

rowInsert.CreateCell(4).SetCellValue(item.address);

rowInsert.GetCell(4).CellStyle =

sheet.GetRow(11).GetCell(0).CellStyle;

rowInsert.CreateCell(5)

.SetCellValue(item.income_date.ToShortDateString());

rowInsert.GetCell(5).CellStyle =

sheet.GetRow(11).GetCell(0).CellStyle;

rowInsert.CreateCell(6)

75

Page 76: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Листинг 4.3 Окончание .SetCellValue(item.departure_date.ToShortDateString());

rowInsert.GetCell(6).CellStyle =

sheet.GetRow(11).GetCell(0).CellStyle;

rowInsert.CreateCell(7).SetCellValue(item.condition);

rowInsert.GetCell(7).CellStyle =

sheet.GetRow(11).GetCell(0).CellStyle;

row++;

Как можно заметить, последовательность такова:

1) вставляется новая строка на лист; 2) заполняются соответствующие ячейки; 3) устанавливается их стиль «подражая» «донорской» ячейке [11, 0].

Очевидно, что для заполнения таблицы удобнее хранить данные в двумерном массиве, это позволило бы организовать цикл для обхода столбцов и соответствующих им полей записей. Однако для работы с БД выгоднее хранить записи как объекты, что сводит на нет применение второго индекса. Приведение List<clients> sheetclients к двумерному массиву вряд ли добавит удобства. Единственное, что можно здесь посоветовать, – организация отдельного метода: если необходимо формировать отчеты из нескольких таблиц, универсальный метод будет очень полезен. После всех манипуляций с таблицей нужно сохранить файл, в конструкцию if добавив workbook.Write(file);

и посмотреть результат в Excel (рис. 4.4):

Рис. 4.4. Сгенерированный отчет

76

Page 77: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

В результате проделанной работы получаем программный модуль, позволяющий не только генерировать отчеты по записям в БД, но и гибко настраивать их содержимое и внешний вид. Использование платформы .NET и тесно взаимодействующих с ней EF и NPOI позволяет легко реализовать обработку информации на всех без исключения этапах: при получении ее из БД, при выдаче пользователю, при экспорте в офисный документ.

Задание по варианту

1. Разработайте xls-шаблон отчета в удобном вам табличном процессоре, поддерживающем xls-формат. Добавьте файл шаблона в ресурсы приложения.

2. Реализуйте заполнение отчета согласно предметной области [2]. Помните, что отчет должен содержать не просто выборку из БД, но и некоторые вычисляемые значения: количество записей, среднее значение, минимальный или максимальный показатель по столбцу и т. д.

3. Реализуйте сохранение стиля ячеек при вставке строк на лист. 4. Реализуйте сохранение файла отчета и выбор директории для

сохранения через диалоговое окно.

Контрольные вопросы

1. Что такое печатная форма (отчет)? Какие требования предъявляются к его структуре?

2. Какие форматы офисных документов вы знаете? Какие библиотеки для Java и .NET позволяют с ними работать?

3. Реализуйте программный модуль генерации отчетов. Какие LINQ-запросы вы использовали?

4. Возможно ли решение обратной задачи? Каким образом искать необходимые данные в документе? Предложите решение.

77

Page 78: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

5. ЗАДАНИЯ К ЛАБОРАТОРНЫМ РАБОТАМ

Согласно своему варианту разработайте клиентское приложение, соответствующее следующим предметным областям [2]:

Вариант 1 – Клиентское приложение для учета деятельности аптеки: поступление и реализация лекарств. Вариант 2 − Клиентское приложение для учета деятельности продуктового магазина: поступление и реализация продовольственных товаров. Вариант 3 − Клиентское приложение для учета деятельности обувного магазина: поступление и реализация обуви. Вариант 4 − Клиентское приложение для учета деятельности магазина одежды: поступление и реализация одежды. Вариант 5 − Клиентское приложение для учета деятельности компьютерного магазина: поступление и реализация компьютеров. Вариант 6 − Клиентское приложение для учета посещаемости занятий в вузе (лекций, практических занятий и лабораторных работ): ведение журнала учета посещаемости. Вариант 7 − Клиентское приложение для учета сдачи зачетов и экзаменов в вузе (лекций, практических занятий и лабораторных работ): заполнение экзаменационных и зачетных ведомостей. Вариант 8 − Клиентское приложение для учета деятельности больницы: учет больных и наличия лекарств. Вариант 9 − Клиентское приложение для учета результатов вступительных экзаменов в вуз: заполнение экзаменационных ведомостей. Вариант 10 − Клиентское приложение для учета деятельности мастерской по ремонту часов: учет поступления заказов и комплектующих. Вариант 11 − Клиентское приложение для учета деятельности мастерской по ремонту обуви: учет поступления заказов и комплектующих.

78

Page 79: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

Вариант 12 − Клиентское приложение для учета коммуникационных услуг: учет внутригородских, междугородних и международных телефонных разговоров, интернет-услуг. Вариант 13 − Клиентское приложение для учета деятельности аэропорта: учет выполнения рейсов. Вариант 14 − Клиентское приложение для учета деятельности железнодорожного вокзала: учет выполнения рейсов. Вариант 15 − Клиентское приложение для учета деятельности автовокзала: учет выполнения рейсов. Вариант 16 − Клиентское приложение для учета деятельности речного порта: учет выполнения рейсов. Вариант 17 − Клиентское приложение для учета футбольных матчей: учет результатов игр (забитые голы, штрафы). Вариант 18 − Клиентское приложение для учета хоккейных матчей: учет результатов игр (забитые голы, штрафы). Вариант 19 − Клиентское приложение для учета волейбольных матчей: учет результатов игр (забитые голы, штрафы). Вариант 20 − Клиентское приложение для учета деятельности библиотеки: учет выдачи и возврата книг. Вариант 21 − Клиентское приложение для учета деятельности пекарни: учет выпуска продукции. Вариант 22 − Клиентское приложение для учета деятельности кондитерской фабрики: учет выпуска продукции. Вариант 23 − Клиентское приложение для учета деятельности обувной фабрики: учет выпуска продукции. Вариант 24 − Клиентское приложение для учета деятельности фармакологической фирмы: учет выпуска продукции. Вариант 25 − Клиентское приложение для учета деятельности фирмы по сборке компьютеров: учет выпуска продукции.

79

Page 80: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

ЗАКЛЮЧЕНИЕ

Выполнение заключительных лабораторных работ по дисциплине «Базы данных» подразумевает полную свободу выбора методов и средств разработки. И вовсе не обязательно следовать по пути SQL-операторов. Для многих платформ, базирующихся на объектно-ориентированных языках программирования, существует огромное количество библиотек, фреймворков и расширений, облегчающих разработку и сопровождение продукта.

В практикуме был рассмотрен фреймворк, являющийся ORM-решением, и показано, как можно управлять базой данных на уровне объектов. Entity Framework активно развивается и продолжает идеи .NET. В грядущей версии EF7 ожидается расширение списка поддерживаемых платформ (в приложениях Windows Phone и Windows Store, а также в системах Linux и Macintosh, где выполняется Mono), поддержка нереляционных хранилищ данных, и логичная, ради поддержки мобильных платформ, оптимизация потребления ресурсов. Таким образом, EF является важной и интересной частью перспективной платформы, и знакомство с ним в рамках лабораторных работ будет отличной опорой в будущих разработках любителей .NET. Именно поэтому в данном практикуме довольно подробно описан весь процесс разработки буквально по шагам, с указанием альтернатив и возможных улучшений архитектуры решения. Так как в подобных случаях важно дать рецепт, который сработает не только на различных вариантах задания к лабораторным работам, но и в последующих проектах. Для укрепления теоретической базы и совершенствования практических навыков рекомендуется следить за обновлениями EF и .NET, используя не только рекомендуемую литературу, но и интернет-ресурсы.

80

Page 81: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

Основная литература

1. Раттц, Д. LINQ: язык интегрированных запросов в C# 2008 для

профессионалов: пер. с англ. / Д. Раттц. – М. : ООО «И.Д.

Вильямс», 2008. – 560 с.

2. Токмаков, Г. П. Базы данных и знаний. Проектирование БД по

технологии «клиент-сервер» и разработка клиентских

приложений в среде Delphi: Методическое пособие к

лабораторным работам / Г.П. Токмаков – Ульяновск : УлГТУ,

2010. − 84 с.

3. Троелсен, Э. Язык программирования C# 5.0 и платформа

.NET 4.5: пер. с англ. / Э. Троелсен. – 6-е изд. – М. :

ООО «И.Д. Вильямс», 2013. – 1312 с.

4. Шамшев, А. Б. Работа с базами данных на языке C#. Технология

АDO .NET : учебное пособие / сост. О. Н. Евсеева,

А. Б. Шамшев. – Ульяновск: УлГТУ, 2009. – 170 с.

Дополнительная литература

5. Пугачев, С. В. Разработка приложений для Windows 8 на языке

C# / С. В. Пугачев, А. М. Шериев, К. А. Кичинский. – СПб. :

БХВ-Петербург, 2013. – 416 с.

6. Токмаков, Г. П. Базы данных. Концепция баз данных,

реляционная модель данных, языки SQL и XML : учебное

пособие / Г. П. Токмаков. – Ульяновск : УлГТУ, 2010. − 192 с.

Интернет-ресурсы

7. https://msdn.microsoft.com/ru-ru/library/bb386871(v=vs.90).aspx –

Общие сведения о службах объектов (платформа Entity

Framework) (дата обращения: 17.04.2016).

81

Page 82: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

8. https://msdn.microsoft.com/ru-ru/library/bb399567(v=vs.90).aspx –

Знакомство с платформой Entity Framework (дата обращения:

17.04.2016).

9. http://professorweb.ru/my/entity-framework/6/level1/1_6.php –

Пример использования Database First подхода в ASP.NET

проекте (дата обращения: 24.04.2016).

10. http://metanit.com/sharp/entityframework/5.1.php – Выполнение

прямых SQL-запросов с использованием DbContext (дата

обращения: 24.04.2016).

11. http://stackoverflow.com/questions/14788942/entity-framework-linq-

selecting-columns-from-multiple-tables – Выборка данных из

нескольких таблиц с помощью EF (дата обращения: 26.04.2016).

82

Page 83: ИСПОЛЬЗОВАНИЕ - ulstu.ruvenec.ulstu.ru/lib/disk/2016/167.pdf · В ПРОЕКТЕ С postgresql Практикум по дисциплине «Базы данных»

86

Учебное электронное издание

ЛЫЛОВА Анна Вячеславовна МОЛОТОВ Роман Сергеевич САХНОВ Альберт Андреевич

ИСПОЛЬЗОВАНИЕ ENTITY FRAMEWORK

В ПРОЕКТЕ С POSTGRESQL

Практикум

ЭИ № 746. Объем данных 1,68 Мб.

Редактор Н. А. Евдокимова

Печатное издание Подписано в печать 29.08.2016. Формат 60×84/16.

Усл. печ. л. 4,88. Тираж 50 экз. Заказ 738.

Ульяновский государственный технический университет 432027, г. Ульяновск, ул. Сев. Венец, д. 32.

ИПК «Венец» УлГТУ, 432027, г. Ульяновск, ул. Сев. Венец, д. 32. Тел.: (8422) 778-113

E-mail: [email protected] http://www.venec.ulstu.ru