FDManager.DeleteConnectionDef не удаляет определение подключения

В моем приложении есть время разработки TFDConnection, которое повторно используется при подключении к другой базе данных (тип).
Я также получаю объединенное соединение из его настроек и регистрирую его с помощью FDManager.AddConnectionDef для использования при многопоточности (как здесь).

При настройке во второй раз я случайно снова позвонил AddConnectionDef с тем же ConnectionDefName. В документации говорится:

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

Этого не происходит. Никаких исключений не возникает, я просто получаю два ConnectionDef с одинаковыми именами.
Для любопытных: следующий блок кода демонстрирует такое поведение (RSP-19107 на портале качества). Это не моя непосредственная проблема, потому что я подумал Ну, тогда я использую DeleteConnectionDef, чтобы сначала удалить старый.
Но оказывается, что это не работает либо. См. второй блок кода.

procedure TFrmFireDACConnectionNames.BtnBug1Click(Sender: TObject);
var
   lParams: TStringList;
   i,l    : integer;
begin
   lParams := TStringList.Create;
   lParams.Add('User_Name=sysdba');
   lParams.Add('Password=masterkey');
   lParams.Add('database=D:\Testing\test.gdb');
   lParams.Add('Server=localhost');
   lParams.Add('Pooled=true');
   lParams.Add('DriverID=FB');
   FDManager.AddConnectionDef('FBPooled','FB',lParams);
   lParams.Values['database'] := 'D:\Testing\test2.gdb';
   FDManager.AddConnectionDef('FBPooled','FB',lParams);

   // This shows the two identical ConnectionDefs (inspect lParams):
   lParams.Clear;
   l := FDManager.ConnectionDefs.Count;
   for i := 0 to l-1 do
      lParams.Add(FDManager.ConnectionDefs[i].Name);
   // Contents on my machine:
   // Access_Demo
   // Access_Demo_Pooled
   // DBDEMOS
   // EMPOYEE
   // MSSQL_Demo
   // RBDemos
   // SQLite_Demo
   // SQLite_Demo_Pooled
   // FBPooled           <== Duplicates
   // FBPooled

   // To check that the two added have their respective Params, inspect lParams with breakpoints on the lines below:
   lParams.Assign(FDManager.ConnectionDefs[l-1].Params);
   // Contents on my machine:
   // User_Name=sysdba
   // Password=masterkey
   // database=D:\Testing\test2.gdb
   // Server=localhost
   // Pooled=true
   // DriverID=FB
   // Name=FBPooled

   lParams.Assign(FDManager.ConnectionDefs[l-2].Params);
   // Contents on my machine:
   // User_Name=sysdba
   // Password=masterkey
   // database=D:\Testing\test.gdb
   // Server=localhost
   // Pooled=true
   // DriverID=FB
   // Name=FBPooled

   lParams.Free;
end;

Ниже приведен пример кода, демонстрирующий сбой DeleteConnectionDef. Обратите внимание, что я даже не использую и не открываю файл TFDConnection.

procedure TFrmFireDACConnectionNames.BtnDeleteTestClick(Sender: TObject);
var
   lParams  : TStringList;
   i,l      : integer;
   lConnName: String;
begin
   lParams := TStringList.Create;
   lConnName := 'MyConnPooled';

   lParams.Add('DriverID=FB');
   lParams.Add('User_Name=sysdba');
   lParams.Add('Password=masterkey');
   lParams.Add('Database=d:\Testing\Diverse\FireDACConnectionNames\test.gdb');
   lParams.Add('Server=localhost');
   lParams.Add('Pooled=true');

   FDManager.AddConnectionDef(lConnName,'FB',lParams);

   lParams.Clear;
   lParams.Add('DriverID=MSSQL');
   lParams.Add('User_Name=test');
   lParams.Add('Password=test');
   lParams.Add('Database=test');
   lParams.Add('Server=VS20032008');
   lParams.Add('Pooled=true');

   for l := FDManager.ConnectionDefs.Count-1 downto 0 do
      if FDManager.ConnectionDefs[l].Name = lConnName then
      begin
         FDManager.DeleteConnectionDef(lConnName);     // This gets executed
         Break;
      end;
   FDManager.AddConnectionDef(lConnName,'MSSQL',lParams);

   // Check ConnectionDefs (inspect lParams):
   lParams.Clear;
   l := FDManager.ConnectionDefs.Count;
   for i := 0 to l-1 do
      lParams.Add(FDManager.ConnectionDefs[i].Name);
   // Contents on my machine:
   // Access_Demo
   // Access_Demo_Pooled
   // DBDEMOS
   // EMPLOYEE
   // MSSQL_Demo
   // RBDemos
   // SQLite_Demo
   // SQLite_Demo_Pooled
   // MyConnPooled      <== Still duplicate
   // MyConnPooled
   lParams.Free;
end;

Так что же здесь происходит и как это исправить?

Это Delphi Tokyo 10.2.1
Если вы хотите запустить этот код, поместите TFDPhysFBDriverLink и TFDPhysMSSQLDriverLink в форму. Я пытался вызвать .Release для них, но это не помогло.


Исправление: размещение компонентов TFDPhysxxxDriverLink не обязательно для запуска кода. Я оставляю это предложение, потому что наличие связанных с ними единиц важно для ошибки AddConnectionDefinition (см. утвержденный ответ).


Устранена проблема: исправления для FireDAC.Stan.Def.pas и FireDAC.Comp.Client.pas доступны по ссылке RSP-19107.


person Jan Doggen    schedule 21.09.2017    source источник


Ответы (1)


Как удалить определение соединения?

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

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

FDManager.DeleteConnectionDef(lConnName);

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

Как предотвратить дублирование имени определения соединения?

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

if not FDManager.IsConnectionDef('FBPooled') then
  FDManager.AddConnectionDef('FBPooled', 'FB', Params)
else
  raise EMyException.Create('Duplicate connection definition name!');

Такой код может быть обходным путем для проблемы, о которой вы сообщили. Попробую описать что не так.

Что не так с предотвращением дублирования имени определения соединения?

К проблеме RSP-19107. Ну, это очень хорошо спрятано. Мне удавалось воспроизвести проблему, только если в приложение был включен физический модуль драйвера[1]. Ожидаемое исключение:

[FireDAC][Стэн][Def]-255. Имя определения [FBPooled] дублируется

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

Итак, почему такой код не вызывает исключения, как утверждает документация, когда включен физический модуль драйвера?

FDManager.AddConnectionDef('DefName', 'FB', Params);
Params.Values['Database'] := 'C:\MyDatabase.db';
FDManager.AddConnectionDef('DefName', 'FB', Params);

Двойная проверка имени определения находится внутри метода TFDDefinition.ParamsChanged, который отражает изменения в параметрах определения соединения. Звучит странно, но имя определения, которое передается методу AddConnectionDef, позже добавляется к параметрам определения в ключе Name, и затем движок ожидает уведомления об изменении, которое вызывает упомянутый Метод ParamsChanged.

Настройка определения в методе AddConnectionDef выглядит следующим образом:

Definition.Params.BeginUpdate; { ← triggers TFDDefinition.ParamsChanging }
try
  Definition.Params.SetStrings(Params); { ← assigns the passed parameters }
  Definition.Name := 'DefName'; { ← adds (or sets) the Name key value in Params }
  Definition.Params.DriverID := 'FB'; { ← creates driver specific parameter instance }
finally
  Definition.Params.EndUpdate; { ← triggers TFDDefinition.ParamsChanged }
end;

На первый взгляд выглядит нормально. Но есть одна небольшая проблема с настройкой строки Params.DriverID. Он инициирует создание экземпляра конкретных параметров драйвера (например, TFDPhysFBConnectionDefParams), который заменяет исходную коллекцию Params. Это правильно, но ломает замок.

Вот что происходит, опять же в псевдокоде:

Definition.Params.BeginUpdate; { ← Definition.Params.FUpdateCount += 1 }
try
  Definition.Params.Free;
  Definition.Params := TDriverSpecificConnectionDefParams.Create;
finally
  Definition.Params.EndUpdate; { ← Definition.Params.FUpdateCount == 0 }
end;

Вот и все. Замена объекта Params просто не может скопировать значение FUpdateCount списка строк, которое должно быть ненулевым, чтобы вызвать событие OnChange при вызове >EndUpdate.

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

Возможное решение этой проблемы в псевдокоде:

var
  UpdateCount: Integer;
begin
  Definition.Params.BeginUpdate; { ← Definition.Params.FUpdateCount == n }
  try
    UpdateCount := Definition.Params.UpdateCount; { ← store the update count }
    Definition.Params.Free;
    Definition.Params := TDriverSpecificConnectionDefParams.Create;
    Definition.UpdateCount := UpdateCount; { ← set the update count for the new instance }
  finally
    Definition.Params.EndUpdate; { ← Definition.Params.FUpdateCount == n }
  end;
end;

[1] На самом деле, если какие-либо файлы драйвера FireDAC.Phys.‹DBMS› есть в вашем списке использования; они включаются автоматически путем размещения в форме компонента TFDPhys‹DBMS›DriverLink.

person Victoria    schedule 22.09.2017
comment
Это какая-то детективная работа! Я перестал отслеживать AddConnectionDef, когда он вошел в интерфейс (IFDStanDefinitions) ;-) И я пропустил существование IsConnectionDef (новый для FireDAC). КСТАТИ. Я отредактировал сноску в вашем ответе. - person Jan Doggen; 22.09.2017
comment
@ Ян, мне потребовалось время, чтобы найти его. Я подозревал ссылки на интерфейс и полностью упустил из виду эту замену объекта (в пределах его собственной блокировки обновления) при поиске причины, по которой этот метод ParamsChanged не вызывается. Спасибо за сноску; Я просто отформатировал его, чтобы он соответствовал остальной части поста. - person Victoria; 22.09.2017
comment
Ваш первый абзац так верен!! После применения «исправления» IsConnectionDef моя тестовая программа работала нормально, но моя реальная программа продолжала иметь проблемы с повторяющимися именами определений соединений. Причина: тот простой факт, что я читал ConnectionDefs[].Names, чтобы проверить, все ли в порядке! Что за трата времени. Теперь я добавляю это в RSP-19107 (я думаю, что это слишком подробно для SO). - person Jan Doggen; 22.09.2017
comment
@Victoria: еще один первоклассный ответ! +1 Я рад, что кто-то может разобраться в тонкостях FDac. - person MartynA; 22.09.2017