1

我的应用程序有一个设计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 错误是必不可少的(请参阅批准的答案)。


已解决的问题:该RSP-19107链接上的补丁FireDAC.Stan.Def.pasFireDAC.Comp.Client.pas可用的补丁。

4

1 回答 1

4

如何删除连接定义?

删除循环的问题是由访问增加了引用计数的迭代对象引起的,这反过来又阻止了从定义集合中删除该对象。我最好避免访问该集合。

顺便提一句。这样的循环没有什么意义,因为删除方法需要名称,而不是索引,所以直接调用它会产生基本相同的效果:

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值,该值需要非零才能在调用EndUpdate方法时触发OnChange事件。

这就是为什么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组件自动包含的。

于 2017-09-22T06:40:58.553 回答