Мне нужно избегать попыток обновить нефизические поля в Delphi TClientDataset, подключенном к TSQLQuery.

Precis: Мой код пытается обновить нефизические поля в Delphi XE TClientDataset (подключенном к TSQLQuery с его набором свойств SQL), которые были созданы в результате выполнения команды Open во время выполнения.

У меня есть TClientDataset, подключенный к TDatasetProvider, подключенный к TSQLQuery, подключенный к TSQLConnection. Первые 3 из этих объектов инкапсулированы в пару классов в библиотеке, которую я использую во многих местах в нескольких проектах. Эти классы создают эти 3 объекта во время выполнения и устраняют значительное количество повторяющегося кода, необходимого, поскольку у меня много, много таких триплетов.

Обычно я загружаю TClientDataset из базы данных, указав некоторый SQL в свойстве SQL TSQLQuery и вызвав Open для TClientDataSet. Fields в TClientDataset создаются с помощью этого вызова Open, т.е. они не существуют до Open.

Я столкнулся с проблемой в ситуации, когда три поля, сгенерированные в TClientDataset, не являются физическими; то есть SQL выполняет вычисления для их генерации. К сожалению, в TClientDataset эти 3 поля не создаются иначе, чем физические поля; их FieldKind равно fkData (в идеале должно быть fkInternalCalc), свойство Calculated равно False (в идеале должно быть True), а их ProviderFlags включает pfInUpdate (чего в идеале не должно быть). Неудивительно, что когда приходит время делать ApplyUpdates на TClientDataset, возникает исключение...

Project XXX.exe raised exception class TDBXError with message
SQL State: 42S22, SQL Error Code: 207 Invalid column name 'Received'.
SQL State: 42S22, SQL Error Code: 207 Invalid column name 'Issued'.
SQL State: 42S22, SQL Error Code: 207 Invalid column name 'DisplayTime'.

Я могу избежать этой ошибки, сняв флаги pfInUpdate этих полей в обработчике событий OnUpdateData TDatasetProvider. Однако это решение требует, чтобы определенные имена полей были известны этой функции, которая находится в общих классах, упомянутых выше, что нарушает универсальность кода.

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

Я не могу изменить их свойства FieldKind или Calculated (на fkInternalCalc и True соответственно) после вызова Open, так как это генерирует сообщение об исключении WorkCDS: Cannot perform this operation on an open dataset. И я не могу изменить эти свойства до вызова Open, так как Fields еще не существует.

Я могу удалить флаг pfInUpdate из свойств ProviderFlags этих Field после Open, но это не будет передано "Дельте" TClientDatset, которая поступает в обработчик событий OnUpdateData. Я также попытался установить свойства поля FieldDefs.InternalCalcField; опять же, это не передается в набор данных Delta.

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

Все результаты поиска в Интернете, с которыми я столкнулся, включая превосходные статьи Кэри Дженсена, касаются настроек времени разработки или сгенерированных не SQL, которые не применимы к моей ситуации.


person Chris Bargh    schedule 22.11.2012    source источник
comment
Является ли ваш компонент производным от TClientDataSet или является композицией?   -  person jachguate    schedule 22.11.2012
comment
Надеюсь, я понял ваш вопрос. Два упомянутых мною класса сами по себе не являются компонентами, они содержат классы TClientDataSet, TDataSetProvider и TSQLQuery, ни один из которых не является производным, т.е. не подклассы. Из двух классов один является производным от другого, но базовый класс двух является производным только от TObject.   -  person Chris Bargh    schedule 22.11.2012
comment
Как вы открываете внутренний ClientDataSet? Я имею в виду, вызываете ли вы метод в своем классе или напрямую вызываете внутренний метод ClientDataSet.Open?   -  person jachguate    schedule 22.11.2012
comment
Метод Open вызывается непосредственно во внутреннем TClientDataSet. TClientDataSet возвращается как параметр var в конструкторе моего производного класса, поскольку он широко используется во внешнем по отношению к моему классу коде.   -  person Chris Bargh    schedule 22.11.2012
comment
Лучший подход, который приходит мне на ум, состоит в том, чтобы изменить это, чтобы вызвать метод Open (или любое другое имя) в вашем классе, который, в свою очередь, открывает внутренний ClientDataSet. Хорошо, что вам не нужно менять всю кодовую базу, а только те места, где вы хотите игнорировать некоторые поля из обновления в ApplyUpdates.   -  person jachguate    schedule 22.11.2012
comment
Готовы ли вы внести это изменение в свой код?   -  person jachguate    schedule 22.11.2012
comment
Меня не беспокоит изменение кода моей библиотеки, хотя, очевидно, я делаю это с некоторой осторожностью. Я ценю усилия, которые вы вложили в это. Однако исключение возникает не тогда, когда я Open TClientDataSet, а когда происходит ApplyUpdates. Я закодировал и успешно протестировал альтернативный подход, в котором я сохраняю указатель на предыдущий обработчик событий OnUpdateData (EH) и устанавливаю новый локальный EH (первой задачей которого является вызов предыдущего EH). Локальный OnUpdateData EH содержит определенный код, очищающий ProviderFlags для каждого нефизического поля в наборе данных Delta.   -  person Chris Bargh    schedule 22.11.2012


Ответы (2)


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

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

Во-первых, один простой механизм заключается в том, чтобы включить новое свойство TStringList, в котором перечислены все поля, которые вы хотите игнорировать, и которые вы будете сопоставлять по имени. Не стесняйтесь принять это или создать новый улучшенный механизм, важно то, что вы можете определить, какие поля вы хотите настроить таким образом.

type
  TMyClass = class
    // all your current class here
  private
    FUpdateIgnoredFields: TStringList;
  public
    property UpdateIgnoredFields: TStringList read FUpdateIgnoredFields write SetUpdateIgnoredFields;
    //don't forget to create this in your constructor, free it in the destructor
    //and Assign any new value in the SetUpdateIgnoreFields method, as usual.
    procedure OpenInnerCDS; //the magic goes here
  end;

procedure TMyClass.OpenInnerCDS;
var
  FieldName: string;
  AFieldToIgnore: TField;
begin
  //opens the inner cds, but before that, configures the update-ignored  
  //fields in the underlying dataset
  //Let's call it InnerBaseDataSet;
  FInnerBaseDataSet.Open; //this opens the DataSet and creates all the fields for it.
  try
    for FieldName in FUpdateIgnoredFields do
    begin
      AFieldToIgnore := FInnerBaseDataSet.FindField(FieldName);
      if Assigned(AFieldToIgnore) then
        AFieldToIgnore.ProviderFlags := AFieldToIgnore.ProviderFlags - [pfInUpdate, pfInWhere];
    end;
    //now, let's open the ClientDataSet;
    FInnerClientDataSet.Open;
  finally
    //I suggest no matter what happens, always close the inner data set
    //but it depends on how the CDS->Provider->DataSet interaction is configured
    FInnerBaseDataSet.Close;
  end;
end;

//the way you use this is to replace the current ClientDataSetOpen with something like:

var
  MyInsance: TMyClass;
begin
  MyInstance := TMyInstance.Create();  //params
  try
    //configuration code here
    //MyInstance.InnerCDS.Open;  <-- not directly now
    MyInstance.UpdateIgnoreFields.Add('CALCULATED_SALARY');
    MyInstance.OpenInnerCDS;
    //use the CDS here.
    MyInstance.InnerCDS.ApplyUpdates(-1); //safely apply updates now.
  finally
    MyInstance.Free;
  end;
end;

Примите это как идею.

Я написал весь код здесь, возможно, синтаксис неправильный, но он показывает всю идею.

person jachguate    schedule 22.11.2012
comment
Принцип, лежащий в основе этой идеи, правильный и отвечает моим потребностям в сигнализации. Однако, как я упоминал в своем предыдущем комментарии, исключение возникает во время ApplyUpdates, а не при вызове Open. Чтобы использовать приведенное выше решение, я бы использовал ваше свойство UpdateIgnoredFields в обработчике событий OnUpdateData TDataSetProvider, потому что Open работает со всем набором данных, а OnUpdateData работает с набором данных Delta. Большое спасибо за вашу помощь. Извините, я не могу проголосовать за вас, моя репутация недостаточна :-) - person Chris Bargh; 22.11.2012
comment
@Chris, показанный подход просто предотвращает исключение во время ApplyUpdates. Сняв флаг pfInUpdate. Теперь, когда я немного подумаю об этом, вы также должны удалить флаг pfInWhere. Просто попробуйте, и вы увидите, как это работает. - person jachguate; 22.11.2012
comment
Вы правы в том, что необходимо также удалить флаг pfInWhere. Однако сбросить ProviderFlags в основном ClientDataSet не получится — я безуспешно пытался это сделать в начале своего тестирования — это работает только с набором данных Delta. Причина в том, что хотя вы можете сбросить флаги в основном наборе данных, это не будет передано набору данных Delta, для которого все еще установлены флаги. Я попытался проследить через VCL, но не смог найти дисконнект, но он определенно не работает. Следовательно, необходимо сделать это в обработчике события OnUpdateData, который имеет дело только с дельта-набором данных. - person Chris Bargh; 22.11.2012
comment
@Chris: я делаю это до того, как поля появятся в ClientDataSet, поэтому изменения должны распространяться, когда ClientDataSet открывается через пару строк кода. Если вы не создадите это во время разработки, оно будет работать. - person jachguate; 22.11.2012
comment
Невозможно ничего сделать с полями до открытия ClientDataSet, потому что сам акт открытия вызывает выполнение SQL в TSQLQuery, и именно из этого все поля в ClientDataSet создан; в этой схеме поля просто не существуют до открытия набора данных. - person Chris Bargh; 22.11.2012
comment
Использование TSQLQuery для динамического определения полей в ClientDataSet — очень мощная функция, но это означает, что поля не существуют до тех пор, пока набор данных не будет открыт; это было причиной моей первоначальной проблемы. - person Chris Bargh; 22.11.2012
comment
извините, похоже, мы не говорим здесь на одном языке. Я не знаю, как заставить вас посмотреть или попробовать то, что уже есть в моем ответе. - person jachguate; 22.11.2012
comment
Я прочитал ваш код. Понятно (и хорошо написано). Однако два упомянутых вами набора данных не отражают мою ситуацию. В моей настройке есть основной ClientDataSet, который существует постоянно, и набор данных Delta, который автоматически создается DataSetProvider как часть вызова ApplyUpdates. Набор данных Delta — это динамический набор данных, который не существует вне вызова ApplyUpdates и, конечно же, не существует в момент открытия основного ClientDataSet. Два набора данных не взаимодействуют. Как я объяснил, ранее я пробовал ваш подход к очистке ProviderFlags и обнаружил, что он не работает. - person Chris Bargh; 22.11.2012
comment
Вы сказали в своем вопросе: у меня есть TClientDataset, подключенный к TDatasetProvider, подключенному к TSQLQuery, подключенному к TSQLConnection. Я называю этот TSQLQuery FInnerBaseDataSet и изменяю его поля перед открытием TClientDataSet. Это не соответствует вашей ситуации? - person jachguate; 22.11.2012
comment
Ах. Я просто не понял, что ваш FInnerBaseDataSet был моим TSQLQuery, вернее, я подумал, что это лишний TClientDataset (назначение которого мне было непонятно!). Итак, я реализовал ваше решение, так как оно лучше моего; мое решение привело к более децентрализованному коду и более сложному использованию. Единственное изменение, которое я сделал, — передать поля в виде открытого массива, чтобы я мог сделать что-то вроде WorkQuery.SetFieldsExcludedFromUpdate(['DisplayTime', 'Issued', 'Received']), за которым следует WorkQuery.Open, где WorkQuery — это мой класс, инкапсулирующий цепочку классов VCL. Еще раз спасибо. - person Chris Bargh; 23.11.2012
comment
Приятно знать, что это было хорошо для вас, рад, что мне наконец удалось объяснить, что я имел в виду. :) - person jachguate; 23.11.2012

вы можете передавать ProviderFlags (а также несколько других свойств) от клиента к поставщику (дельта), установив соответствующие необязательные параметры на CDS. не забудьте установить параметр IncludeInDelta

person vavan    schedule 22.11.2012