Обновление вложенного набора данных с помощью poFetchDetailsOnDemand

Есть ли способ обновить только подробный набор данных без перезагрузки всего основного набора данных?

это то, что я пробовал до сих пор:

DM.ClientDataSet2.Refresh;      
DM.ClientDataSet2.RefreshRecord;

Я также пробовал:

DM.ClientDataSet1.Refresh;

Но описанный выше метод обновляет весь основной набор данных, а не только текущую запись.

Теперь следующий код, похоже, ничего не делает:

DM.ClientDataSet1.RefreshRecord;

Есть ли обходной путь или правильный способ сделать то, что я хочу? (может быть интерпозер...)

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

ClientDataSet1 = основной набор данных

ClientDataSet2 = Detail DataSet , имеет следующий вид: *

object ClientDataSet2: TClientDataSet
    Aggregates = <>
    DataSetField = ClientDataSet1ADOQuery2
    FetchOnDemand = False
    .....
end

Свойства провайдера:

object DataSetProvider1: TDataSetProvider
    DataSet = ADOQuery1
    Options = [poFetchDetailsOnDemand]
    UpdateMode = upWhereKeyOnly
    Left = 24
    Top = 104
  end

person EProgrammerNotFound    schedule 14.10.2013    source источник
comment
Насколько мне известно, во вложенных клиентских наборах данных вы не можете использовать Refresh для подробного набора данных. Прочтите эту статью, написанную Кэри Дженсен, и найдите слово обновления.   -  person Guillem Vicens    schedule 15.10.2013
comment
В связанной статье автор сказал, что я не могу обновить набор данных без поставщика набора данных. Ok. Итак, единственный ли способ обновить весь основной набор данных?   -  person EProgrammerNotFound    schedule 15.10.2013
comment
Не уверен на 100%, но так кажется. В любом случае, какая у вас проблема с обновлением основного набора данных?   -  person Guillem Vicens    schedule 18.10.2013
comment
Это слишком много данных для перезагрузки. Фактический основной набор данных содержит около 150 миллионов записей, а его детальный набор — всего 150–500 записей.   -  person EProgrammerNotFound    schedule 19.10.2013
comment
Я перечитал ваш вопрос и обнаружил, что ошибка closed dataset довольно странная. Я нашел эту ссылку, где говорится, что вы должны иметь возможность использовать RefreshRecord в наборе nested clientDataset. Я предполагаю, что что-то неправильно настроено. Пожалуйста, проверьте предыдущую ссылку, а также эту.   -  person Guillem Vicens    schedule 19.10.2013
comment
Пробовали ли вы извлекать подробные записи в другой клиентский набор данных, а затем назначать его данные в ClientDataSet2?   -  person MartynA    schedule 21.10.2013
comment
@GuillemVicens Я исправил исключение Closed Dataset, но все равно не работает   -  person EProgrammerNotFound    schedule 24.10.2013
comment
@MartynA Не очень хорошая идея, так как мне придется делать это с каждым набором данных M/D, который у меня есть.   -  person EProgrammerNotFound    schedule 24.10.2013
comment
Если вы исправили код, вам следует обновить свой вопрос, удалив упоминание об ошибке и объяснив, почему он не работает. Во всяком случае, я не думаю, что это возможно. Я пытаюсь сделать то же самое в Delphi XE. Никаких успехов до сих пор. :(   -  person EMBarbosa    schedule 07.11.2013


Ответы (1)


Поиск в Google находит множество статей, в которых говорится, что это вообще невозможно с вложенными ClientDataSets без закрытия и повторного открытия главной CDS, чего OP не хочет делать в этом случае. Однако ...

Короткий ответ на вопрос: да, в довольно простом случае, который я тестировал, и он довольно прост, хотя и немного многословен; Чтобы понять, как правильно сделать необходимые шаги, потребовалось некоторое время.

Код приведен ниже и включает комментарии, объясняющие, как он работает, и несколько потенциальных проблем, а также то, как их можно избежать или обойти. Я протестировал его только с TAdoQueries, питающим поставщика CDS.

Когда я начал разбираться во всем этом, вскоре стало очевидно, что при обычной настройке master+detail, хотя Providers+CDS с удовольствием обновляют мастер-данные с сервера, они просто не будут обновлять detail записи после того, как они были прочитаны с сервера в первый раз с момента открытия cdsMaster. Это может быть по дизайну, конечно.

Я не думаю, что мне нужно публиковать DFM, чтобы идти с кодом. Я просто настроил AdoQueries обычным способом master-detail (с подробным запросом, имеющим PK мастера в качестве параметра), DataSetProvider указывал на главный AdoQuery, главный CDS указывал на провайдера, а подробный cDS указывал на DataSetField файла cdsMaster. Чтобы поэкспериментировать и посмотреть, что происходит, для каждого из этих наборов данных есть DBGrids и DBNavigator.

Вкратце, приведенный ниже код работает так, чтобы временно отфильтровать мастер AdoQuery и мастер CDS до текущей строки, а затем принудительно обновить их данные и данные dtail для текущей основной строки. Выполнение этого способа, в отличие от любого другого, который я пробовал, приводит к обновлению строк сведений, вложенных в поле DataSet cdsMaster.

Кстати, другие тупики, которые я пробовал, включали и не включали poFetchDetailsOnDemand в значение true, то же самое cdsMaster.FetchDetailsOnDemand. Очевидно, что «FetchDetailsOnDemand» не означает ReFetchDetailsOnDemand!

Я столкнулся с одной или двумя проблемами, когда мое «решение» заработало, самая липкая из них описана в этом вопросе SO: -a-datasetfield/24722787#24722787">Обновление ClientDataSet, вложенного в DataSetField

Я проверил, что это правильно работает с серверной частью Sql Server 2000 (!) , включая получение изменений данных строки, запущенных на сервере из ISqlW. Я также проверил, используя Sql Server Profiler, что сетевой трафик при обновлении включает только одну главную строку и ее детали.

Delphi 7 + Win7 64-бит, кстати.

procedure TForm1.cdsMasterRowRefresh(MasterPK : Integer);
begin
  //  The following operations will cause the cursor on the cdsMaster to scroll
  //  so we need to check and set a flag to avoid re-entrancy
  if DoingRefresh then Exit;
  DoingRefresh := True;

  try
    //  Filter the cdsMaster down to the single row which is to be refreshed.
    cdsMaster.Filter := MasterPKName + ' = ' + IntToStr(MasterPK);
    cdsMaster.Filtered := True;
    cdsMaster.Refresh;
    Inc(cdsMasterRefreshes);  //  just a counter to assist debugging

    //  release the filter
    cdsMaster.Filtered := False;

    // clearing the filter may cause the cdsMaster cursor to move, so ...
    cdsMaster.Locate(MasterPKName, MasterPK, []);
  finally
    DoingRefresh := False;
  end;
end;

procedure TForm1.qMasterRowRefresh(MasterPK : Integer);
begin
  try
    //  First, filter the AdoQuery master down to the cdsMaster current row
    qMaster.Filter := MasterPKName + ' = ' + IntToStr(MasterPK);
    qMaster.Filtered := True;

    //  At this point Ado is happy to refresh only the current master row from the server
    qMaster.Refresh;

    // NOTE:
    //  The reason for the following operations on the qDetail AdoQuery is that I noticed
    //  during testing situations where this dataset would not be up-to-date at this point
    //  in the refreshing operations, so we update it manually.  The reason I do it manually
    //  is that simply calling qDetail's Refresh provoked the Ado "Insufficient key column
    //  information for updating or refreshing" despite its query not involving a join
    //  and the underlying table having a PK

    qDetail.Parameters.ParamByName(MasterPKName).Value := MasterPK;
    qDetail.Close;
    qDetail.Open;

    //  With the master and detail rows now re-read from the server, we can update
    //  the cdsMaster
    cdsMasterRowRefresh(MasterPK);
  finally
    //  Now, we can clear the filter
    qMaster.Filtered := False;
    qMaster.Locate(MasterPKName, MasterPK, []);
    // Obviously, if qMaster were filtered in the first place, we'd need to reinstate that later on
  end;
end;

procedure TForm1.RefreshcdsMasterAndDetails;
var
  MasterPK : Integer;
begin
  if cdsMaster.ChangeCount > 0 then
    raise Exception.Create(Format('cdsMaster has %d change(s) pending.', [cdsMaster.ChangeCount]));
  MasterPK := cdsMaster.FieldByName(MasterPKName).AsInteger;

  cdsDetail.DisableControls;
  cdsMaster.DisableControls;
  qDetail.DisableControls;
  qMaster.DisableControls;

  try
    try
      qMasterRowRefresh(MasterPK);
    except
      //  Add exception handling here according to taste
      //  I haven't encountered any during debugging/testing so:
      raise;
    end;
  finally
    qMaster.EnableControls;
    qDetail.EnableControls;
    cdsMaster.EnableControls;
    cdsDetail.EnableControls;
  end;
end;

procedure TForm1.cdsMasterAfterScroll(DataSet: TDataSet);
begin
  RefreshcdsMasterAndDetails;
end;

procedure TForm1.cdsMasterAfterPost(DataSet: TDataSet);
//  NOTE:  The reason that this, in addition to cdsMasterAfterScroll, calls RefreshcdsMasterAndDetails is
//         because RefreshcdsMasterAndDetails only refreshes the master + detail AdoQueries for the current
//         cdsMaster row.  Therefore in the case where the current cdsMaster row or its detail(s)
//         have been updated, this row needs the refresh treatment before we leave it.
begin
  cdsMaster.ApplyUpdates(-1);
  RefreshcdsMasterAndDetails;
end;

procedure TForm1.btnRefreshClick(Sender: TObject);
begin
  RefreshcdsMasterAndDetails;
end;

procedure TForm1.cdsDetailAfterPost(DataSet: TDataSet);
begin
  cdsMaster.ApplyUpdates(-1);
end;
person MartynA    schedule 13.07.2014
comment
Хорошо, я протестирую и скоро вернусь с дополнительными комментариями по этому поводу. - person EProgrammerNotFound; 14.07.2014