Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

ADO.NET: Архитектура клиентского приложения БД

1.8K
30 января 2008 года
rSolanov
106 / / 04.05.2005
Здравствуйте! Хотелось бы поговорить об архитектуре клиентского приложения базы данных, использующего в качестве доступа к БД ADO.NET. Начну с организации приложения, написанного на Delphi. В проекте, написанном на Delphi, можно создать класс-контейнер TDataModule, в котором можно добавить все необходимые для работы приложения компоненты. Среди них это и компоненты, работающие с БД, а именно: TADOConnection, TADOStoredProc, TADOTable, TADODataSet, TADOCommand и др. в их необходимом количестве. Указатель на объект класса TDataModule является глобальным в рамках всего приложения и любой DB-компонент любого окна можно связать с инкапсулированными в TDataModule компонентами и таким образом обеспечить взаимодействие интерфейса пользователя и данными БД. Важным моментом является то, что одному объекту базы данных в большинстве случаев (хотя всё зависит от реализации) можно поставить в соответствие один объект в приложении, например одна таблица в БД-один объект класса TADOTable. Пишется это достаточно быстро (много создаётся автоматичеки самой IDE), так же как и работает и все от этого счастливы. Теперь рассмотрим Visual Studio .Net с ADO.Net. Данная среда разработки также, как и IDE Delphi, даёт возможность программисту создать основу приложения в режиме in DesignTime автоматически, практически без написания кода "вручную". Но давайте обратим внимание: насколько оптимальным является код, создаваемый в VS всмысле архитектуры создаваемого кода, так сказать, со всеми от сюда вытекающими. Например, есть приложение, в котором имеется несколько десятков окон, в каждом из которых имеются компоненты, работающие с БД и как мы уже договорились, настроенными автоматически (т.е. автоматически сгенерированные классы-потомки от SQLDataAdapter и DataSet). Как пример, на мой взгляд "неоптимальной" архитектуры является наличие в каждом классе потомке от SQLDataAdapter инкапсулированного SQLConnection. Таких адаптеров целая уйма (для каждого окна как минимум один) и внутри каждого из них создаётся SQLConnection. А ведь по-сути база часто бывает только одна. И напрашивается вопрос:
насколько профессиональным со стороны программиста является использование внутри приложения автоматически создаваемого кода? Может лучше организовать некую свою архитектуру, например, один SQLConnection, SQLDataReader'ы, DataSet'ы и некоторые другие классы организвать в виде синглетонов, которые будут существовать в единственном экзмпляре и будут глобальными в рамках всего приложения и тем самым добиваясь некой параллели с внутренностями проекта, выполненного на Delphi?
241
30 января 2008 года
Sanila_san
1.6K / / 07.06.2005
Цитата: rSolanov
Указатель на объект класса TDataModule является глобальным в рамках всего приложения и любой DB-компонент любого окна можно связать с инкапсулированными в TDataModule компонентами…

Статический класс доступа к данным суть то же самое. :)

Цитата: rSolanov
Важным моментом является то, что одному объекту базы данных в большинстве случаев (хотя всё зависит от реализации) можно поставить в соответствие один объект в приложении, например одна таблица в БД-один объект класса TADOTable. Пишется это достаточно быстро (много создаётся автоматичеки самой IDE), так же как и работает и все от этого счастливы.

Если это расширяемо - вперёд, но я бы сделал одно узкое место и передавал данные через него.

Цитата: rSolanov
Теперь рассмотрим Visual Studio .Net с ADO.Net. Данная среда разработки также, как и IDE Delphi, даёт возможность программисту создать основу приложения в режиме in DesignTime автоматически, практически без написания кода "вручную". Но давайте обратим внимание: насколько оптимальным является код, создаваемый в VS всмысле архитектуры создаваемого кода, так сказать, со всеми от сюда вытекающими. Например, есть приложение, в котором имеется несколько десятков окон, в каждом из которых имеются компоненты, работающие с БД и как мы уже договорились, настроенными автоматически (т.е. автоматически сгенерированные классы-потомки от SQLDataAdapter и DataSet).

Абстрагируйте доступ к данным в один класс, опишите методы доступа и не надо будет использовать потомков из компонентов. Это неоптимально, сами же понимаете. :)

Цитата: rSolanov
Как пример, на мой взгляд "неоптимальной" архитектуры является наличие в каждом классе потомке от SQLDataAdapter инкапсулированного SQLConnection. Таких адаптеров целая уйма (для каждого окна как минимум один) и внутри каждого из них создаётся SQLConnection. А ведь по-сути база часто бывает только одна.

А разве кто-то заставляет так делать? У нас, например, наша стандартная конторская заготовка для клиентских приложений делает просто и тупо: по XML-описанию генерируется форма, которая берёт данные по запросу, описанному в том же XML-описании. Своеобразно донельзя, но довольно удобно и чертовски расширяемо.

Цитата: rSolanov
И напрашивается вопрос:
насколько профессиональным со стороны программиста является использование внутри приложения автоматически создаваемого кода?

System.Reflection рулит, притом нередко. :) Если по делу, то надо исходить из конкретной ситуации. Лично я такие вещи использовать не очень люблю, но - исключительно по причине личных предпочтений писать так, а не иначе. Хотя доводилось использовать и автогенерацию, и оно весьма неплохо работает.

Цитата: rSolanov
Может лучше организовать некую свою архитектуру, например, один SQLConnection, SQLDataReader'ы, DataSet'ы и некоторые другие классы организвать в виде синглетонов, которые будут существовать в единственном экзмпляре и будут глобальными в рамках всего приложения и тем самым добиваясь некой параллели с внутренностями проекта, выполненного на Delphi?

Про синглтоны уже как-то говорилось. Открывать по соединению на окно, конечно же, не очень умно, но ведь этого делать и не нужно. Есть уровень интерфейса (пишем класс MyGUI), есть уровень доступа к данным (юзаем пространство System.Data) и есть уровень абстракции между первыми двумя (пишем класс DbAbstractor). SqlConnection достаточно создать один раз в классе DbAbstractor, который, грубо говоря, качает данные из БД в приложение и обратно. То есть, на входе у него методы ADO.NET, а на выходе - DataSet, DataTable и т.д. Когда клиентская часть запрашивает данные, в принципе, можно пойти многими путями: от динамической генерации запроса до передачи параметров в хранимую процедуру, всё это делается в виде указания параметров соответствующих методов класса DbAbstractor, а он сам обращается к БД и возвращает тот же DataSet. Потом его в памяти можно отфильтровать как надо, отобразить в DataGridView и сделать что угодно, это дело техники.

P.S. Я буду вам очень благодарен за абзацные отступы, поскольку с ними проще и читать, и отвечать.

1.8K
31 января 2008 года
rSolanov
106 / / 04.05.2005
Цитата: Sanila_san

А разве кто-то заставляет так делать? У нас, например, наша стандартная конторская заготовка для клиентских приложений делает просто и тупо: по XML-описанию генерируется форма, которая берёт данные по запросу, описанному в том же XML-описании. Своеобразно донельзя, но довольно удобно и чертовски расширяемо.


Я так понимаю, что XML-описание хранится где-то на сетевом ресурсе и у любого сотрудника вашей компании, у которого стоит ваше ПО, есть доступ к этому файлу. Поэтому любой пользователь может изменить и даже удалить данный файл. Или у вас построено всё иначе?

Цитата: Sanila_san

System.Reflection рулит, притом нередко. Если по делу, то надо исходить из конкретной ситуации. Лично я такие вещи использовать не очень люблю, но - исключительно по причине личных предпочтений писать так, а не иначе. Хотя доводилось использовать и автогенерацию, и оно весьма неплохо работает.


Поясните, пожалуйста, как Вы используете System.Reflection?

Цитата: Sanila_san

Про синглтоны уже как-то говорилось. Открывать по соединению на окно, конечно же, не очень умно, но ведь этого делать и не нужно. Есть уровень интерфейса (пишем класс MyGUI), есть уровень доступа к данным (юзаем пространство System.Data) и есть уровень абстракции между первыми двумя (пишем класс DbAbstractor). SqlConnection достаточно создать один раз в классе DbAbstractor, который, грубо говоря, качает данные из БД в приложение и обратно. То есть, на входе у него методы ADO.NET, а на выходе - DataSet, DataTable и т.д. Когда клиентская часть запрашивает данные, в принципе, можно пойти многими путями: от динамической генерации запроса до передачи параметров в хранимую процедуру, всё это делается в виде указания параметров соответствующих методов класса DbAbstractor, а он сам обращается к БД и возвращает тот же DataSet. Потом его в памяти можно отфильтровать как надо, отобразить в DataGridView и сделать что угодно, это дело техники.


Весьма логично. Я так и буду делать. Большой Вам респект.

Цитата: Sanila_san

P.S. Я буду вам очень благодарен за абзацные отступы, поскольку с ними проще и читать, и отвечать.


Да, хорошо, в следующий раз буду тексту придавать более читабельный вид.

Часто интерфейс пользователя бывает построен так, что есть окно с перечнем каких-либо загруженных данных из БД. На этом же окне располагаются кнопки "Добавить", "Редактировать", "Удалить". При нажатии на первые из двух кнопок открывается модальное окно с детальной информацией по выбранной записи из общего перечня. Как в каждой записи перечня хранить уникальный ключ и как его лучше передавать модальному окну? Sanila_san, Вы используете такой интерфейс?

1.8K
31 января 2008 года
rSolanov
106 / / 04.05.2005
Цитата: rSolanov

Как в каждой записи перечня хранить уникальный ключ


Уже разобрался, вот ответ: Добавляем новые поля в количестве полей, которые составляют ключ (если ключ составной) и прямо в редакторе Columns устанавливаем свойство Visible каждого такого Column в состояние false.

241
01 февраля 2008 года
Sanila_san
1.6K / / 07.06.2005
Цитата: rSolanov
Я так понимаю, что XML-описание хранится где-то на сетевом ресурсе и у любого сотрудника вашей компании, у которого стоит ваше ПО, есть доступ к этому файлу. Поэтому любой пользователь может изменить и даже удалить данный файл. Или у вас построено всё иначе?

Вот фрагмент XML-файла описания меню:

Код:
<menuitem Text="Статистика">
        <menuitem Text="Продажи за месяц" Method="frmList_Show" Library="VSWinList07.dll" Class="VersioSoft.VSWinList07.ShowList" MDIParent="">
          <parms>
            <parm Value="frm_Sales30" />
          </parms>
      </menuitem>
        <menuitem Text="Продажи за неделю" Method="frmList_Show" Library="VSWinList07.dll" Class="VersioSoft.VSWinList07.ShowList" MDIParent="">
          <parms>
            <parm Value="frm_Sales7" />
          </parms>
        </menuitem>
        <menuitem Text="Продажи с начала месяца" Method="frmList_Show" Library="VSWinList07.dll" Class="VersioSoft.VSWinList07.ShowList" MDIParent="">
          <parms>
            <parm Value="frm_SalesFTM" />
          </parms>
        </menuitem>
       </menuitem>


Вот пример описания формы:
Код:
<listmain>
  <vstable tableName="SMSSentMessages" vssource="SMSShowMsgTransactions" vscaption="СМС-транзакции" sourcename="MainSource" />
  <vscolumn header="Номер клиента" align="center" readonly="true" visible="true" type="textbox" mappingname="nvcDestinationNumber" width="100" />
  <vscolumn header="Номер системы" align="center" readonly="true" visible="true" type="textbox" mappingname="nvcDestNumber" width="100" />
  <vscolumn header="Текст запроса" align="left" readonly="true" visible="true" type="textbox" mappingname="nvcMessagePayload" width="100" />
  <vscolumn header="Дата запроса" align="left" readonly="true" visible="true" type="textbox" mappingname="dtDeliveryDate" width="100" />
  <vscolumn header="Текст ответа" align="center" readonly="true" visible="true" type="textbox" mappingname="Expr1" width="100" />
  <vscolumn header="Дата ответа" align="center" readonly="true" visible="true" type="textbox" mappingname="dtSentDate" width="100" />
  <vscolumn header="Статус доставки" align="center" readonly="true" visible="true" type="textbox" mappingname="tintDeliveryStatus" width="100" />
  <vscolumn header="GUID отправленного сообщения" align="center" readonly="true" visible="false" type="textbox" mappingname="guidSentMessageID" width="125" />
</listmain>
Я говорил об этом. В конкретно моём случае каждое такое описание хранится в строках таблицы БД, но можно хранить и в файле. Как видите, тут ничего секретного нет, и если ручками подредактировать файл, от этого в учшем случае изменится ширина поля, в худшем - форма не отобразится. Все эти XML-файлы лежат локально вместе с экзешником программы, в папке для файлов настроек.

Цитата: rSolanov
Поясните, пожалуйста, как Вы используете System.Reflection?

Лично я System.Reflection не использую, поскольку нет такой необходимости. В одной из наработок нашей конторы это используется для создания форм, если не ошибаюсь.

Цитата: rSolanov
Часто интерфейс пользователя бывает построен так, что есть окно с перечнем каких-либо загруженных данных из БД. На этом же окне располагаются кнопки "Добавить", "Редактировать", "Удалить". При нажатии на первые из двух кнопок открывается модальное окно с детальной информацией по выбранной записи из общего перечня. Как в каждой записи перечня хранить уникальный ключ и как его лучше передавать модальному окну? Sanila_san, Вы используете такой интерфейс?

Я по долгу службы этот интерфейс не использую, но навскидку могу предложить такое решение: в DataGridView имеется однозначное соответствие с номером строки в таблице DataSet, соответсвенно, по клику можно выбрать строку и все ячейки в ней. Она передаётся как параметр в функции формы. Нечто подобное у меня где-то использовалось. На самом деле всё может быть несколько сложнее, чем я объяснил, но принцип остаётся.

1.8K
01 февраля 2008 года
rSolanov
106 / / 04.05.2005
Цитата: Sanila_san

Я по долгу службы этот интерфейс не использую, но навскидку могу предложить такое решение: в DataGridView имеется однозначное соответствие с номером строки в таблице DataSet, соответсвенно, по клику можно выбрать строку и все ячейки в ней. Она передаётся как параметр в функции формы. Нечто подобное у меня где-то использовалось. На самом деле всё может быть несколько сложнее, чем я объяснил, но принцип остаётся.


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

5
02 февраля 2008 года
hardcase
4.5K / / 09.08.2005
Цитата: rSolanov
И напрашивается вопрос:
насколько профессиональным со стороны программиста является использование внутри приложения автоматически создаваемого кода?


В нашем текущем проекте мы отказались от DataSet-ов вообще (для Веб приложения они тяжеловаты). Используется схема с самописными классами для DAL/DTO и BLL. При том каждый DAL класс, грубо говоря, - это набор хранимых процедур в базе данных.
Чтобы не писать много шаблонного кода для создания коммандеров (DbCommand) я сделал хитрый финт ушами - возложил написание этого кода на генератор. Внешне описание хранимой процедуры выглядит так:

 
Код:
public abstract class SecurityDataAccess : DataAccessBase {
        public SecurityDataAccess(DataContext context)
            : base(context) {
        }

        [StoredProcedure("sdo_security_GetRights")]
        public abstract AccessListEntry GetRights(Guid principal_id, Guid object_id);
...
}
Во время работы приложения у специальной фабрики мы спрашиваем инстанс на экземпляр этого абстрактного класса.
При первом обращении запускается анализатор отражения и генератор C#-кода (CodeDOM) - вместе они создают реализацию абстрактных методов, помеченных атрибутом StoredProcedureAttribute. Т.е. (пока что) в рантайме компилируется сборка с классом-наследником и тут-же подгружается, наследник сразу же инстанциируется.
AccessListEntry - это некий DTO объект, он имеет конструктор по умолчанию и несколько свойств с атрибутом DataColumnAttribute. После вызова хранимой процедуры из датаридера данные зашиваются в экземпляр такого класса, свойства объекта и столбцы датаридера соотносятся по атрибуту. Также поддерживаются коллекции объектов, визуально это выглядит как прототип с результатом ICollection<AccessListEntry>, ну и простейшие случаи с void - когда хранимка ничего не возвращает.

Подобный подход упрощает работу на этапе разработки софтины - изменил хранимку на сервере - изменил прототип хранимки в коде.
241
04 февраля 2008 года
Sanila_san
1.6K / / 07.06.2005
Цитата: rSolanov
А какой же у Вас интерфейс приложения? Как вы просматриваете детальную информацию по выбранному объекту предметной области из общего перечня?

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

 
Код:
+----+-------+
| ID | Товар |
+----+-------+
у нас имеется поле ID, по которому можно запросить в БД описание Товара и вообще всё, что с ним может быть связано. Говоря детально, по клику открывается форма, туда передаётся ID и уже эта форма запрашивает нужные данные.
1.8K
05 февраля 2008 года
rSolanov
106 / / 04.05.2005
Цитата: Sanila_san

А разве кто-то заставляет так делать? У нас, например, наша стандартная конторская заготовка для клиентских приложений делает просто и тупо: по XML-описанию генерируется форма, которая берёт данные по запросу, описанному в том же XML-описании. Своеобразно донельзя, но довольно удобно и чертовски расширяемо.


Это всё хорошо, но тогда для этого необходима некая утилита, которая из визуально создаваемой заготовки генерировала XML-описание. Если составлять такой файл вручную, то это займёт сного времени, особенно если окно имеет множество различных компонентов. Как Вы создаёте такой файл и почему отдали предпочтение хранить его в папке с приложением, а не в самой базе?

Цитата: Sanila_san
Очень просто. В каждой строке нужной таблицы имеется некий идентификатор, у нас это обычно GUID, но можно и любой другой. Даже если в нашей программе мы выбираем товар из общего перечня вида
 
Код:
+----+-------+
| ID | Товар |
+----+-------+
у нас имеется поле ID, по которому можно запросить в БД описание Товара и вообще всё, что с ним может быть связано. Говоря детально, по клику открывается форма, туда передаётся ID и уже эта форма запрашивает нужные данные.


А компоненты окна (списки, листбоксы, комбобоксы и прочее) создаются динамически (in RunTime), взависимости от того какие данные получены на запрос или данные выдаются просто в некую консоль?

241
05 февраля 2008 года
Sanila_san
1.6K / / 07.06.2005
Цитата: rSolanov
Это всё хорошо, но тогда для этого необходима некая утилита, которая из визуально создаваемой заготовки генерировала XML-описание. Если составлять такой файл вручную, то это займёт сного времени, особенно если окно имеет множество различных компонентов.

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

Цитата: rSolanov
Как Вы создаёте такой файл и почему отдали предпочтение хранить его в папке с приложением, а не в самой базе?

Файл описания меню создаётся в Visual Studio. В папке с программой (точнее, в соседней папке, но сути это не меняет) его хранить удобно исключительно по организационным причинам. На безопасность он никак не влияет, поскольку все разрешения устанавливаются на уровне доступа к данным. Файл описания списков может быть как файлом, так и строчками в БД, поскольку всё равно хранится XML. А в моём случае он как раз хранится в базе, что не очень-то удобно, кстати.

Цитата: rSolanov
А компоненты окна (списки, листбоксы, комбобоксы и прочее) создаются динамически (in RunTime), взависимости от того какие данные получены на запрос или данные выдаются просто в некую консоль?

Компоненты окна создаются динамически по описанию. Собственно говоря, у нас тут все динамические формы выглядят похоже: это табличка, производная от DataGridView, кнопки, комбобоксы, текстбоксы, ещё доступен календарь для выбора даты. Всё. Все остальные формы (такие, как "Отправка сообщений вручную" или "Расчёт кредита для физических лиц") пишутся ручками в студии и вызываются методом MyForm.Show() (см. XML-описание списка).

241
05 февраля 2008 года
Sanila_san
1.6K / / 07.06.2005
Но тут надо понимать, что у нас все приложения пишутся под определённых клиентов, и заранее под них узкозаточены. :) Поэтому если вдруг надо динамически описывать какие-то формы произвольного вида, тут пахнет усложнением XML-описания и диким усложнением автогенерации. В нашем же случае если есть форма, скажем, генерации пенсионного договора, то эта форма согласованно с заказчиком делается в Visual Studio как отдельный проект и потом просто подключается к оболочке.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог