Применение tagged values в БД

   Применение стандартных методов проектирования баз данных не всегда удовлетворяет запросы программиста. Одно из ограничений – невозможность изменить набор атрибутов объекта после создания БД, что, при постоянно изменяемых требованиях заказчика, приводит к необходимости менять структуру БД (не потеряв при этом введенные данные), менять и заново отлаживать код программы, поля ввода/редактирования и т.д.
   Вашему вниманию предлагается один из способов решения этой проблемы — применение tagged values (TV , тэг-значений, или именованных значений).  
Сами значения хранятся в БД в строковом формате, но могут интерпретироваться в приложении по-разному, в зависимости от заданного типа значения. Аналог – всем известные ini-files! Основное преимущество применения TV – это возможность расширения списка значений в процессе исполнения приложения, без изменения структуры БД!
(image placeholder)

Список тэг-значений хранятся в таблице TaggedValue.

  • DefaultValue – это значение будет использоваться, если не задано действующее значение.
  • DefaultValueAlias – соответственно, значение для локализации.
  • ValueType – тип значения. Кроме стандартных типов, можно использовать свои, все зависит от фантазии разработчика. В зависимости от типа значения выбирается встроенный редактор значений. Например, для типа «OCL» используется редактор OCL-выражений. Для перечислимого типа производится выбор значений, которые хранятся в таблице TV_ValueSet. Возможен выбор из списка объектов БД – тогда надо задать ValuesListName.
  • Hint – подсказка при выборе/редактировании значения.
  • HintAlias – локализованная подсказка.
  • IsReadOnly – ну, это понятно.
  • ValuesListName – название класса объектов, из списка значений которых будет производиться выбор. Например «City», и тогда будет предложен выбор из списка ClassByExpressionName(‘City’).

   Список значений для набора объектов – в таблице TV_ValueSet. Например, для логического типа можно задать значения «True/False», а возможно и «Можно/Нельзя».
  Сами значения TV хранятся в таблице TV_Value. StringValue – действительное значение, значение, StringValueAlias – то, которое используется для локализации программы (или для показа юзеру ().

(image placeholder)

Для того, чтобы подключить к объекту тэг-значения, нужно создать ассоциацию типа n-n – OverridenTagedValues. Вся прелесть в том, что таким образом хранятся только переопределенные значения! Если нет значения – будет использоваться значение по-умолчанию! Классом такой ассоциации будет TV_Value, то есть, экземпляр класса – тэг-значение будет создан только при действительном вводе информации! Если грамотно подобрать умолчания, можно существенно сэкономить на размере БД!

Программная реализация.

Эта функция возвращает действующее тэг-значение по его имени. Если значение не было переопределено – возвращается значение по умолчанию.

function TMetaActorProperty.ActualValueByName(Tag: String): String;
var
  _TaggedValue:TTaggedValue;
  _TV_Value:TTV_Value;
begin
  _TaggedValue:=Self.TaggedValues.EvaluateExpressionAsDirectElement(‘self->select(name=’+QuotedStr(Tag)+’)->first’) as TTaggedValue;
  if Self.OverridenTaggedValues.Includes(_TaggedValue) then begin
    _TV_Value:=Self.TV_Value.BoldObjects[Self.OverridenTaggedValues.IndexOf(_TaggedValue)];
    Result:=_TV_Value.StringValue;
  end
  else
    Result:=Self.TaggedValues.EvaluateExpressionAsString(‘self->select(name=’+QuotedStr(Tag)+’)->first.defaultValue’, 1);
end;

Эта процедура устанавливает тэг-значение.

procedure TMetaActorProperty.SetTVByName(Tag: String; Value: String);
var
  _TaggedValue:TTaggedValue;
  _TV_Value:TTV_Value;
  Overriden: Boolean;
begin
  _TaggedValue:=LocateInList(Self.TaggedValues, ‘name’, Tag) as TTaggedValue;
  if not Assigned(_TaggedValue) then begin
     MsgError(Self.Name+’: Not found TV=’+Tag);
     Exit;
  end;  
  Overriden:=Self.OverridenTaggedValues.Includes(_TaggedValue);
  if Overriden then begin //Ранее было переопределено
    _TV_Value:=           //Найдем переопределенное значение
      Self.TV_Value.BoldObjects[Self.OverridenTaggedValues.IndexOf(_TaggedValue)];
    if (Value=_TaggedValue.DefaultValue) then //Если ввели значение по умолчанию — удалим переопределенное
       Self.OverridenTaggedValues.Remove(_TaggedValue)
    else  //Ранее было переопределено и изменили значение
      _TV_Value.StringValue:=Value;
  end
  else   //Ранее было по умолчанию
  begin
    Assert(Assigned(_TaggedValue), ‘SetTVByName’);
    Self.OverridenTaggedValues.Add(_TaggedValue);
    _TV_Value:=           //Найдем переопределенное значение
      Self.TV_Value.BoldObjects[Self.OverridenTaggedValues.IndexOf(_TaggedValue)];
    _TV_Value.StringValue:=Value;
  end;
end;

Функция дает список подключенных тэг-значений.

function TMetaActorProperty.TaggedValues: TTaggedValueList;  //Find TaggedValueList
var
  expr: string;
begin
  Result:=nil;
  if not Assigned(Self) then Exit;
  expr:=’TV_Section.allInstances->select(name=’+QuotedStr(Self.MetaClassName)+’)->first.taggedValues’;
  Result:=TBoldSystem.DefaultSystem.EvaluateExpressionAsDirectElement(expr)as TTaggedValueList;
end;

Такие тэг-значения удобны для сохранения в БД настроек программы, быстрого создания прототипов проекта для согласования с заказчиком и добавления/изменения атрибутов на «лету» (в дальнейшем, после обкатки приложения, легко все оформить в виде «настоящих» атрибутов).

Для  создания/редактирования тэг-значений применяется редактор на основе компонента TCommonInspector из пакета Greatis — http://www.greatis.com.