Есть ли простой способ сравнить строки подключения, не разбирая его сам?

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

Прежде чем я пойду и напишу свое собственное сравнение для этого, уже есть что-то, что может сделать это?

Я искал способ, и я ничего не могу найти об этом.

2 ответа

Решение

Вы можете использовать IDataInitialize::GetDataSource метод, который возвращает неинициализированный объект источника данных из заданной строки подключения. Из-за этого метода возвращает указатель на объект источника данных IUnknown типа, вы не можете напрямую сравнивать объекты, полученные для двух сравниваемых строк соединения. Тем не мение, you can query IDBProperties интерфейс на этих неинициализированных объектах источника данных, что дает вам возможность получить доступ ко всем свойствам, поддерживаемым данным провайдером.

Чтобы получить набор свойств, вам нужно использовать IDBProperties::GetProperties метод. Это вернет DBPROPSETструктура, которая содержит массив DBPROP элементы (свойства). Затем вы просто итерируете этот массив и сравните свойства этих двух объектов источника данных так, как вам нужно.

Следующие 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://stackru.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

Чтобы получить коллекцию ConnectionString свойства, вы можете назначить ConnectionString в TADOConnection (без фактического подключения к БД) и использовать TADOConnection.Properties коллекция (элемент коллекции ADOInt.Property_) например:

ADOConnection.Properties.Get_Item('Data Source')

Вероятно, вам следует сравнить конкретные свойства, чтобы определить, установлено ли соединение для определенного хранилища данных через определенного поставщика. например:
Provider, Data Source, Initial Catalog, User ID \ Password (необязательный).

Есть много свойств, которые вы можете игнорировать в зависимости от поставщика, например:
Workstation ID, Persist Security Info, Use Procedure for Prepare, Auto Translate, так далее...

Вот пример того, как перебрать 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;

Там может быть гораздо больше свойств, которые добавляются / изменяются в ConnectionString после TADOConnection был подключен к БД, поэтому необходимо принять это во внимание.

Другие вопросы по тегам