Почему FireDAC игнорирует имя индекса? - PullRequest
3 голосов
/ 01 июля 2019

Я пытаюсь создать таблицу в базе данных SQL Server, используя FireDAC. Однако вместо использования имени индекса, которое я предоставляю, FireDAC использует неверное имя индекса, вызывая исключение, и таблица не создается. Я что-то не так делаю? Если нет, есть ли обходной путь?

Обратите внимание, что я использую правильное имя схемы базы данных cnf для TableName. Мне специально нужно создать таблицу в схеме .

Простейший тестовый пример:

var
  Connection: TFDConnection;
  Table: TFDTable;
begin
  Connection := TFDConnection.Create(nil);
  Table := TFDTable.Create(nil);
  try
    Connection.Params.Add ('DriverID=MSSQL');
    Connection.Params.Add ('OSAuthent=No');
    Connection.Params.Add ('User_Name=sa');
    Connection.Params.Add ('Password=XXXXXX');
    Connection.Params.Add ('Server=DAVE-DELL\MSSQLSERVER2016');
    Connection.Params.Add ('Database=PROJECT_DB');
    Connection.Params.Add ('MARS=No');

    Connection.Open;
    Table.Connection := Connection;

    Table.TableName := 'cnf.TestTable';
    Table.FieldDefs.Add ('TableID', ftAutoInc, 0, true);
    Table.FieldDefs.Add ('Field1', ftInteger, 0, true);
    Table.FieldDefs.Add ('Field2', ftstring, 100, true);

    Table.IndexDefs.Add ('PK_XYZ', 'TableID', [ixPrimary]);   // should use this index name!

    Table.CreateTable (true);

  finally
    Table.Free;
    Connection.Free;
  end;

end;

Возникло исключение:

[FireDAC][Phys][ODBC][Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near '.'.

Запуск SQL Server Profiler показывает, что FireDAC пытается создать индекс, используя следующий код SQL:

ALTER TABLE temp.TestTable ADD CONSTRAINT [cnf].[PK_TestTable] PRIMARY KEY (TableID)

И, конечно же, [cnf].[PK_TestTable] не является допустимым именем индекса в T-SQL, что является основной проблемой.

  • Если удалить строку Table.IndexDefs.Add, таблица будет создана правильно, но без индекса.
  • Если я заменим эту строку на следующую, то возникнет та же проблема:

    with Table.IndexDefs.AddIndexDef do begin
      Name := 'PK_XYZ';
      Options := [ixPrimary];
      Fields := 'TableID';
    end;
    
  • Если я заменю настройку имени таблицы на следующую, это вызовет ту же проблему:

    Table.TableName := 'TestTable';
    Table.SchemaName := 'cnf';
    

Почему он использует свое (неправильное) индексное имя вместо имени, которое я ему дал? (т.е. PK_XYZ)

  • Embarcadero® Delphi 10.1, Берлин, версия 24.0.25048.9432
  • SQL Server 2016 (SP2-CU4) - 13.0.5233.0 (X64)

1 Ответ

4 голосов
/ 02 июля 2019

Я что-то не так делаю?
Почему он использует свое (неправильное) индексное имя вместо имени, которое я ему дал?

Кажется, вы все делаетев самый раз.Проблема заключается в сгенерированной команде SQL, которую вы отследили.SQL Server не допускает имя схемы в имени ограничения при добавлении ограничения с использованием ALTER TABLE.Ограничения, созданные таким образом, автоматически становятся частью схемы связанной таблицы, однако вам следует позже использовать имя схемы при ссылке на ограничение:

SELECT OBJECT_ID('cnf.PK_XYZ')

Теперь, где все идет не так?FireDAC использует TFDPhysCommandGenerator и его предков для генерации команд SQL для конкретной СУБД.Ваш вызов метода CreateTable приводит к вызову TFDPhysCommandGenerator.GetCreatePrimaryKey, который отвечает за генерацию SQL для первичного ключа.Он также содержит этот код:

sTab := GetFrom;
FConnMeta.DecodeObjName(sTab, rName, nil, [doUnquote]);
rName.FObject := 'PK_' + rName.FObject;
Result := 'ALTER TABLE ' + sTab + ' ADD CONSTRAINT ' +
  FConnMeta.EncodeObjName(rName, nil, [eoQuote, eoNormalize]) + ' PRIMARY KEY (';

Этот код делает то, что он принимает ваше полное имя таблицы (sTab), разбивает его (DecodeObjName) на части (rName) и добавляет 'PK_' к имени таблицы и присоединяет детали (EncodeObjName) к полному имени, которое затем используется в качестве имени ограничения для вашего первичного ключа.Теперь мы можем ясно видеть, что генератор команд игнорирует имя вашего индекса и генерирует ошибочный T-SQL.Это может быть ошибка или просто не поддерживаемая функция.EMBT должен принять решение по этому вопросу.Я бы рекомендовал сообщить об этом как об ошибке.

Есть ли обходной путь?

Да, вы можете перехватить проблемный метод или переопределить его в своемсобственный производный класс.Реализация ничего из этого не является тривиальной, и из-за юридических проблем я не собираюсь расширять ее здесь, потому что мне пришлось бы дублировать исходный код FireDAC.

Что касается синтаксической ошибки, добавив эти строки в 'TFDPhysCommandGenerator.Реализация GetCreatePrimaryKey после DecodeObjName решит проблему:

rName.FCatalog := '';
rName.FSchema := '';
rName.FBaseObject := '';
rName.FLink := '';

Исправление имени ограничения будет более громоздким, чем то, потому что метод получает только имена столбцов индекса в качестве аргумента и не имеет очевидного доступа к оригиналуIndexDefs, где вы можете просто использовать имя индекса в качестве имени ограничения первичного ключа.Получение доступа к имени индекса оттуда также позволит вам избавиться от имени таблицы декодирования / кодирования в имени индекса.Однако этот процесс может быть важен для других DMBS, отличных от SQL Server.

PS: Если бы только половина всех вопросов была написана таким образом ... Спасибо за этот замечательный вопрос.

...