Поделиться через


Как реализовать обработчик бизнес-логики для статьи публикации слиянием (программирование объектов 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\100\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. Создайте соединение с издателем с помощью класса ServerConnection.

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

    • Имя статьи в свойстве Name.

    • Имя публикации в свойстве PublicationName.

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

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

  3. Вызовите метод Create. Дополнительные сведения см. в разделе Как определить статью (программирование объектов RMO).

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

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

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

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

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

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

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

Пример

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

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\100\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\100\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 = "AdventureWorks2008R2";
            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 = "AdventureWorks2008R2"
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