DataConnection

Необходимо подключение библиотеки DataConnection.dll.

Шаблон кастомного DataConnection

<DataConnection Name="CustomGetDataConnection" Type="CustomGetDataConnection" Assembly="Template">
  <!-- Элементы DataConnection -->
</DataConnection>

В атрибуте Type указывается имя класса, описывающего кастомный DataConnection, а в качестве значения атрибута Assembly указывается имя сборки, в которой реализован этот класс.

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

  • CustomGetDataConnectionXmlSettings - класс загрузки данных из xml-файла;

  • CustomGetDataConnection - класс с логикой формирования результирующей таблицы.

Парсинг xml

Полный код шаблона:

using System.Xml;

namespace WorkflowForms.DataConnections
{
    public class CustomGetDataConnectionXmlSettings : GetDataConnectionXmlSettings
    {
        /*
         * Набор свойств для хранения данных
         * public string PropertyName { get; private set; }
         */
         
        public CustomGetDataConnectionXmlSettings(IDependable parent, IWorkflowForm form, XmlNode node, XmlSqlQueryProvider queryProvider)
            : base(parent, form, node)
        {
            /*
             * Здесь должен быть парсинг xml-кода,
             * чтобы получить значения тэгов и их атрибутов.
             */
        }
    }
}

Конструктор класса принимает параметры:

  • parent IDependable - ссылка на экземпляр класса CustomCommand, для которого создавался объект типа CustomCommandSettings;

  • form IWorkflowForm - форма, на которой описана кастомная команда;

  • node XmlNode - узел, который соответствует тэгу <Command>, описанному в xml-файле формы.

  • dataBindingProvider IDataBindingProvider -

Исполняемый код

Полный код класса:

using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;

namespace WorkflowForms.DataConnections
{
    public class CustomGetDataConnection : GetDataConnection
    {
        private const string SET_PROPERTY = "SetProperty";
        private const string ANY_PROPERTY = "AnyProperty";

        private DataTable _table;

        private IWorkflowForm _form;

        protected List<Item> list = new List<Item>();

        // Менеджер строковых ресурсов для поддержки языков в интерфейсе
        // private Template.StringManager _manager;

        #region Custom Properties
        private string _anyProperty;
        private string AnyProperty
        {
            get
            {
                return _anyProperty;
            }
            set
            {
                _anyProperty = value;
                // Когда изменяется значение свойства объекта, необходимо сделать рассылку события всем подписчикам.
                OnPropertyChange(ANY_PROPERTY);
            }
        }
        #endregion

        public CustomGetDataConnection(IWorkflowForm form, System.Xml.XmlNode node)
            : base(form, node)
        {
        }

        #region Инициализация DC
        public override void InternalInit(System.Xml.XmlNode node, IWorkflowForm form)
        {
            base.InternalInit(node, form);
            _form = form;

            // _manager = new Template.StringManager(form);

            LoadSettings(node, form);

            // Создаем структуру результирующей таблицы
            CreateTable();
        }

        protected void LoadSettings(System.Xml.XmlNode node, IWorkflowForm form)
        {
            var settings = new CustomGetDataConnectionXmlSettings(this, form, node, new XmlSqlQueryProvider());

            /*
             * Получение значений и сохранение в переменные
             */
        }

        public override async Task PostInit()
        {
            await Refresh();
            await Task.CompletedTask;
        }
        #endregion

        #region Основная логика
        // Метод, через который элементы формы будут получать данные из DC
        public override DataTable GetDataTable()
        {
            return _table;
        }

        public override async Task Refresh(string queryName = null)
        {
            await RefreshDataAsync();
            FillTable();

            // Вызов метода для рассылки события подписчикам об изменении данных в DC
            OnChange();
        }

        private async Task RefreshDataAsync()
        {
            // Получение строкового ресурса по ключу
            // string str = _manager.GetString("TextId");
          

        }
        #endregion

        #region SetProperty
        public override void SetProperty(string propertyName, Dictionary<string, object> parameters)
        {
            if (propertyName.Equals(SET_PROPERTY, StringComparison.OrdinalIgnoreCase))
            {
                // Логика обработки
            }
            else
            {
                base.SetProperty(propertyName, parameters);
            }
        }
        #endregion

        #region GetProperty
        public override object GetProperty(string propertyName, Dictionary<string, object> parameters)
        {
            if (propertyName.Equals(ANY_PROPERTY, StringComparison.OrdinalIgnoreCase))
            {
                return AnyProperty;
            }
            else
            {
                return base.GetProperty(propertyName, parameters);
            }
        }
        #endregion

        #region Заполнение таблицы
        private void FillTable()
        {
            _table.Clear();

            list.ForEach(item =>
            {
                DataRow row = _table.NewRow();
                row["FirstColumn"] = item.FirstField;
                row["SecondColumn"] = item.SecondField;
                row["ThirdColumn"] = item.ThirdField;
                _table.Rows.Add(row);
            });
        }
        #endregion

        #region Создание таблицы
        private void CreateTable()
        {
            // Пример создания колонок результирующей таблицы
            _table = new DataTable("myTableName");
            _table.Columns.Add(CreateColumn("FirstColumn", typeof(int)));
            _table.Columns.Add(CreateColumn("SecondColumn", typeof(string)));
            _table.Columns.Add(CreateColumn("ThirdColumn", typeof(bool)));

        }

        private DataColumn CreateColumn(string columnName, Type dataType, object defaultValue = null)
        {
            var column = new DataColumn
            {
                ColumnName = columnName,
                DataType = dataType
            };

            if (defaultValue != null)
            {
                column.DefaultValue = defaultValue;
            }

            return column;
        }
        #endregion
    }

    #region Вложенные типы
    public class Item
    {
        public int FirstField { get; set; }

        public string SecondField { get; set; }

        public bool ThirdField { get; set; }
    }
    #endregion
}

Get/Set-проперти

Кастомный GetDataConnection может иметь свои get и set-проперти, реализация которых описывается в методах GetProperty и SetProperty соответственно.

Обращение к set-проперти происходит через команду ValueSetCommand:

<Command Name="ValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <DataConnection Name="CustomGetDataConnection">
    <Property Name="SetProperty">
      <Parameters>
        <Parameter Name="FirstParameter">value</Parameter>
      </Parameters>
    </Property>
  </DataConnection>
</Command>

В классе необходимо переопределить метод SetProperty:

private const string SET_PROPERTY = "SetProperty";

public override void SetProperty(string propertyName, Dictionary<string, object> parameters)
{
    if (propertyName.Equals(SET_PROPERTY, StringComparison.OrdinalIgnoreCase))
    {
        // Логика обработки
    }
    else
    {
        base.SetProperty(propertyName, parameters);
    }
}

В параметре parameters метода SetProperty хранится словарь всех параметров, описанных в команде ValueSetCommand в тэге <Parameters>.

Аналогичным образом отработает get-проперти:

<DataConnection SourceDataConnection="CustomGetDataConnection">
  <Property Name="AnyProperty">
    <Parameters>
      <Parameter Name="FirstParameter">value</Parameter>
    </Parameters>
  </Property>
</DataConnection>

В классе необходимо переопределить метод GetProperty:

private const string ANY_PROPERTY = "AnyProperty";

public override object GetProperty(string propertyName, Dictionary<string, object> parameters)
{
    if (propertyName.Equals(GET_PROPERTY, StringComparison.OrdinalIgnoreCase))
    {
        return AnyProperty;
    }
    else
    {
        return base.GetProperty(propertyName, parameters);
    }
}

В параметре parameters метода GetProperty хранится словарь всех параметров, описанных в тэге <Parameters> при обращении к get-проперти.

Элементы DataConnection

Условие

В примере рассмотрим xml-код соединения с данными с обязательным тэгом <AnyCondition> для передачи имени условия:

<DataConnection Name="CustomGetDataConnection" Type="CustomGetDataConnection" Assembly="Template">
  <AnyCondition Name="MyCondition" />
</DataConnection>

Парсинг xml

В конструкторе класса CustomGetDataConnectionXmlSettings необходимо прописать код вида:

// using WorkflowForms.Conditions;

// public ICondition AnyCondition { get; private set; }
 
string name = XmlParser.GetRequiredAttributeValue<string>(
    form, node, "AnyCondition", "Name");
    
AnyCondition = form.GetCondition(name);

Так как имя условия указывается в качестве значения атрибута обязательного тэга, то для его получения используется статический метод GetRequiredAttributeValue класса XmlParser. В третьем параметре (string path) указываем полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в четвертом параметре (string attribute). Если элемент или его атрибут отсутствует, будет возвращено исключение типа InvalidXmlException.

Если необходимо получить значение необязательного атрибута, то следует использовать метод GetAttributeValue. Этот метод в случае отсутствия элемента или его атрибута будет возвращать значение по умолчанию, переданное в параметрах метода.

Имея имя условия, объект типа ICondition можно получить через переменную form типа IWorkflowForm, используя метод GetCondition.

Исполняемая часть

В методе LoadSettings из объекта settings, переданного в метод в качестве параметра, можно получить объект типа ICondition, обратившись к публичному свойству AnyCondition:

// using WorkflowForms.Conditions;

// public ICondition _anyCondition;

_anyCondition = settings.AnyCondition;

В методе ExecuteAsyncCommand можно получать значение условия, обращаясь к свойству Value:

if (_anyCondition.Value)
{
    // ...
}

SQL-запросы

Если соединение с данными должно отправлять на сервер запрос на выполнение SqlQuery, то всегда должно указываться имя процесса (WorkflowName), в рамках которого описан SQL-запрос. Аналогичный тэг указывается в описании PrimaryGetDataConnection.

<DataConnection Name="CustomGetDataConnection" Type="CustomGetDataConnection" Assembly="Template">
  <Workflow Name="Template" />
  <AnySqlQuery Name="LanguageSelectSqlQuery" />
</DataConnection>

Парсинг xml

В конструкторе класса CustomGetDataConnectionXmlSettings необходимо прописать код вида:

// public string WorkflowName { get; private set; }
// public string AnySqlQueryName { get; private set; }

WorkflowName = XmlParser.GetRequiredAttributeValue<string>(
    form, node, "Workflow", "Name");
    
AnySqlQueryName = XmlParser.GetRequiredAttributeValue<string>(
    form, node, "AnySqlQuery", "Name");

Так как имя процесса и запрос являются обязательными для описания в команде, то для получения их значений используется статический метод GetRequiredAttributeValue класса XmlParser. В третьем параметре (string path) указываем полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в четвертом параметре (string attribute). Если элемент или его атрибут отсутствует, будет возвращено исключение типа InvalidXmlException.

Исполняемая часть

В методе LoadSettings из объекта settings, переданного в метод в качестве параметра, можно получить имя процесса и имя SQL-запроса, который нужно выполнять:

// public string _workflowName;
// public string _anySqlQueryName;

_workflowName = settings.WorkflowName;
_anySqlQueryName = settings.AnySqlQueryName;

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

/*
 * Формирование словаря параметров, значения которых будут
 * подставляться в текст запроса вместо переменных
 */
var parameters = new Dictionary<string, object>() {
    // ...
};

DataTable table = await _form.WorkflowEngineClient.ExecuteQuery(
    _workflowName, _anySqlQueryName, parameters
).ConfigureAwait(false);

// var arr = table.AsEnumerable().ToList().Select(row => row["title"]);

Результатом выполнения метода ExecuteQuery будет таблица типа DataTable, дальнейшая работа с которой может быть с помощью LINQ, либо путем перебора всех строк:

foreach (var row in table.Rows)
{
    // ...
}

Объекты формы

<DataConnection Name="CustomGetDataConnection" Type="CustomGetDataConnection" Assembly="Template">
  <AnyControl Name="MyVariable" />
</DataConnection>

Парсинг xml

В конструкторе класса CustomGetDataConnectionXmlSettings необходимо прописать код вида:

// using WorkflowForms.Controls;

// public IControl AnyConrol { get; private set; }

string name = XmlParser.GetRequiredAttributeValue<string>(
    form, node, "AnyControl", "Name");
    
AnyConrol = form.GetControl(name);

Так как имя объекта формы указывается в качестве значения атрибута обязательного тэга, то для его получения используется статический метод GetRequiredAttributeValue класса XmlParser. В третьем параметре (string path) указываем полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в четвертом параметре (string attribute). Если элемент или его атрибут отсутствует, будет возвращено исключение типа InvalidXmlException.

Если необходимо получить значение необязательного атрибута, то следует использовать метод GetAttributeValue. Этот метод в случае отсутствия элемента или его атрибута будет возвращать значение по умолчанию, переданное в параметрах метода.

Имея имя объекта формы, объект типа IControl можно получить через переменную form типа IWorkflowForm, используя метод GetControl.

Исполняемая часть

В методе LoadSettings из объекта settings, переданного в метод в качестве параметра, можно получить ссылку на объект формы:

// using WorkflowForms.Controls;

// private IControl _anyConrol;

_anyConrol = settings.AnyConrol;

Для получения значения из объекта формы необходимо обращаться к свойству Value:

var value = _anyConrol.Value;

DataConnection

<DataConnection Name="CustomGetDataConnection" Type="CustomGetDataConnection" Assembly="Template">
  <AnyDataConnection Name="MyPrimaryGetDataConnection" />
</DataConnection>

Парсинг xml

В конструкторе класса CustomGetDataConnectionXmlSettings необходимо прописать код вида:

// public IGetDataConnection AnyDataConnection { get; private set; }

string name = XmlParser.GetRequiredAttributeValue<string>(
    form, node, "AnyDataConnection", "Name");
    
AnyDataConnection = form.GetDataConnection(name);

Так как имя DataConnection указывается в качестве значения атрибута обязательного тэга, то для его получения используется статический метод GetRequiredAttributeValue класса XmlParser. В третьем параметре (string path) указываем полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в четвертом параметре (string attribute). Если элемент или его атрибут отсутствует, будет возвращено исключение типа InvalidXmlException.

Если необходимо получить значение необязательного атрибута, то следует использовать метод GetAttributeValue. Этот метод в случае отсутствия элемента или его атрибута будет возвращать значение по умолчанию, переданное в параметрах метода.

Имея имя соединения с данными, объект типа IGetDataConnection можно получить через переменную form типа IWorkflowForm, используя метод GetDataConnection.

Исполняемая часть

В методе LoadSettings из объекта settings, переданного в метод в качестве параметра, можно получить ссылку на DataConnection:

// using WorkflowForms.DataBindings;

// private IGetDataConnection _anyDataConnection;

_anyDataConnection = settings.AnyDataConnection;

Если кастомный DataConnection использует данные другого DataConnection, то необходимо установить зависимость первого от второго, чтобы гарантировать порядок загрузки при старте формы. Для этого в метод InternalInit класса CustomGetDataConnection необходимо добавить строки:

SetDependencies(new HashSet<IDependable>()
{
    new DataConnectionDataBinding(
        form, _anyDataConnection, new List<IDataBindingQuery>()),
});

Имея ссылку на DataConnection, можно получить его таблицу данных вызвав метод GetDataTable:

var dataTable = _anyDataConnection.GetDataTable();            

foreach (DataRow row in dataTable.Rows)
{
    // Convert.ToInt32(row["FirstField"]),
    // Convert.ToString(row["SecondField"]),
    // Convert.ToBoolean(row["ThirdField"])
}

Last updated