Во время работы над созданием приложений баз данных, работа так или иначе идет с некими объектами - сущностями.
Сущность - не что иное, как набор таблиц (впрочем, может быть и одна таблица), связанных между собой и моделирующих некий объект реального мира.
Сущности могут иметь следующую функциональность:
- посмотреть/показать список (всех экземпляров данной сущности).
- добавить (экземпляр сущности);
- сформировать отчет (ы);
- отредактировать (экземпляр сущности);
- удалить (экземпляр сущности);
- и др.
Заметим, что некоторые методы (реализующие ту или иную функциональность) сущности требуют задания параметров, например, для удаления нужно задать ключ, для метода обновления данных, кроме ключа, неплохо бы задать и другие параметры (характеризующие сущность).
Определим интерфейс сущности (исходные тексты приведены на C#):
public interface IDBEntity
{
object List ();
object Add (CDBEntityParameters dbEntityparameters);
object Report (CDBEntityParameters dbEntityparameters);
object Edit (CDBEntityParameters dbEntityparameters);
object Delete (CDBEntityParameters dbEntityparameters);
}
Угу, правильно, забыли вписать в него свойство, идентифицирующее сущность, попросту говоря, наименование самой сущности, сразу же впишем и строку соединения с БД (все равно понадобится):
public interface IDBEntity
{
string EntityName
{ get; }
string ConnectionString
{ get; }
object List ();
object Add (CDBEntityParameters dbEntityParameters);
object Report (CDBEntityParameters dbEntityParameters);
object Edit (CDBEntityParameters dbEntityParameters);
object Delete (CDBEntityParameters dbEntityParameters);
}
То есть теперь мы можем написать, примерно такой код (с учетом определенного выше интерфейса и где-то, наверняка, определенного класса CDBEntity, реализующего интерфейс IDBEntity):
CDBEntity Entity = new CDBEntity(companies, connectionString);
Entity.List();
Что должно произойти при выполнении данного кода? Неплохо было бы, если б данный метод вернул содержимое того, что мы подразумеваем под сущностью companies, в данном случае, это могла бы быть таблица Entity.dbo.Companies (точно, угадали, все sql-скрипты, что могут быть приведены в данной статье, будут на T-SQL).
CDBEntity Entity = new CDBEntity(companies);
DataTable entityTable = Entity.List();
foreach( DataRow row in entityTable.Rows )
{
// а здесь мог бы быть код работы с таблицей
}
Откуда класс CDEntity мог бы узнать о том, что для сущности companies надо что-то делать (читать, удалять, обновлять, формировать набор данных для отчета) именно с таблицей Entity.dbo.Companies?
Многие из Вас их уже когда-нибудь, как-нибудь, где-нибудь да использовали. Первый раз я такое использовал году эдак в 1996, когда писал автоматизированные рабочие места, попросту АРМы, на FoxPro 2.0/2.6 for DOS.
Определимся, что нам нужно для того, чтобы наш класс CDBEntity, реализующий интерфейс IDBEntity, действительно мог бы реализовать методы работы с сущностями.
Определимся также, что нам может потребоваться от такого класса, кроме как реализация очевидных методов работы с сущностью. Например, поддержка разграничения прав доступа пользователей (не каждому пользователю будет разрешено выполнять операцию удалить или добавить, а многим будет разрешено только смотреть).
Немного опережая события замечу, что некоторые пользователи, не зная великий и могучий русский язык, захотят работать с приложениями, разработанными на основе описываемой архитектуры, но на других великих и могучих языках.
А теперь глянем на схему:
рис.1 (схема таблиц метаданных)
Посмотрим на содержимое некоторых таблиц:
рис.3 (table EntitySQLCommands)
А теперь глянем на реализацию метода, например, Edit:
public virtual void Edit (CDBEntityParameter[] dbEntityParameters)
{
if (AllowDelete)
{
if ((FConnectionString != "") & (FEntityName != ""))
{
SqlConnection sqlCon = new SqlConnection( FConnectionString );
try
{
sqlCon.Open();
SqlCommand sqlComUpdate = new SqlCommand (FSqlExpressionUpdate, sqlCon);
foreach (CDBEntityParameter par in dbEntityParameters)
{
if (FSqlExpressionUpdate.IndexOf(par.ParameterName ) > 0)
sqlComUpdate.Parameters.Add(par.ParameterName, par.ParameterType).Value = par.ParameterValue;
}
sqlComUpdate.ExecuteNonQuery();
}
catch ( SqlException ex )
{
Console.WriteLine( ex.Message );
}
finally
{
sqlCon.Close();
}
}
}
else
{
throw( new Exception("You do not have permission to edit : " + FentityName));
}
}
Переменная FSqlExpressionUpdate может содержать, например, такое выражение: exec Entity.dbo.UpdateCompany @id, @nameCompany, а сама процедура Entity.dbo.UpdateCompany может выглядеть, например, так:
create procedure dbo.UpdateCompany (@id int, @nameCompany nvarchar(50))
as
set nocount on
update Entity.dbo.Companies
set nameCompany = @nameCompany
where id = @id
go
А переменная FAllowUpdate содержит true, если текущий пользователь имеет право на редактирование данной сущности.
Пожалуй, для реализации класса, работающего с сущностями БД, используя метаданные, мы написали достаточно.
Теперь надо бы подумать и о визуализации данных. То есть поговорим о контролах.
Для этого неплохо было бы, если бы контрол знал о существовании сущности, с которой он работает, то есть:
public interface IDBControl
{
IDBEntity Entity
{ get; set; }
}
Посмотрим на реализацию контрола, некий вариант ComboBox, точнее, посмотрим только на реализацию того, что предопределено в интерфейсе:
public sealed class DBComboBox : System.Windows.Forms.UserControl, IDBControl
{
public System.Windows.Forms.ComboBox cb;
private IDBEntity FEntity;
public IDBEntity Entity
{
get { return FEntity; }
set {
FEntity = value;
if ( FEntity != null )
{
if (FEntity != null )
{
...
cb.DataSource = (DataTable)FEntity.List ();
...
}
}
}
}
...
}
Как видно из текста, при присвоении (в runtime) экземпляра сущности контролу, происходит инициализация DataSource, и контрол, в принципе, готов к использованию.
Наследником интерфейса IDBControl, можно сделать, практически любой компонент, например, System.Windows.Forms.Panel, System.Windows.Forms.Form. Вы, пока подумайте над перспективами, а я продолжу работать.
Предположим, у нас уже есть форма DBFormEntity, наследник IDBControl, есть также форма DBFormEntityList и DBFormEntityEdit. Первая, как можно догадаться из названия, отображает что-то списочное, например, для выбора (ListView, DataGrid, TreeVew), вторая предоставляет возможность редактирования экземпляров сущности.
Предположим даже, что у нас есть даже интерфейсы: IDBControl, IDBFormEntityList, IDFormEntityEdit.
Глянем на класс:
public class CEntity
{
public static object FormList(IDBEntity Entity)
{
object result = null;
try
{
Assembly formAssembly = Assembly. LoadFrom (Entity.AssemblyNameList);
Type formType = formAssembly.GetType(Entity.FormNameList
FormEntityList form = (FormEntityList)Activator.CreateInstance(formType);
form.Entity = Entity;
if (form.ShowDialog() == DialogResult.OK)
{
result = form.KeyValue;
}
}
catch
{
}
return result;
}
public static object[] FormEdit(IDBEntity Entity)
{
object[] result = null;
try
{
Assembly formAssembly = Assembly. LoadFrom (Entity.AssemblyNameEdit);
Type formType = formAssembly.GetType(Entity.FormNameEdit
FormEntityEdit form = (FormEntityEdit)Activator.CreateInstance(formType);
form.Entity = Entity;
if (form.ShowDialog() == DialogResult.OK)
{
result = form.Parameters;
}
}
catch
{
}
return result;
}
}
Вызвать форму просмотра можно, например, так:
CEntity.FormList (Entity);
Пару слов об организации solutions:
- первый содержит описания всех интерфейсов и вспомогательных классов подобных CDBEntityParameters;
- второй может содержать реализацию контролов и форм;
- третий реализует интерфейс IDBEntity в классе CDBEntity;
Как организовать остальное, то есть формы, ориентированные на ту или иную сущность, реализацию классов сущностей (наследников CDBEntity или его экземпляры) и главную форму с реализацией метода Main(), наверное, уже не столь важно. Впрочем, стоит внимательно и хорошо подумать и об этом.
Данная организация позволит разрабатывать как уже ставшие привычными двухуровневые системы, так и трехуровневые, используя COM+ или .NET Remoting.
Собственно, это всё, читайте, думайте, спрашивайте, предлагайте.