Статья
Версия для печати
Обсудить на форуме
Объектно-компонентная платформа создания приложений БД.


Введение


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

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

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


Кодинг


Определим интерфейс сущности (исходные тексты приведены на C#):

Код: (C++)
public interface IDBEntity
{
        object List ();
        object Add (CDBEntityParameters dbEntityparameters);
        object Report (CDBEntityParameters dbEntityparameters);
        object Edit (CDBEntityParameters dbEntityparameters);
        object Delete (CDBEntityParameters dbEntityparameters);
}

Угу, правильно, забыли вписать в него свойство, идентифицирующее сущность, попросту говоря, наименование самой сущности, сразу же впишем и строку соединения с БД (все равно понадобится):

Код: (C++)
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):

Код: (C++)
CDBEntity Entity = new CDBEntity(companies, connectionString);
Entity.List();

Что должно произойти при выполнении данного кода? Неплохо было бы, если б данный метод вернул содержимое того, что мы подразумеваем под сущностью companies, в данном случае, это могла бы быть таблица Entity.dbo.Companies (точно, угадали, все sql-скрипты, что могут быть приведены в данной статье, будут на T-SQL).

Код: (C++)
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 (схема таблиц метаданных)

Посмотрим на содержимое некоторых таблиц:


рис.2 (table Entities)


рис.3 (table EntitySQLCommands)

А теперь глянем на реализацию метода, например, Edit:

Код: (C++)
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 может выглядеть, например, так:

Код: (C++)
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, если текущий пользователь имеет право на редактирование данной сущности.


Думаем


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

Код: (C++)
public interface IDBControl
{
        IDBEntity Entity
        { get; set; }
}

Посмотрим на реализацию контрола, некий вариант ComboBox, точнее, посмотрим только на реализацию того, что предопределено в интерфейсе:

Код: (C++)
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.
Глянем на класс:

Код: (C++)
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.
Собственно, это всё, читайте, думайте, спрашивайте, предлагайте.


Автор: Александр Меркульев
Версия для печати
Обсудить на форуме