有没有简单的方法可以比较连接字符串,而不需要自己解析它?

7

我需要能够比较两个不同的连接字符串,并确定它们是否是相同的信息。我不能进行简单的字符串比较,因为属性可能以不同的方式布置,但仍然表示相同的连接。

在编写自己的比较函数之前,是否已经有现成的方法可以做到这一点吗?

我已经搜索了一些方法,但没有找到相关的内容。


你是想在程序内解析连接字符串,还是想比较配置文件或其他文件系统中的连接字符串? - Aaron Bertrand
在程序中,我有两个字符串,每个字符串都是一个连接字符串,我需要检查它们是否连接到同一个数据库。 - Jerry Dodge
我看到至少两种解决方案:使用正则表达式;使用指定分号分隔符的TStringList。 - Abelisto
2个回答

11
您可以使用IDataInitialize::GetDataSource方法,该方法从给定的连接字符串返回一个未初始化的数据源对象。由于此方法返回一个IUnknown类型的数据源对象指针,因此您不能直接比较为两个不同连接字符串获取的对象。但是,您可以查询这些未初始化的数据源对象上的IDBProperties接口,这使您能够访问给定提供程序支持的所有属性。
要获取一个属性集,您需要使用 IDBProperties::GetProperties 方法。这将返回一个包含一组DBPROP元素(属性)的DBPROPSET结构。然后,您只需迭代此数组并以所需的方式比较这两个数据源对象的属性即可。
以下的 IsSameConnStr 函数在连接字符串相等时返回 True,否则返回 False。请注意,所使用的属性值比较不区分大小写,除了DBPROP_AUTH_PASSWORD属性,它是区分大小写的:
uses
  ActiveX, ComObj, OleDB;

function IsSameVarWideStr(const AValue1, AValue2: OleVariant;
  ACaseSensitive: Boolean = False): Boolean;
begin
  Result := VarType(AValue1) = VarType(AValue2);
  if Result then
  begin
    if ACaseSensitive then
      Result := WideCompareStr(VarToWideStr(AValue1),
        VarToWideStr(AValue2)) = 0
    else
      Result := WideCompareText(VarToWideStr(AValue1),
        VarToWideStr(AValue2)) = 0;
  end;
end;

function IsSameConnStr(const AConnStr1, AConnStr2: WideString): Boolean;
var
  I: Integer;
  DataSrc1: IUnknown;
  DataSrc2: IUnknown;
  DataInit: IDataInitialize;
  PropSet1: PDBPropSet;
  PropSet2: PDBPropSet;
  PropSetCnt1: ULONG;
  PropSetCnt2: ULONG;
  Properties1: IDBProperties;
  Properties2: IDBProperties;
const
  DBPROP_AUTH_PASSWORD = $00000009;
begin
  // first check if the input connection strings aren't exactly the same
  Result := CompareStr(AConnStr1, AConnStr2) = 0;
  // if they are not same, then...
  if not Result then
  begin
    // create IDataInitialize object instance
    OleCheck(CoCreateInstance(CLSID_DataLinks, nil, CLSCTX_INPROC_SERVER or
      CLSCTX_LOCAL_SERVER, IID_IDataInitialize, DataInit));
    // get data source objects for both input connection strings
    OleCheck(DataInit.GetDataSource(nil, CLSCTX_INPROC_SERVER,
      PWideChar(AConnStr1), IUnknown, DataSrc1));
    OleCheck(DataInit.GetDataSource(nil, CLSCTX_INPROC_SERVER,
      PWideChar(AConnStr2), IUnknown, DataSrc2));
    // query for IDBProperties objects of the data source objects
    if Succeeded(DataSrc1.QueryInterface(IID_IDBProperties, Properties1)) and
      Succeeded(DataSrc2.QueryInterface(IID_IDBProperties, Properties2)) then
    begin
      // get properties of data source objects
      OleCheck(Properties1.GetProperties(0, nil, PropSetCnt1, PropSet1));
      OleCheck(Properties2.GetProperties(0, nil, PropSetCnt2, PropSet2));
      try
        // same DB provider will have the same set of initialization properties,
        // so the first check might be the property count, if that differs, then
        // at least DB provider is different, so if this equals, then...
        if PropSetCnt1 = PropSetCnt2 then
        begin
          // initialize positive result
          Result := True;
          // iterate all the properties
          for I := 0 to PropSet1.cProperties - 1 do
          begin
            // check if we're comparing the same property and if so, compare the
            // property values; for password property compare the value with case
            // sensitivity, for all the others case insensitively; if any of this
            // doesn't match, we're done with False result and we can exit
            if (PropSet1.rgProperties[I].dwPropertyID <>
              PropSet2.rgProperties[I].dwPropertyID) or
              not IsSameVarWideStr(PropSet1.rgProperties[I].vValue,
              PropSet2.rgProperties[I].vValue,
              PropSet1.rgProperties[I].dwPropertyID = DBPROP_AUTH_PASSWORD) then
            begin
              Result := False;
              Break;
            end;
          end;
        end;
      finally
        // release the property sets; note that you should avoid this common
        // try..finally block and that you should free also each property array
        // element by using IMalloc::Free; why I've used CoTaskMemFree see this
        // question http://stackoverflow.com/q/3079508/960757
        CoTaskMemFree(PropSet1);
        CoTaskMemFree(PropSet2);
      end;
    end;
  end;
end;

我认为使用方法很清晰,所以我更愿意提及一些连接字符串的结果:

IsSameConnStr = True
AConnStr1: Provider=MSDASQL.1;Persist Security Info=True;Data Source=datasource
AConnStr2: Provider=MSDASQL.1;Persist Security Info=True;Data Source=DATASOURCE

IsSameConnStr = True
AConnStr1: Provider=MSDASQL.1;Data Source=datasource;Persist Security Info=True
AConnStr2: Provider=MSDASQL.1;Persist Security Info=True;Data Source=DATASOURCE

IsSameConnStr = True
AConnStr1: Provider=MSDASQL.1;Password=PASSWORD;Data Source=datasource;Persist Security Info=True
AConnStr2: Provider=MSDASQL.1;Data Source=DATASOURCE;Password=PASSWORD;Persist Security Info=True

IsSameConnStr = False - password differs in case sensitivity
AConnStr1: Provider=MSDASQL.1;Password=PASSWORd;Data Source=datasource;Persist Security Info=True
AConnStr2: Provider=MSDASQL.1;Data Source=DATASOURCE;Password=PASSWORD;Persist Security Info=True

6
为了获取ConnectionString属性的集合,您可以将ConnectionString赋值给TADOConnection(不需要连接到数据库),并使用TADOConnection.Properties集合(集合项为ADOInt.Property_)例如:
ADOConnection.Properties.Get_Item('Data Source')

你应该比较具体的属性,以确定连接是否通过特定提供者设置到特定数据存储。例如:
提供程序数据源初始目录用户ID \ 密码(可选)。

根据提供者,你可能希望忽略许多属性,例如:
工作站ID保持安全性信息使用准备过程自动转换等。

以下是一个遍历TADOConnection属性集合的示例:

var
  ADOConnection: TADOConnection;
  PropName, PropValue: WideString;
  I: Integer;

  ADOConnection := TADOConnection.Create(nil);
  try
    ADOConnection.ConnectionString := 'Provider=MSDASQL.1;Password=secret;Data Source=127.0.0.1;User ID=user;Initial Catalog=mycatalog';
    for I := 0 to ADOConnection.Properties.Count - 1 do
    begin
      // Properties.Item[I] is ADOInt.Property_
      PropName := ADOConnection.Properties.Item[I].Name;
      PropValue := VarToWideStr(ADOConnection.Properties.Item[I].Value);
      ShowMessage(Format('%s=%s', [PropName, PropValue]));
    end;
  finally
    ADOConnection.Free;
  end;

TADOConnection连接到数据库之后,可能会添加/更改许多属性到ConnectionString中,因此您需要考虑这一点。


那实际上是我在找到 IDataInitialize 之前想到的第一件事。[+1] - TLama
2
@TLama,也许TADOConnection属性集合实际上是通过IDataInitialize实现的。我不确定... - kobik
3
看看这篇文章,里面有一条注释:"ADO连接对象隐式使用IDataInitialize。",但我喜欢这种方式是因为它简单易懂。 - TLama

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接