Реализация обработчика бизнес-логики для статьи публикации слиянием

Область применения: SQL Server

В этом разделе описывается реализация обработчика бизнес-логики для статьи слияния в SQL Server с помощью программирования репликации или объектов управления репликацией (RMO).

Пространство имен Microsoft.SqlServer.Replication.BusinessLogicSupport реализует интерфейс, позволяющий создавать сложную бизнес-логику для обработки событий, возникающих в процессе синхронизации слиянием. Методы в обработчике бизнес-логики могут вызываться процессом репликации для каждой изменившейся строки, которая проходит репликацию в ходе синхронизации.

Общий порядок действий по реализации обработчика бизнес-логики следующий.

  1. Создайте сборку обработчика бизнес-логики.

  2. Зарегистрируйте сборку на распространителе.

  3. Выполните развертывание сборки на сервере, где работает агент слияния. Для подписок по запросу агент работает на подписчике, а для принудительных подписок — на распространителе. Если используется веб-синхронизация, агент работает на веб-сервере.

  4. Создание новых статей и настройка существующих статей на использование обработчика бизнес-логики.

Указанный обработчик бизнес-логики выполняется для каждой синхронизируемой строки. Сложная логика и вызовы других приложений или сетевых служб могут влиять на производительность. Дополнительные сведения об обработчиках бизнес-логики см. в статье Выполнение бизнес логики во время синхронизации слияния.

В этом разделе

При помощи программирования репликации

Создание и развертывание обработчика бизнес-логики

  1. В Microsoft Visual Studio создайте проект для сборки .NET, содержащей код, реализующий обработчик бизнес-логики.

  2. Добавьте в проект ссылки на следующие пространства имен.

    Ссылка на сборку Расположение
    Microsoft.SqlServer.Replication.BusinessLogicSupport C:\Program Files\Microsoft SQL Server\nnn\COM (установка по умолчанию)
    System.Data Глобальный кэш сборок (компонент платформы .NET Framework)
    System.Data.Common Глобальный кэш сборок (компонент платформы .NET Framework)
  3. Добавьте класс, переопределяющий класс BusinessLogicModule .

  4. Реализуйте свойство HandledChangeStates , чтобы показать обрабатываемые типы изменений.

  5. Переопределите один или несколько следующих методов класса BusinessLogicModule :

    • CommitHandler — вызывается, когда изменение данных фиксируется в ходе синхронизации;

    • DeleteErrorHandler — вызывается, когда возникает ошибка при загрузке или передаче инструкции DELETE;

    • DeleteHandler — вызывается, когда передаются или загружаются инструкции DELETE;

    • InsertErrorHandler — вызывается, когда возникает ошибка при загрузке или передаче инструкции INSERT;

    • InsertHandler — вызывается, когда передаются или загружаются инструкции INSERT;

    • UpdateConflictsHandler — вызывается, когда на издателе или на подписчике возникает конфликт инструкций UPDATE;

    • UpdateDeleteConflictHandler — вызывается, когда инструкции UPDATE вызывают конфликт с инструкциями DELETE на издателе и на подписчике;

    • UpdateErrorHandler — вызывается, когда возникает ошибка при загрузке или передаче инструкции UPDATE;

    • UpdateHandler — вызывается, когда передаются или загружаются инструкции UPDATE.

  6. Постройте проект, чтобы создать сборку обработчика бизнес-логики.

  7. Разверните сборку в каталоге, содержашем исполняемый файл агент слияния (replmerg.exe), который для установки по умолчанию — C:\Program Files\Microsoft SQL Server\nnn\COM, или установите его в глобальном кэше сборок .NET (GAC). Если эта сборка используется не только агентом слияния, то она должна быть помещена в глобальный кэш сборок. Установка сборки в глобальный кэш производится с помощью соответствующей программы (Gacutil.exe) , поставляемой в пакете .NET Framework SDK.

    Примечание.

    Обработчик бизнес-логики должен быть развернут на всех серверах, на которых работает агент слияния, включая север IIS, на котором во время веб-синхронизации находится файл replisapi.dll.

Регистрация обработчика бизнес-логики

  1. На издателе выполните sp_enumcustomresolvers (Transact-SQL), чтобы убедиться, что сборка еще не зарегистрирована в качестве обработчика бизнес-логики.

  2. На распространитее выполните sp_registercustomresolver (Transact-SQL), указав понятное имя обработчика бизнес-логики для @article_resolver, значение true для @is_dotnet_assembly, имя сборки для @dotnet_assembly_name и полное имя класса, переопределяющего BusinessLogicModule для @dotnet_class_name.

    Примечание.

    Если сборка не найдена ни в том же каталоге, что и исполняемый файл агента слияния, ни в папке приложения, производящего синхронный запуск агента слияния, ни в глобальном кэше сборок (GAC), то в параметре @dotnet_assembly_nameпомимо имени сборки необходимо задать ее полный путь. При проведении сеанса веб-синхронизации необходимо указать местоположение сборки на веб-сервере.

Использование обработчика бизнес-логики со статьей в новой таблице

  1. Выполните sp_addmergearticle (Transact-SQL), чтобы определить статью, указав понятное имя обработчика бизнес-логики для @article_resolver. Дополнительные сведения см. в статье определить статью.

Использование обработчика бизнес-логики со статьей в существующей таблице

  1. Выполните sp_changemergearticle (Transact-SQL), указав @publication, @article, значение article_resolver для @property и понятное имя обработчика бизнес-логики для @value.

Примеры (программирование репликации)

В следующем примере представлен обработчик бизнес-логики, осуществляющий запись в журнал аудита.

using System;
using System.Text;
using System.Data;
using System.Data.Common;
using Microsoft.SqlServer.Replication.BusinessLogicSupport;
using Microsoft.Samples.SqlServer.BusinessLogicHandler;

namespace Microsoft.Samples.SqlServer.BusinessLogicHandler
{
    public class OrderEntryBusinessLogicHandler :
      Microsoft.SqlServer.Replication.BusinessLogicSupport.BusinessLogicModule
    {
        // Variables to hold server names.
        private string publisherName;
        private string subscriberName;

        public OrderEntryBusinessLogicHandler()
        {
        }

        // Implement the Initialize method to get publication 
        // and subscription information.
        public override void Initialize(
            string publisher,
            string subscriber,
            string distributor,
            string publisherDB,
            string subscriberDB,
            string articleName)
        {
            // Set the Publisher and Subscriber names.
            publisherName = publisher;
            subscriberName = subscriber;
        }

        // Declare what types of row changes, conflicts, or errors to handle.
        override public ChangeStates HandledChangeStates
        {
            get
            {
                // Handle Subscriber inserts, updates and deletes.
                return ChangeStates.SubscriberInserts |
                  ChangeStates.SubscriberUpdates | ChangeStates.SubscriberDeletes;
            }
        }

        public override ActionOnDataChange InsertHandler(SourceIdentifier insertSource,
          DataSet insertedDataSet, ref DataSet customDataSet, ref int historyLogLevel,
          ref string historyLogMessage)
        {
            if (insertSource == SourceIdentifier.SourceIsSubscriber)
            {
                // Build a line item in the audit message to log the Subscriber insert.
                StringBuilder AuditMessage = new StringBuilder();
                AuditMessage.Append(String.Format("A new order was entered at {0}. " +
                  "The SalesOrderID for the order is :", subscriberName));
                AuditMessage.Append(insertedDataSet.Tables[0].Rows[0]["SalesOrderID"].ToString());
                AuditMessage.Append("The order must be shipped by :");
                AuditMessage.Append(insertedDataSet.Tables[0].Rows[0]["DueDate"].ToString());

                // Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString();
                
                // Set the history log level to the default verbose level.
                historyLogLevel = 1;

                // Accept the inserted data in the Subscriber's data set and 
                // apply it to the Publisher.
                return ActionOnDataChange.AcceptData;
            }
            else
            {
                return base.InsertHandler(insertSource, insertedDataSet, ref customDataSet,
                  ref historyLogLevel, ref historyLogMessage);
            }
        }

        public override ActionOnDataChange UpdateHandler(SourceIdentifier updateSource,
          DataSet updatedDataSet, ref DataSet customDataSet, ref int historyLogLevel,
          ref string historyLogMessage)
        {
            if (updateSource == SourceIdentifier.SourceIsPublisher)
            {
                // Build a line item in the audit message to log the Subscriber update.
                StringBuilder AuditMessage = new StringBuilder();
                AuditMessage.Append(String.Format("An existing order was updated at {0}. " +
                  "The SalesOrderID for the order is ", subscriberName));
                AuditMessage.Append(updatedDataSet.Tables[0].Rows[0]["SalesOrderID"].ToString());
                AuditMessage.Append("The order must now be shipped by :");
                AuditMessage.Append(updatedDataSet.Tables[0].Rows[0]["DueDate"].ToString());

                // Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString();
                // Set the history log level to the default verbose level.
                historyLogLevel = 1;

                // Accept the updated data in the Subscriber's data set and apply it to the Publisher.
                return ActionOnDataChange.AcceptData;
            }
            else
            {
                return base.UpdateHandler(updateSource, updatedDataSet,
                  ref customDataSet, ref historyLogLevel, ref historyLogMessage);
            }
        }

        public override ActionOnDataDelete DeleteHandler(SourceIdentifier deleteSource,
          DataSet deletedDataSet, ref int historyLogLevel, ref string historyLogMessage)
        {
            if (deleteSource == SourceIdentifier.SourceIsSubscriber)
            {
                // Build a line item in the audit message to log the Subscriber deletes.
                // Note that the rowguid is the only information that is 
                // available in the dataset.
                StringBuilder AuditMessage = new StringBuilder();
                AuditMessage.Append(String.Format("An existing order was deleted at {0}. " +
                  "The rowguid for the order is ", subscriberName));
                AuditMessage.Append(deletedDataSet.Tables[0].Rows[0]["rowguid"].ToString());

                // Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString();
                // Set the history log level to the default verbose level.
                historyLogLevel = 1;

                // Accept the delete and apply it to the Publisher.
                return ActionOnDataDelete.AcceptDelete;
            }
            else
            {
                return base.DeleteHandler(deleteSource, deletedDataSet,
                  ref historyLogLevel, ref historyLogMessage);
            }
        }
    }
}
Imports System
Imports System.Text
Imports System.Data
Imports System.Data.Common
Imports Microsoft.SqlServer.Replication.BusinessLogicSupport

Namespace Microsoft.Samples.SqlServer.BusinessLogicHandler
    Public Class OrderEntryBusinessLogicHandler
        Inherits BusinessLogicModule

        ' Variables to hold server names.
        Private publisherName As String
        Private subscriberName As String

        ' Implement the Initialize method to get publication 
        ' and subscription information.
        Public Overrides Sub Initialize( _
        ByVal publisher As String, _
        ByVal subscriber As String, _
        ByVal distributor As String, _
        ByVal publisherDB As String, _
        ByVal subscriberDB As String, _
        ByVal articleName As String _
      )
            ' Set the Publisher and Subscriber names.
            publisherName = publisher
            subscriberName = subscriber
        End Sub

        ' Declare what types of row changes, conflicts, or errors to handle.
        Public Overrides ReadOnly Property HandledChangeStates() As ChangeStates
            Get
                ' Handle Subscriber inserts, updates and deletes.
                Return (ChangeStates.SubscriberInserts Or _
                 ChangeStates.SubscriberUpdates Or ChangeStates.SubscriberDeletes)
            End Get
        End Property

        Public Overrides Function InsertHandler(ByVal insertSource As SourceIdentifier, _
        ByVal insertedDataSet As DataSet, ByRef customDataSet As DataSet, _
        ByRef historyLogLevel As Integer, ByRef historyLogMessage As String) _
        As ActionOnDataChange

            If insertSource = SourceIdentifier.SourceIsSubscriber Then
                ' Build a line item in the audit message to log the Subscriber insert.
                Dim AuditMessage As StringBuilder = New StringBuilder()
                AuditMessage.Append(String.Format("A new order was entered at {0}. " + _
                 "The SalesOrderID for the order is :", subscriberName))
                AuditMessage.Append(insertedDataSet.Tables(0).Rows(0)("SalesOrderID").ToString())
                AuditMessage.Append("The order must be shipped by :")
                AuditMessage.Append(insertedDataSet.Tables(0).Rows(0)("DueDate").ToString())

                ' Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString()

                ' Set the history log level to the default verbose level.
                historyLogLevel = 1

                ' Accept the inserted data in the Subscriber's data set and 
                ' apply it to the Publisher.
                Return ActionOnDataChange.AcceptData
            Else
                Return MyBase.InsertHandler(insertSource, insertedDataSet, customDataSet, _
                 historyLogLevel, historyLogMessage)
            End If
        End Function
        Public Overrides Function UpdateHandler(ByVal updateSource As SourceIdentifier, _
        ByVal updatedDataSet As DataSet, ByRef customDataSet As DataSet, _
        ByRef historyLogLevel As Integer, ByRef historyLogMessage As String) _
        As ActionOnDataChange

            If updateSource = SourceIdentifier.SourceIsPublisher Then
                ' Build a line item in the audit message to log the Subscriber update.
                Dim AuditMessage As StringBuilder = New StringBuilder()
                AuditMessage.Append(String.Format("An existing order was updated at {0}. " + _
                 "The SalesOrderID for the order is ", subscriberName))
                AuditMessage.Append(updatedDataSet.Tables(0).Rows(0)("SalesOrderID").ToString())
                AuditMessage.Append("The order must now be shipped by :")
                AuditMessage.Append(updatedDataSet.Tables(0).Rows(0)("DueDate").ToString())

                ' Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString()
                ' Set the history log level to the default verbose level.
                historyLogLevel = 1

                ' Accept the updated data in the Subscriber's data set and apply it to the Publisher.
                Return ActionOnDataChange.AcceptData
            Else
                Return MyBase.UpdateHandler(updateSource, updatedDataSet, _
                 customDataSet, historyLogLevel, historyLogMessage)
            End If
        End Function
        Public Overrides Function DeleteHandler(ByVal deleteSource As SourceIdentifier, _
        ByVal deletedDataSet As DataSet, ByRef historyLogLevel As Integer, _
         ByRef historyLogMessage As String) As ActionOnDataDelete
            If deleteSource = SourceIdentifier.SourceIsSubscriber Then
                ' Build a line item in the audit message to log the Subscriber deletes.
                ' Note that the rowguid is the only information that is 
                ' available in the dataset.
                Dim AuditMessage As StringBuilder = New StringBuilder()
                AuditMessage.Append(String.Format("An existing order was deleted at {0}. " + _
                 "The rowguid for the order is ", subscriberName))
                AuditMessage.Append(deletedDataSet.Tables(0).Rows(0)("rowguid").ToString())

                ' Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString()
                ' Set the history log level to the default verbose level.
                historyLogLevel = 1

                ' Accept the delete and apply it to the Publisher.
                Return ActionOnDataDelete.AcceptDelete
            Else
                Return MyBase.DeleteHandler(deleteSource, deletedDataSet, _
                 historyLogLevel, historyLogMessage)
            End If
        End Function
    End Class
End Namespace

Следующий пример осуществляет регистрацию обработчика бизнес-логики на сервере распространителя и настройку существующих статей публикации на его использование.

DECLARE @publication AS sysname;
DECLARE @article AS sysname;
DECLARE @friendlyname AS sysname;
DECLARE @assembly AS nvarchar(500);
DECLARE @class AS sysname;
SET @publication = N'AdvWorksCustomers';
SET @article = N'Customers';
SET @friendlyname = N'OrderEntryLogic';
SET @assembly = N'C:\Program Files\Microsoft SQL Server\120\COM\CustomLogic.dll';
SET @class = N'Microsoft.Samples.SqlServer.BusinessLogicHandler.OrderEntryBusinessLogicHandler';

-- Register the business logic handler at the Distributor.
EXEC sys.sp_registercustomresolver 
    @article_resolver = @friendlyname,
    @resolver_clsid = NULL,
    @is_dotnet_assembly = N'true',
    @dotnet_assembly_name = @assembly,
    @dotnet_class_name = @class;

-- Add an article that uses the business logic handler
-- at the Publisher.
EXEC sp_changemergearticle 
    @publication = @publication, 
    @article = @article,
    @property = N'article_resolver', 
    @value = @friendlyname,
    @force_invalidate_snapshot = 0,
    @force_reinit_subscription = 0;
GO

При помощи объектов RMO

Создание обработчика бизнес-логики

  1. В Microsoft Visual Studio создайте проект для сборки .NET, содержащей код, реализующий обработчик бизнес-логики.

  2. Добавьте в проект ссылки на следующие пространства имен.

    Ссылка на сборку Расположение
    Microsoft.SqlServer.Replication.BusinessLogicSupport C:\Program Files\Microsoft SQL Server\nnn\COM (установка по умолчанию)
    System.Data Глобальный кэш сборок (компонент платформы .NET Framework)
    System.Data.Common Глобальный кэш сборок (компонент платформы .NET Framework)
  3. Добавьте класс, переопределяющий класс BusinessLogicModule .

  4. Реализуйте свойство HandledChangeStates , чтобы показать обрабатываемые типы изменений.

  5. Переопределите один или несколько следующих методов класса BusinessLogicModule :

    • CommitHandler — вызывается, когда изменение данных фиксируется в ходе синхронизации;

    • DeleteErrorHandler — вызывается, когда возникает ошибка при загрузке или передаче инструкции DELETE;

    • DeleteHandler — вызывается, когда передаются или загружаются инструкции DELETE;

    • InsertErrorHandler — вызывается, когда возникает ошибка при загрузке или передаче инструкции INSERT;

    • InsertHandler — вызывается, когда передаются или загружаются инструкции INSERT;

    • UpdateConflictsHandler — вызывается, когда на издателе или на подписчике возникает конфликт инструкций UPDATE;

    • UpdateDeleteConflictHandler — вызывается, когда инструкции UPDATE вызывают конфликт с инструкциями DELETE на издателе и на подписчике;

    • UpdateErrorHandler — вызывается, когда возникает ошибка в инструкции UPDATE при передаче или загрузке;

    • UpdateHandler — вызывается, когда передаются или загружаются инструкции UPDATE.

    Примечание.

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

  6. Постройте проект, чтобы создать сборку обработчика бизнес-логики.

Регистрация обработчика бизнес-логики

  1. Создайте соединение с распространителем с помощью класса ServerConnection .

  2. Создайте экземпляр класса ReplicationServer. Передайте объект ServerConnection , созданный на шаге 1.

  3. Чтобы убедиться, что сборка не была ранее зарегистрирована в качестве обработчика бизнес-логики, вызовите метод EnumBusinessLogicHandlers и просмотрите возвращенный объект ArrayList .

  4. Создайте экземпляр класса BusinessLogicHandler. Задайте следующие свойства.

    • DotNetAssemblyName — имя сборки .NET. Если сборка не найдена ни в том же каталоге, что и исполняемый файл агента слияния, ни в папке приложения, производящего синхронный запуск агента слияния, ни в глобальном кэше сборок (GAC), то ее имя должно включать полный путь. При использовании обработчика бизнес-логики во время сеанса веб-синхронизации необходимо перед именем сборки указывать ее полный путь.

    • DotNetClassName — полное имя класса, который замещает класс BusinessLogicModule и реализует обработчик бизнес-логики.

    • FriendlyName — понятное имя, используемое для доступа к обработчику бизнес-логики.

    • IsDotNetAssembly — значение true.

Развертывание обработчика бизнес-логики

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

Использование обработчика бизнес-логики со статьей в новой таблице

  1. Создайте соединение с издателем с помощью класса ServerConnection .

  2. Создайте экземпляр класса MergeArticle. Задайте следующие свойства:

    • Имя статьи для Name.

    • имя публикации в качестве значения параметра PublicationName.

    • Имя базы данных публикации в свойстве DatabaseName.

    • Понятное имя обработчика бизнес-логики (FriendlyName) в свойстве ArticleResolver.

  3. Вызовите метод Create . Дополнительные сведения см. в статье определить статью.

Использование обработчика бизнес-логики со статьей в существующей таблице

  1. Создайте соединение с издателем с помощью класса ServerConnection .

  2. Создайте экземпляр класса MergeArticle.

  3. Установите свойства Name, PublicationNameи DatabaseName .

  4. Установите полученное на шаге 1 соединение в качестве значения свойства ConnectionContext .

  5. Чтобы получить свойства объекта, вызовите метод LoadProperties . Если этот метод возвращает false, то либо на шаге 3 были неверно определены свойства статьи, либо статья не существует. Дополнительные сведения см. в статье View and Modify Article Properties.

  6. Задайте понятное имя обработчика бизнес-логики в параметре ArticleResolver. Это значение свойства FriendlyName , указанного при регистрации обработчика бизнес-логики.

Примеры (объекты RMO)

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

using System;
using System.Text;
using System.Data;
using System.Data.Common;
using Microsoft.SqlServer.Replication.BusinessLogicSupport;
using Microsoft.Samples.SqlServer.BusinessLogicHandler;

namespace Microsoft.Samples.SqlServer.BusinessLogicHandler
{
    public class OrderEntryBusinessLogicHandler :
      Microsoft.SqlServer.Replication.BusinessLogicSupport.BusinessLogicModule
    {
        // Variables to hold server names.
        private string publisherName;
        private string subscriberName;

        public OrderEntryBusinessLogicHandler()
        {
        }

        // Implement the Initialize method to get publication 
        // and subscription information.
        public override void Initialize(
            string publisher,
            string subscriber,
            string distributor,
            string publisherDB,
            string subscriberDB,
            string articleName)
        {
            // Set the Publisher and Subscriber names.
            publisherName = publisher;
            subscriberName = subscriber;
        }

        // Declare what types of row changes, conflicts, or errors to handle.
        override public ChangeStates HandledChangeStates
        {
            get
            {
                // Handle Subscriber inserts, updates and deletes.
                return ChangeStates.SubscriberInserts |
                  ChangeStates.SubscriberUpdates | ChangeStates.SubscriberDeletes;
            }
        }

        public override ActionOnDataChange InsertHandler(SourceIdentifier insertSource,
          DataSet insertedDataSet, ref DataSet customDataSet, ref int historyLogLevel,
          ref string historyLogMessage)
        {
            if (insertSource == SourceIdentifier.SourceIsSubscriber)
            {
                // Build a line item in the audit message to log the Subscriber insert.
                StringBuilder AuditMessage = new StringBuilder();
                AuditMessage.Append(String.Format("A new order was entered at {0}. " +
                  "The SalesOrderID for the order is :", subscriberName));
                AuditMessage.Append(insertedDataSet.Tables[0].Rows[0]["SalesOrderID"].ToString());
                AuditMessage.Append("The order must be shipped by :");
                AuditMessage.Append(insertedDataSet.Tables[0].Rows[0]["DueDate"].ToString());

                // Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString();
                
                // Set the history log level to the default verbose level.
                historyLogLevel = 1;

                // Accept the inserted data in the Subscriber's data set and 
                // apply it to the Publisher.
                return ActionOnDataChange.AcceptData;
            }
            else
            {
                return base.InsertHandler(insertSource, insertedDataSet, ref customDataSet,
                  ref historyLogLevel, ref historyLogMessage);
            }
        }

        public override ActionOnDataChange UpdateHandler(SourceIdentifier updateSource,
          DataSet updatedDataSet, ref DataSet customDataSet, ref int historyLogLevel,
          ref string historyLogMessage)
        {
            if (updateSource == SourceIdentifier.SourceIsPublisher)
            {
                // Build a line item in the audit message to log the Subscriber update.
                StringBuilder AuditMessage = new StringBuilder();
                AuditMessage.Append(String.Format("An existing order was updated at {0}. " +
                  "The SalesOrderID for the order is ", subscriberName));
                AuditMessage.Append(updatedDataSet.Tables[0].Rows[0]["SalesOrderID"].ToString());
                AuditMessage.Append("The order must now be shipped by :");
                AuditMessage.Append(updatedDataSet.Tables[0].Rows[0]["DueDate"].ToString());

                // Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString();
                // Set the history log level to the default verbose level.
                historyLogLevel = 1;

                // Accept the updated data in the Subscriber's data set and apply it to the Publisher.
                return ActionOnDataChange.AcceptData;
            }
            else
            {
                return base.UpdateHandler(updateSource, updatedDataSet,
                  ref customDataSet, ref historyLogLevel, ref historyLogMessage);
            }
        }

        public override ActionOnDataDelete DeleteHandler(SourceIdentifier deleteSource,
          DataSet deletedDataSet, ref int historyLogLevel, ref string historyLogMessage)
        {
            if (deleteSource == SourceIdentifier.SourceIsSubscriber)
            {
                // Build a line item in the audit message to log the Subscriber deletes.
                // Note that the rowguid is the only information that is 
                // available in the dataset.
                StringBuilder AuditMessage = new StringBuilder();
                AuditMessage.Append(String.Format("An existing order was deleted at {0}. " +
                  "The rowguid for the order is ", subscriberName));
                AuditMessage.Append(deletedDataSet.Tables[0].Rows[0]["rowguid"].ToString());

                // Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString();
                // Set the history log level to the default verbose level.
                historyLogLevel = 1;

                // Accept the delete and apply it to the Publisher.
                return ActionOnDataDelete.AcceptDelete;
            }
            else
            {
                return base.DeleteHandler(deleteSource, deletedDataSet,
                  ref historyLogLevel, ref historyLogMessage);
            }
        }
    }
}
Imports System
Imports System.Text
Imports System.Data
Imports System.Data.Common
Imports Microsoft.SqlServer.Replication.BusinessLogicSupport

Namespace Microsoft.Samples.SqlServer.BusinessLogicHandler
    Public Class OrderEntryBusinessLogicHandler
        Inherits BusinessLogicModule

        ' Variables to hold server names.
        Private publisherName As String
        Private subscriberName As String

        ' Implement the Initialize method to get publication 
        ' and subscription information.
        Public Overrides Sub Initialize( _
        ByVal publisher As String, _
        ByVal subscriber As String, _
        ByVal distributor As String, _
        ByVal publisherDB As String, _
        ByVal subscriberDB As String, _
        ByVal articleName As String _
      )
            ' Set the Publisher and Subscriber names.
            publisherName = publisher
            subscriberName = subscriber
        End Sub

        ' Declare what types of row changes, conflicts, or errors to handle.
        Public Overrides ReadOnly Property HandledChangeStates() As ChangeStates
            Get
                ' Handle Subscriber inserts, updates and deletes.
                Return (ChangeStates.SubscriberInserts Or _
                 ChangeStates.SubscriberUpdates Or ChangeStates.SubscriberDeletes)
            End Get
        End Property

        Public Overrides Function InsertHandler(ByVal insertSource As SourceIdentifier, _
        ByVal insertedDataSet As DataSet, ByRef customDataSet As DataSet, _
        ByRef historyLogLevel As Integer, ByRef historyLogMessage As String) _
        As ActionOnDataChange

            If insertSource = SourceIdentifier.SourceIsSubscriber Then
                ' Build a line item in the audit message to log the Subscriber insert.
                Dim AuditMessage As StringBuilder = New StringBuilder()
                AuditMessage.Append(String.Format("A new order was entered at {0}. " + _
                 "The SalesOrderID for the order is :", subscriberName))
                AuditMessage.Append(insertedDataSet.Tables(0).Rows(0)("SalesOrderID").ToString())
                AuditMessage.Append("The order must be shipped by :")
                AuditMessage.Append(insertedDataSet.Tables(0).Rows(0)("DueDate").ToString())

                ' Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString()

                ' Set the history log level to the default verbose level.
                historyLogLevel = 1

                ' Accept the inserted data in the Subscriber's data set and 
                ' apply it to the Publisher.
                Return ActionOnDataChange.AcceptData
            Else
                Return MyBase.InsertHandler(insertSource, insertedDataSet, customDataSet, _
                 historyLogLevel, historyLogMessage)
            End If
        End Function
        Public Overrides Function UpdateHandler(ByVal updateSource As SourceIdentifier, _
        ByVal updatedDataSet As DataSet, ByRef customDataSet As DataSet, _
        ByRef historyLogLevel As Integer, ByRef historyLogMessage As String) _
        As ActionOnDataChange

            If updateSource = SourceIdentifier.SourceIsPublisher Then
                ' Build a line item in the audit message to log the Subscriber update.
                Dim AuditMessage As StringBuilder = New StringBuilder()
                AuditMessage.Append(String.Format("An existing order was updated at {0}. " + _
                 "The SalesOrderID for the order is ", subscriberName))
                AuditMessage.Append(updatedDataSet.Tables(0).Rows(0)("SalesOrderID").ToString())
                AuditMessage.Append("The order must now be shipped by :")
                AuditMessage.Append(updatedDataSet.Tables(0).Rows(0)("DueDate").ToString())

                ' Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString()
                ' Set the history log level to the default verbose level.
                historyLogLevel = 1

                ' Accept the updated data in the Subscriber's data set and apply it to the Publisher.
                Return ActionOnDataChange.AcceptData
            Else
                Return MyBase.UpdateHandler(updateSource, updatedDataSet, _
                 customDataSet, historyLogLevel, historyLogMessage)
            End If
        End Function
        Public Overrides Function DeleteHandler(ByVal deleteSource As SourceIdentifier, _
        ByVal deletedDataSet As DataSet, ByRef historyLogLevel As Integer, _
         ByRef historyLogMessage As String) As ActionOnDataDelete
            If deleteSource = SourceIdentifier.SourceIsSubscriber Then
                ' Build a line item in the audit message to log the Subscriber deletes.
                ' Note that the rowguid is the only information that is 
                ' available in the dataset.
                Dim AuditMessage As StringBuilder = New StringBuilder()
                AuditMessage.Append(String.Format("An existing order was deleted at {0}. " + _
                 "The rowguid for the order is ", subscriberName))
                AuditMessage.Append(deletedDataSet.Tables(0).Rows(0)("rowguid").ToString())

                ' Set the reference parameter to write the line to the log file.
                historyLogMessage = AuditMessage.ToString()
                ' Set the history log level to the default verbose level.
                historyLogLevel = 1

                ' Accept the delete and apply it to the Publisher.
                Return ActionOnDataDelete.AcceptDelete
            Else
                Return MyBase.DeleteHandler(deleteSource, deletedDataSet, _
                 historyLogLevel, historyLogMessage)
            End If
        End Function
    End Class
End Namespace

В следующем примере производится регистрация обработчика бизнес-логики на распространителе.

// Specify the Distributor name and business logic properties.
string distributorName = publisherInstance;
string assemblyName = @"C:\Program Files\Microsoft SQL Server\110\COM\CustomLogic.dll";
string className = "Microsoft.Samples.SqlServer.BusinessLogicHandler.OrderEntryBusinessLogicHandler";
string friendlyName = "OrderEntryLogic";

ReplicationServer distributor;
BusinessLogicHandler customLogic;

    // Create a connection to the Distributor.
ServerConnection distributorConn = new ServerConnection(distributorName);

try
{
    // Connect to the Distributor.
    distributorConn.Connect();

    // Set the Distributor properties.
    distributor = new ReplicationServer(distributorConn);

    // Set the business logic handler properties.
    customLogic = new BusinessLogicHandler();
    customLogic.DotNetAssemblyName = assemblyName;
    customLogic.DotNetClassName = className;
    customLogic.FriendlyName = friendlyName;
    customLogic.IsDotNetAssembly = true;

    Boolean isRegistered = false;

    // Check if the business logic handler is already registered at the Distributor.
    foreach (BusinessLogicHandler registeredLogic
        in distributor.EnumBusinessLogicHandlers())
    {
        if (registeredLogic == customLogic)
        {
            isRegistered = true;
        }
    }

    // Register the custom logic.
    if (!isRegistered)
    {
        distributor.RegisterBusinessLogicHandler(customLogic);
    }
}
catch (Exception ex)
{
    // Do error handling here.
    throw new ApplicationException(string.Format(
        "The {0} assembly could not be registered.",
        assemblyName), ex);
}
finally
{
    distributorConn.Disconnect();
}
' Specify the Distributor name and business logic properties.
Dim distributorName As String = publisherInstance
Dim assemblyName As String = "C:\Program Files\Microsoft SQL Server\110\COM\CustomLogic.dll"
Dim className As String = "Microsoft.Samples.SqlServer.BusinessLogicHandler.OrderEntryBusinessLogicHandler"
Dim friendlyName As String = "OrderEntryLogic"

Dim distributor As ReplicationServer
Dim customLogic As BusinessLogicHandler

' Create a connection to the Distributor.
Dim distributorConn As ServerConnection = New ServerConnection(distributorName)

Try
    ' Connect to the Distributor.
    distributorConn.Connect()

    ' Set the Distributor properties.
    distributor = New ReplicationServer(distributorConn)

    ' Set the business logic handler properties.
    customLogic = New BusinessLogicHandler()
    customLogic.DotNetAssemblyName = assemblyName
    customLogic.DotNetClassName = className
    customLogic.FriendlyName = friendlyName
    customLogic.IsDotNetAssembly = True

    Dim isRegistered As Boolean = False

    ' Check if the business logic handler is already registered at the Distributor.
    For Each registeredLogic As BusinessLogicHandler _
    In distributor.EnumBusinessLogicHandlers
        If registeredLogic Is customLogic Then
            isRegistered = True
        End If
    Next

    ' Register the custom logic.
    If Not isRegistered Then
        distributor.RegisterBusinessLogicHandler(customLogic)
    End If
Catch ex As Exception
    ' Do error handling here.
    Throw New ApplicationException(String.Format( _
     "The {0} assembly could not be registered.", _
     assemblyName), ex)
Finally
    distributorConn.Disconnect()
End Try

В следующем примере производится настройка существующей статьи на использование обработчика бизнес-логики.

// Define the Publisher, publication, and article names.
string publisherName = publisherInstance;
string publicationName = "AdvWorksSalesOrdersMerge";
string publicationDbName = "AdventureWorks2022";
string articleName = "SalesOrderHeader";

// Set the friendly name of the business logic handler.
string customLogic = "OrderEntryLogic";

MergeArticle article = new MergeArticle();

// Create a connection to the Publisher.
ServerConnection conn = new ServerConnection(publisherName);

try
{
    // Connect to the Publisher.
    conn.Connect();

    // Set the required properties for the article.
    article.ConnectionContext = conn;
    article.Name = articleName;
    article.DatabaseName = publicationDbName;
    article.PublicationName = publicationName;

    // Load the article properties.
    if (article.LoadProperties())
    {
        article.ArticleResolver = customLogic;
    }
    else
    {
        // Throw an exception of the article does not exist.
        throw new ApplicationException(String.Format(
        "{0} is not published in {1}", articleName, publicationName));
    }
    
}
catch (Exception ex)
{
    // Do error handling here and rollback the transaction.
    throw new ApplicationException(String.Format(
        "The business logic handler {0} could not be associated with " +
        " the {1} article.",customLogic,articleName), ex);
}
finally
{
    conn.Disconnect();
}
' Define the Publisher, publication, and article names.
Dim publisherName As String = publisherInstance
Dim publicationName As String = "AdvWorksSalesOrdersMerge"
Dim publicationDbName As String = "AdventureWorks2022"
Dim articleName As String = "SalesOrderHeader"

' Set the friendly name of the business logic handler.
Dim customLogic As String = "OrderEntryLogic"

Dim article As MergeArticle = New MergeArticle()

' Create a connection to the Publisher.
Dim conn As ServerConnection = New ServerConnection(publisherName)

Try
    ' Connect to the Publisher.
    conn.Connect()

    ' Set the required properties for the article.
    article.ConnectionContext = conn
    article.Name = articleName
    article.DatabaseName = publicationDbName
    article.PublicationName = publicationName

    ' Load the article properties.
    If article.LoadProperties() Then
        article.ArticleResolver = customLogic
    Else
        ' Throw an exception of the article does not exist.
        Throw New ApplicationException(String.Format( _
         "{0} is not published in {1}", articleName, publicationName))
    End If

Catch ex As Exception
    ' Do error handling here and rollback the transaction.
    Throw New ApplicationException(String.Format( _
     "The business logic handler {0} could not be associated with " + _
     " the {1} article.", customLogic, articleName), ex)
Finally
    conn.Disconnect()
End Try