Не удалось получить образец Delphi EventAlerter, работающий с SQL Server 2012

Мне нужно обнаружить изменения в базе данных, и я подумал, что компонент TFDEventAlerter будет хорошим вариантом. Пример приложения, поставляемого с Delphi 10.2 Tokyo, у меня не работает. Это работает для других типов баз данных. Нажатие на кнопку Fire Events не обнаруживается при подключении к MS SQL. Кто-нибудь может предложить какие-либо изменения, чтобы заставить это работать? Кроме того, вы можете указать мне рабочий пример этого компонента с SQL Server?

1 ответ

FireDAC предоставляет два способа использования уведомлений о запросах SQL Server. Либо вы позволяете FireDAC создать таблицу _FD_EVENTS, за которой отслеживается UPDATE значения поля в строке, вставляемой для каждого зарегистрированного события [1] (раздел 2.1.), Либо задаете запрос SELECT, чей набор результатов будет отслеживаться для любого UPDATE. с момента регистрации события [1] (раздел 2.2.).

Для использования уведомлений о запросах SQL Server, реализованных FireDAC в целом, вы должны сначала включить их для используемой базы данных (раздел 1.).


1. Включить уведомления

Для уведомлений о запросах, которые реализует FireDAC, вы должны включить компонент Service Broker в базе данных, к которой вы будете подключены. Чтобы проверить, включен ли компонент Service Broker для вашей базы данных, вы можете выполнить запрос следующим образом (с возможными результатами 0= отключено, 1= включено):

SELECT is_broker_enabled FROM sys.databases WHERE name = 'MyDatabase'

Если он отключен, выполните этот запрос:

ALTER DATABASE MyDatabase SET ENABLE_BROKER;

Или, если у вас будут какие-то ожидающие транзакции и выполнение вышеуказанного запроса занимает много времени, вы можете откатить все ожидающие транзакции и немедленно ИЗМЕНИТЬ вашу базу данных:

ALTER DATABASE MyDatabase SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;

Хорошо, теперь давайте предположим, что у вас включен компонент Service Broker для вашей базы данных, и у вас достаточно прав доступа для подписки на уведомления.


2.1. _FD_EVENTS ОБНОВЛЕНИЕ уведомления

Когда событие, определенное в формате , зарегистрировано [1], FireDAC создает таблицу _FD_EVENTS (если необходимо) в подключенной базе данных и вставляет в эту таблицу строку со значением поля " Имя" (которое предназначено для быть уникальным именем события) и значением поля Значение, установленным на 0. Затем событие запускается всякий раз, когда значением поля Значение в этой строке является ОБНОВЛЕНИЕ d.

Итак, если вы определите свое событие так:

FDEventAlerter1.Names.Clear;
{ create temporary, uniquely named queue and service }
FDEventAlerter1.Names.Add('QUEUE=?');
FDEventAlerter1.Names.Add('SERVICE=?');
{ define event in format '<message>', where:
  <message> - event name (passed as AEventName param of the OnAlert event) }
FDEventAlerter1.Names.Add('MyEvent');

Вы можете вызвать событие, например, выполнив запрос, подобный этому (на самом деле, даже значение BIGINT может переполниться запросом, подобным этому, так что остерегайтесь использовать его в рабочем коде):

UPDATE _FD_EVENTS SET Value = Value + 1 WHERE Name = 'MyEvent';

События, определенные в формате , также могут запускаться методом Signal из кода (он внутренне создает запрос, как описано выше, для запуска подписанного события).


2.2. Запрос результатов обновления ОБНОВЛЕНИЕ

Другим способом является просмотр любого ОБНОВЛЕНИЯ определенного результирующего набора, возвращаемого данным запросом SELECT во время регистрации события [1].

Давайте создадим такую ​​таблицу с двумя вставленными строками:

CREATE TABLE Customers
(
   ID INT IDENTITY(1, 1) PRIMARY KEY NOT NULL,
   FirstName VARCHAR(50) NOT NULL
);

INSERT INTO Customers (FirstName) VALUES ('John');
INSERT INTO Customers (FirstName) VALUES ('Alice');

Теперь, чтобы получить уведомление при изменении значения поля FirstName любой из этих двух строк, определите формат события следующим образом, прежде чем зарегистрировать событие [1]:

FDEventAlerter1.Names.Clear;
{ create temporary, uniquely named queue and service }
FDEventAlerter1.Names.Add('QUEUE=?');
FDEventAlerter1.Names.Add('SERVICE=?');
{ define event in format 'CHANGE<index>=<message>;<SELECT query>', where:
  <index> - event index
  <message> - event name (passed as AEventName param of the OnAlert event)
  <SELECT query> - SELECT query whose resultset will be watched for UPDATE
                   changes; when this resultset changes, the event fires }
FDEventAlerter1.Names.Add('CHANGE1=MyEvent;SELECT FirstName FROM Customers');

Для запуска события вы можете затем обновить значение поля FirstName любой строки в наборе наблюдаемых результатов, например:

UPDATE Customers SET FirstName = 'Johnny' WHERE ID = 1;

Или же:

UPDATE Customers SET FirstName = 'Alicia' WHERE ID = 2;

Или же:

UPDATE Customers SET FirstName = 'Robert' WHERE ID = 1;
UPDATE Customers SET FirstName = 'Cathie' WHERE ID = 2;

Но вы не будете уведомлены, когда вы ВСТАВИТЕ новую строку и ОБНОВИТЕ ее впоследствии после регистрации события [1], независимо от того, что определенный набор результатов будет фактически включать такую ​​строку. Таким образом, вы не получите уведомление:

INSERT INTO Customers (FirstName) VALUES ('Barry');
UPDATE Customers SET FirstName = 'Barrie' WHERE ID = SCOPE_IDENTITY();

Это означает, что вы на самом деле создаете такой результат, который просматривается. То же самое происходит и в случае событий, определенных в формате , только FireDAC создает таблицу с одной строкой, которая затем отслеживает обновления для вас.


3. Минимальный пример

Вот минимальный пример использования формата . Я предполагаю, что FDEventAlerter1 назначил Connection, который использует драйвер собственного клиента SQL Server, и создал обработчики событий для событий OnAlert и OnTimeout, показанных ниже:

procedure TForm1.ButtonStartClick(Sender: TObject);
begin
  FDEventAlerter1.Names.Clear;
  { create temporary, uniquely named queue and service }
  FDEventAlerter1.Names.Add('QUEUE=?');
  FDEventAlerter1.Names.Add('SERVICE=?');
  { define event in format '<message>', where:
    <message> - event name (passed as AEventName param of the OnAlert event) }
  FDEventAlerter1.Names.Add('MyEvent');
  { there's only one for SQL Server, <default> should equal to this by now }
  FDEventAlerter1.Options.Kind := 'QueryNotifies';
  { if Timeout > 0 and no event arrives in this number of milliseconds, the
    OnTimeout event is fired, we can use it as "heartbeat" }
  FDEventAlerter1.Options.Timeout := 10000;
  { execute event handlers in the main thread }
  FDEventAlerter1.Options.Synchronize := True;
  { start watching }
  FDEventAlerter1.Active := True;
end;

procedure TForm1.FDEventAlerter1Alert(ASender: TFDCustomEventAlerter;
  const AEventName: string; const AArgument: Variant);
begin
  { if AEventName matches <message> from the event definition }
  if SameText(AEventName, 'MyEvent') then
    ShowMessage('MyEvent fired!');
end;

procedure TForm1.FDEventAlerter1Timeout(Sender: TObject);
begin
  ShowMessage('No event was fired in 10 seconds!');
end;

А теперь попробуйте вызвать вышеуказанное событие. Выполните это на SQL Server:

UPDATE _FD_EVENTS SET Value = Value + 1 WHERE Name = 'MyEvent';

Вызов метода Signal из кода должен привести к тому же результату, что и выполнение вышеуказанного запроса, сигнальное событие.


[1] - под регистрацией события подразумевается либо вызов метода Register, либо включение свойства Active.

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