ADO.NET: Архитектура клиентского приложения БД
насколько профессиональным со стороны программиста является использование внутри приложения автоматически создаваемого кода? Может лучше организовать некую свою архитектуру, например, один SQLConnection, SQLDataReader'ы, DataSet'ы и некоторые другие классы организвать в виде синглетонов, которые будут существовать в единственном экзмпляре и будут глобальными в рамках всего приложения и тем самым добиваясь некой параллели с внутренностями проекта, выполненного на Delphi?
Статический класс доступа к данным суть то же самое. :)
Если это расширяемо - вперёд, но я бы сделал одно узкое место и передавал данные через него.
Абстрагируйте доступ к данным в один класс, опишите методы доступа и не надо будет использовать потомков из компонентов. Это неоптимально, сами же понимаете. :)
А разве кто-то заставляет так делать? У нас, например, наша стандартная конторская заготовка для клиентских приложений делает просто и тупо: по XML-описанию генерируется форма, которая берёт данные по запросу, описанному в том же XML-описании. Своеобразно донельзя, но довольно удобно и чертовски расширяемо.
насколько профессиональным со стороны программиста является использование внутри приложения автоматически создаваемого кода?
System.Reflection рулит, притом нередко. :) Если по делу, то надо исходить из конкретной ситуации. Лично я такие вещи использовать не очень люблю, но - исключительно по причине личных предпочтений писать так, а не иначе. Хотя доводилось использовать и автогенерацию, и оно весьма неплохо работает.
Про синглтоны уже как-то говорилось. Открывать по соединению на окно, конечно же, не очень умно, но ведь этого делать и не нужно. Есть уровень интерфейса (пишем класс MyGUI), есть уровень доступа к данным (юзаем пространство System.Data) и есть уровень абстракции между первыми двумя (пишем класс DbAbstractor). SqlConnection достаточно создать один раз в классе DbAbstractor, который, грубо говоря, качает данные из БД в приложение и обратно. То есть, на входе у него методы ADO.NET, а на выходе - DataSet, DataTable и т.д. Когда клиентская часть запрашивает данные, в принципе, можно пойти многими путями: от динамической генерации запроса до передачи параметров в хранимую процедуру, всё это делается в виде указания параметров соответствующих методов класса DbAbstractor, а он сам обращается к БД и возвращает тот же DataSet. Потом его в памяти можно отфильтровать как надо, отобразить в DataGridView и сделать что угодно, это дело техники.
P.S. Я буду вам очень благодарен за абзацные отступы, поскольку с ними проще и читать, и отвечать.
А разве кто-то заставляет так делать? У нас, например, наша стандартная конторская заготовка для клиентских приложений делает просто и тупо: по XML-описанию генерируется форма, которая берёт данные по запросу, описанному в том же XML-описании. Своеобразно донельзя, но довольно удобно и чертовски расширяемо.
Я так понимаю, что XML-описание хранится где-то на сетевом ресурсе и у любого сотрудника вашей компании, у которого стоит ваше ПО, есть доступ к этому файлу. Поэтому любой пользователь может изменить и даже удалить данный файл. Или у вас построено всё иначе?
System.Reflection рулит, притом нередко. Если по делу, то надо исходить из конкретной ситуации. Лично я такие вещи использовать не очень люблю, но - исключительно по причине личных предпочтений писать так, а не иначе. Хотя доводилось использовать и автогенерацию, и оно весьма неплохо работает.
Поясните, пожалуйста, как Вы используете System.Reflection?
Про синглтоны уже как-то говорилось. Открывать по соединению на окно, конечно же, не очень умно, но ведь этого делать и не нужно. Есть уровень интерфейса (пишем класс MyGUI), есть уровень доступа к данным (юзаем пространство System.Data) и есть уровень абстракции между первыми двумя (пишем класс DbAbstractor). SqlConnection достаточно создать один раз в классе DbAbstractor, который, грубо говоря, качает данные из БД в приложение и обратно. То есть, на входе у него методы ADO.NET, а на выходе - DataSet, DataTable и т.д. Когда клиентская часть запрашивает данные, в принципе, можно пойти многими путями: от динамической генерации запроса до передачи параметров в хранимую процедуру, всё это делается в виде указания параметров соответствующих методов класса DbAbstractor, а он сам обращается к БД и возвращает тот же DataSet. Потом его в памяти можно отфильтровать как надо, отобразить в DataGridView и сделать что угодно, это дело техники.
Весьма логично. Я так и буду делать. Большой Вам респект.
P.S. Я буду вам очень благодарен за абзацные отступы, поскольку с ними проще и читать, и отвечать.
Да, хорошо, в следующий раз буду тексту придавать более читабельный вид.
Часто интерфейс пользователя бывает построен так, что есть окно с перечнем каких-либо загруженных данных из БД. На этом же окне располагаются кнопки "Добавить", "Редактировать", "Удалить". При нажатии на первые из двух кнопок открывается модальное окно с детальной информацией по выбранной записи из общего перечня. Как в каждой записи перечня хранить уникальный ключ и как его лучше передавать модальному окну? Sanila_san, Вы используете такой интерфейс?
Как в каждой записи перечня хранить уникальный ключ
Уже разобрался, вот ответ: Добавляем новые поля в количестве полей, которые составляют ключ (если ключ составной) и прямо в редакторе Columns устанавливаем свойство Visible каждого такого Column в состояние false.
Вот фрагмент XML-файла описания меню:
<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>
Вот пример описания формы:
<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>
Лично я System.Reflection не использую, поскольку нет такой необходимости. В одной из наработок нашей конторы это используется для создания форм, если не ошибаюсь.
Я по долгу службы этот интерфейс не использую, но навскидку могу предложить такое решение: в DataGridView имеется однозначное соответствие с номером строки в таблице DataSet, соответсвенно, по клику можно выбрать строку и все ячейки в ней. Она передаётся как параметр в функции формы. Нечто подобное у меня где-то использовалось. На самом деле всё может быть несколько сложнее, чем я объяснил, но принцип остаётся.
Я по долгу службы этот интерфейс не использую, но навскидку могу предложить такое решение: в DataGridView имеется однозначное соответствие с номером строки в таблице DataSet, соответсвенно, по клику можно выбрать строку и все ячейки в ней. Она передаётся как параметр в функции формы. Нечто подобное у меня где-то использовалось. На самом деле всё может быть несколько сложнее, чем я объяснил, но принцип остаётся.
А какой же у Вас интерфейс приложения? Как вы просматриваете детальную информацию по выбранному объекту предметной области из общего перечня?
насколько профессиональным со стороны программиста является использование внутри приложения автоматически создаваемого кода?
В нашем текущем проекте мы отказались от DataSet-ов вообще (для Веб приложения они тяжеловаты). Используется схема с самописными классами для DAL/DTO и BLL. При том каждый DAL класс, грубо говоря, - это набор хранимых процедур в базе данных.
Чтобы не писать много шаблонного кода для создания коммандеров (DbCommand) я сделал хитрый финт ушами - возложил написание этого кода на генератор. Внешне описание хранимой процедуры выглядит так:
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 - когда хранимка ничего не возвращает.
Подобный подход упрощает работу на этапе разработки софтины - изменил хранимку на сервере - изменил прототип хранимки в коде.
Очень просто. В каждой строке нужной таблицы имеется некий идентификатор, у нас это обычно GUID, но можно и любой другой. Даже если в нашей программе мы выбираем товар из общего перечня вида
| ID | Товар |
+----+-------+
А разве кто-то заставляет так делать? У нас, например, наша стандартная конторская заготовка для клиентских приложений делает просто и тупо: по XML-описанию генерируется форма, которая берёт данные по запросу, описанному в том же XML-описании. Своеобразно донельзя, но довольно удобно и чертовски расширяемо.
Это всё хорошо, но тогда для этого необходима некая утилита, которая из визуально создаваемой заготовки генерировала XML-описание. Если составлять такой файл вручную, то это займёт сного времени, особенно если окно имеет множество различных компонентов. Как Вы создаёте такой файл и почему отдали предпочтение хранить его в папке с приложением, а не в самой базе?
| ID | Товар |
+----+-------+
А компоненты окна (списки, листбоксы, комбобоксы и прочее) создаются динамически (in RunTime), взависимости от того какие данные получены на запрос или данные выдаются просто в некую консоль?
Действительно, разработан механизм, позволяющий генерировать такой файл по некоему описанию. Описание вводится в таблице, конкретно у нас это сделано не бог весть как удобно, но, в принципе, логично и осваиваемо. Правда, некоторые элементы удобно писать именно ручками - мы так правим запросы. Посмотрите пример ещё раз, в нём нет ничего сложного.
Файл описания меню создаётся в Visual Studio. В папке с программой (точнее, в соседней папке, но сути это не меняет) его хранить удобно исключительно по организационным причинам. На безопасность он никак не влияет, поскольку все разрешения устанавливаются на уровне доступа к данным. Файл описания списков может быть как файлом, так и строчками в БД, поскольку всё равно хранится XML. А в моём случае он как раз хранится в базе, что не очень-то удобно, кстати.
Компоненты окна создаются динамически по описанию. Собственно говоря, у нас тут все динамические формы выглядят похоже: это табличка, производная от DataGridView, кнопки, комбобоксы, текстбоксы, ещё доступен календарь для выбора даты. Всё. Все остальные формы (такие, как "Отправка сообщений вручную" или "Расчёт кредита для физических лиц") пишутся ручками в студии и вызываются методом MyForm.Show() (см. XML-описание списка).