Руководство по Catharsis - часть 2: пример Catharsis

ОГЛАВЛЕНИЕ

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

Пример проекта Catharsis 

Catharsis - это мощный инструмент быстрой разработки приложений (RAD - Rapid Application Development), который использует набор лучших шаблонов разработки, составленных вокруг конкретной архитектуры используя MVC.

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

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

Введение 

Пример решения содержит такие сущности, как Agent (Агент) и AgentContract (КонтрактАгента). В данной части мы рассмотрим то, как данные сущности интегрированы в структуру Catharsis.

Давайте сначала исследуем сущности Insurance Agent (Страховой агент). Начнем мы с изучения таблицы базы данных для того, чтобы найти информацию, хранимую для страхового агента и затем исследуем слой данных и увидим то, как таблицы встраиваются в приложение. Здесь мы видим преобразование файла NHibernate и объекты доступа к данным (интерфейс DAO - Data Access Object) в решении.

А вот скрипт создания таблиц баз данных:

CREATE TABLE [dbo].[InsuranceAgent](
    [InsuranceAgentId] [int] IDENTITY(1,1) NOT NULL,
    [Code] [nvarchar](150) NOT NULL,
    [IsInternal] [bit] NOT NULL,
    [Rating] [smallint] NOT NULL,
    [CommissionRate] [decimal](18, 10) NOT NULL,
    [CommissionPeak] [decimal](18, 2) NOT NULL,
    [CurrencyId] [smallint] NOT NULL,
    [CountryId] [smallint] NOT NULL,
    [GenderId] [tinyint] NOT NULL,
    [Started] [datetime] NOT NULL,
    [Finished] [datetime] NULL,
 CONSTRAINT [PK_InsuranceAgent] PRIMARY KEY CLUSTERED
(
    [InsuranceAgentId] ASC
)

А это преобразование файла HNibernate для InsuranceAgent:

<?xml version='1.0' encoding='utf-8' ?>
<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'
    namespace='Firm.SmartControls.Entity.Insurance' assembly='Firm.SmartControls.Entity'>
  <class name='Agent' table='InsuranceAgent' lazy='true' >
      <id name='ID' column='InsuranceAgentId' >
        <generator class='native'></generator>
      </id>
      <property not-null='true' name='Code' />
      <property not-null='true' name='IsInternal' />
      <property not-null='true' name='Rating' />
      <property not-null='true' name='CommissionRate' />
      <property not-null='true' name='CommissionPeak' />
      <property not-null='true' name='Started' />
      <property not-null='false' name='Finished' />

      <many-to-one name='Currency' column='CurrencyId' lazy='false' ></many-to-one>
      <many-to-one name='Country' column='CountryId' lazy='false' ></many-to-one>
      <many-to-one name='Gender' column='GenderId' lazy='false' ></many-to-one>

      <bag name='Contracts' inverse='true' lazy='true'
               cascade='all-delete-orphan' >
          <key column='InsuranceAgentId' />
          <one-to-many class='AgentContract' />
      </bag>
  </class>
</hibernate-mapping>
  

Заметьте, что lazy="true" означает, что экземпляр объекта будет создан тогда, когда он необходим.

Первые семь параметров, указанных в таблице преобразования, являются простым построением соответствий. Currency, Country и Gender являются соответствиями типа один-ко-многим. Это скорее ‘типы сущностей’, чем простые ‘типы значений’.

Они обрабатываются в виде CodeLists в решении Catharsis.


 

Теперь мы рассмотрим объект доступа к данным (Dao) - это тот объект, которые читает информацию из базы данных посредством NHibernate. Он используется в качестве части функциональности поиска для возврата всех кортежей базы данных, которые удовлетворяют определенный набор критериев. Он также обрабатывает кляузы сортировки (Order-By).

А вот часть кода из файла:

public override IList<Agent> GetBySearch(ISearch searchObject)
{
Check.Require(searchObject.Is(), “Use only AgentSearch not-null instance for searching Agent entities”);
  var search = searchObject as AgentSearch;
  if (search.Is())
  {
    Criteria.CreateEqual(Str.Common.ID, search.IDSearch);//Поиск идентификатора ID         
    Criteria.CreateLike(Str.Common.Code, search.Example.Code);
    Criteria.CreateEqual(Str.Business.Agent.IsInternal,    search.IsInternalSearch);
    Criteria.CreateGe(Str.Business.Agent.Rating, search.RatingFrom);
    Criteria.CreateGe(Str.Business.Agent.CommissionRate, search.CommissionRateFrom);
    Criteria.CreateGe(Str.Business.Agent.CommissionPeak, search.CommissionPeakFrom);
    Criteria.CreateGeDate(Str.Business.Agent.Started, search.StartedFrom);
    Criteria.CreateGeDate(Str.Business.Agent.Finished, search.FinishedFrom);
    Criteria.CreateLe(Str.Business.Agent.Rating, search.RatingTo);
    Criteria.CreateLe(Str.Business.Agent.CommissionRate, search.CommissionRateTo);
    Criteria.CreateLe(Str.Business.Agent.CommissionPeak, search.CommissionPeakTo);
    Criteria.CreateLeDate(Str.Business.Agent.Started, search.StartedTo);
    Criteria.CreateLeDate(Str.Business.Agent.Finished, search.FinishedTo);
    Criteria.CreateEqual(Str.Controllers.Currency, search.Example.Currency);
    Criteria.CreateEqual(Str.Controllers.Country, search.Example.Country);
  }
  return GetListByCriteria(searchObject);
}

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

А вот блок, демонстрирующий класс Agent:

public partial class Str
    {
        public partial class Business
        {
            #region Agent
            public class Agent
            {
                protected Agent() { }
                public const string IsInternal = "IsInternal";
                public const string Rating = "Rating";

Метод GetBySearch в DAO вызывается используя SearchObject , как это можно заметить в подписи метода: 

public override IList<Agent> GetBySearch(ISearch searchObject)
{
Check.Require(searchObject.Is(), “Use only AgentSearch not-null instance for searching Agent entities”);
  var search = searchObject as AgentSearch;


 

Теперь мы можем изучить объект поиска:

А вот как устанавливается объект поиска:

public class AgentSearch : BaseSearch<Agent>, ISearch<Agent>
    {
        #region properties
        /// <summary>
        /// Важно предоставить название Отличное от названия примера:
        /// IsInternal -- IsInternalSearch!
        /// Метод Bind привязывает оба: Search object + Example!
        /// Является ли пример IsInternal типом bool или нет? - ведь это может навредит,
        /// если null будет послано в IsInternal (которая должна быть IsInternalSearch)
        /// </summary>
        public virtual bool? IsInternalSearch { get; set; }

        public virtual DateTime? StartedFrom { get; set; }
        public virtual DateTime? StartedTo { get; set; }
        public virtual DateTime? FinishedFrom { get; set; }
        public virtual DateTime? FinishedTo { get; set; }

        public virtual short? RatingFrom { get; set; }
        public virtual short? RatingTo { get; set; }

        public virtual decimal? CommissionRateFrom { get; set; }
        public virtual decimal? CommissionRateTo { get; set; }

        public virtual decimal? CommissionPeakFrom { get; set; }
        public virtual decimal? CommissionPeakTo { get; set; }    
        #endregion properties
    }

Важно отметить, что каждый элемент в поиске может быть nullable, как указано вопросительным знаком после типа данных. Это означает, что параметры поиска опциональны и предоставление пустого значения (null) для любого обнуляемого (nullable) параметра поиска может быть принято.


 

Давайте изучим как экран List в приложении ищет и отображает агентов (Agents) в базе данных:

Файл aspx , который содержит указанную выше страницу, найден в веб-проекте:

Установка таблицы для отображения данных выполняется структурой. Структура автоматически создаст список. Добавление дополнительных колонок к списку может быть выполнено в методе OnBeforeList()из Controller искомой сущности.

А вот конструктор в фоновом коде List.aspx.cs:

 namespace Firm.SmartControls.Web.Views.Agent
{
    public partial class List : ViewPageBase<IAgentModel>
    {
    }
}
  

Мы можем заметить, что IAgentModel используется для создания списка.

Конструктор данного интерфейса показан далее:

 [ConcreteType("Firm.SmartControls.Models.Insurance.AgentModel, Firm.SmartControls.Models")]
    public interface IAgentModel : IBaseEntityModel<Agent>
    {
        /// <summary>
        /// Параметры поиска для фильтрации списка агентов = 'Agent'
        /// </summary>
        new AgentSearch SearchParam { get; set; }
    }
  

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

Давайте изучим сущность, используемую интерфейсом - сущность Agent.

Это также важный файл, поэтому мы рассмотрим каждую его часть:


Члены:

#region members
IList<AgentContract> _contracts;
  // Нет членов для стандартного значения - это выполняется посредством шаблона PROTOTYPE по Facade.CreateNew()
#endregion members

Каждый агент (Agent) может иметь множество контрактов (Contracts) и это будет обсуждено позже.

Некоторые свойства кода продемонстрированы ниже: 

#region properties
/// Уникальный код
public virtual string Code { get; set; }
/// Переключатель IsInternal
public virtual bool IsInternal { get; set; }
/// небольшое значение int в качестве рейтинга - Rating (для демонстрации возможностей формата)
public virtual short Rating { get; set; }
/// десятичные значения для подсчета комиссионных (для демонстрации возможностей формата)
public virtual decimal CommissionRate { get; set; }
/// десятичные значения для сохранения информации о наибольшей комиссии (для демонстрации возможностей формата)
public virtual decimal CommissionPeak { get; set; }
/// codelist
public virtual Currency Currency { get; set; } 

Методы получения и установки значений (Get\Set) существуют для каждого свойства. Заметьте, что тип данных для условных единиц (Currency) является Currency потому, что для этого используется другая сущность.

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

#region contract list
/// Список контрактов (загруженные файлы)
public virtual IList<AgentContract> Contracts
{
     get
     {
          if (_contracts.IsNull())
          {
             _contracts = new List<AgentContract>();}
          return _contracts;
      }
      protected set { _contracts = value; }
}
#endregion contract list        

Каждая сущность в Catharsis имеет некоторые перегруженные абстрактные методы, которые могут помочь в определении сущности. Свойство, возвращаемое в данные методы (Code в указанном ниже примере), может быть установлено при добавлении сущности используя Guidence в Catharsis.

#region override abstract
/// Предоставляет быстрое строковое описание  об экземпляре 'Agent'
public override string ToDisplay()
{
    // Метод зачастую используется по отношению к View
    // тем самым предоставляя четкий и понятный
    // набор свойств (разделенный пробелами ' '),
    // что быстро предоставит информацию о данном экземпляре
     return Code;
}
/// Возвращаемое значение должно быть обработано в качестве уникального бизнес-ключа.
/// Метод Equal (реализованный в базовом классе Tracked) будет использовать его  
/// для сравнения двух агентов (Agents)
protected override string GetDomainObjectSignature()
{
    // Также стоит добавить возврат свойства (или список свойств, разделенных "|")
    // что позволит различать сущности
   return Code;
}
#endregion override abstract
  

Конкретный класс AgentModel выглядит следующим образом.

/// <summary>
/// Все данные и свойства привязки для представлений 'Agent'
/// </summary>
public class AgentModel : BaseEntityModel<Agent>, IAgentModel
{
    /// <summary>
    /// Устанавливает ShowExportAction == true (позволяет экспортировать в Excel )
    /// </summary>
    public AgentModel()
       : base()
    {
        ExportModel.ShowExportAction = true;
    }
    /// <summary>
    /// Поиск объекта, содержащего пример данной сущности
    /// и он может быть расширен более мощными свойствами для поиска
    /// </summary>
    public new AgentSearch SearchParam
    {
       get { return base.SearchParam as AgentSearch; }
       set { base.SearchParam = value; }
     }
 }

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

Дополнительная информация  

Исходный код для Catharsis вы можете загрузить по данной ссылке:
http://www.codeplex.com/Catharsis

Данный документ исследует пример решения Firm.SmartControls для структуры Catharsis.