Cómo implementar un controlador de lógica de negocios para un artículo de mezcla (programación con RMO)

El espacio de nombres Microsoft.SqlServer.Replication.BusinessLogicSupport implementa una interfaz que le permite escribir una lógica de negocios personalizada para administrar eventos que se producen durante el proceso de sincronización de la replicación de mezcla. El proceso de la replicación puede invocar los métodos en el controlador de lógica de negocios para cada fila cambiada que se replica durante la sincronización.

El proceso general para implementar un controlador de lógica de negocios es:

  1. Cree el ensamblado del controlador de lógica de negocios.

  2. Registre el ensamblado en el distribuidor.

  3. Implemente el ensamblado en el servidor en el que se ejecuta el Agente de mezcla. Para una suscripción de extracción, el agente se ejecuta en el suscriptor y, para una suscripción de inserción, se ejecuta en el distribuidor. Al usar la sincronización web, el agente se ejecuta en el servidor web.

  4. Cree un artículo que use el controlador de lógica de negocios, o bien modifique un artículo existente para usar dicho controlador.

El controlador de lógica de negocios especificado se ejecuta para cada fila que se sincroniza. La lógica compleja y las llamadas a otras aplicaciones o servicios de red pueden afectar al rendimiento. Para obtener más información acerca de los controladores de lógica de negocios, vea Ejecutar la lógica de negocios durante la sincronización de mezcla.

Para crear un controlador de lógica de negocios

  1. En Microsoft Visual Studio, cree un nuevo proyecto para el ensamblado .NET que contiene el código que implementa el controlador de lógica de negocios.

  2. Agregue referencias al proyecto para los siguientes espacios de nombres.

    Referencia de ensamblado

    Ubicación

    Microsoft.SqlServer.Replication.BusinessLogicSupport

    C:\Archivos de programa\Microsoft SQL Server\100\COM (instalación predeterminada)

    System.Data

    GAC (componente de .NET Framework)

    System.Data.Common

    GAC (componente de .NET Framework)

  3. Agregue una clase que invalide la clase BusinessLogicModule.

  4. Implemente la propiedad HandledChangeStates para indicar los tipos de cambios que se administran.

  5. Invalide uno o varios de los métodos siguientes de la clase BusinessLogicModule:

    • CommitHandler- se invoca cuando se confirma un cambio de datos durante la sincronización.

    • DeleteErrorHandler- se invoca si se produce un error mientras una instrucción DELETE se carga o se descarga.

    • DeleteHandler- se invoca cuando las instrucciones DELETE se cargan o se descargan.

    • InsertErrorHandler- se invoca si se produce un error cuando una instrucción INSERT se carga o se descarga.

    • InsertHandler- se invoca cuando las instrucciones INSERT se cargan o se descargan.

    • UpdateConflictsHandler- se invoca cuando se producen instrucciones UPDATE conflictivas en el publicador y en el suscriptor.

    • UpdateDeleteConflictHandler- se invoca cuando las instrucciones UPDATE entran en conflicto con las instrucciones DELETE en el publicador y en el suscriptor.

    • UpdateErrorHandler- se invoca si se produce un error cuando una instrucción UPDATE se carga o se descarga.

    • UpdateHandler- se invoca cuando las instrucciones UPDATE se cargan o se descargan.

    [!NOTA]

    La resolución predeterminada del artículo administra los conflictos de artículo que no controle explícitamente la lógica de negocios personalizada.

  6. Genere el proyecto para crear el ensamblado del controlador de lógica de negocios.

Para registrar un controlador de lógica de negocios

  1. Cree una conexión al distribuidor mediante la clase ServerConnection.

  2. Cree una instancia de la clase ReplicationServer. Pase el objeto ServerConnection del paso 1.

  3. Llame a EnumBusinessLogicHandlers y compruebe el objeto ArrayList devuelto para asegurarse de que el ensamblado aún no se ha registrado como un controlador de lógica de negocios.

  4. Cree una instancia de la clase BusinessLogicHandler. Especifique las propiedades siguientes:

    • DotNetAssemblyName - nombre del ensamblado .NET. Si el ensamblado no está implementado en el mismo directorio que el ejecutable del Agente de mezcla, en el mismo directorio que la aplicación que inicia de forma sincrónica dicho agente o en la GAC, debe incluir la ruta de acceso completa con el nombre de ensamblado. Debe incluir la ruta de acceso completa con el nombre de ensamblado al usar un controlador de lógica de negocios con la sincronización web.

    • DotNetClassName - el nombre completo de la clase que invalida BusinessLogicModule e implementa el controlador de lógica de negocios.

    • FriendlyName- nombre descriptivo que se usa al obtener acceso al controlador de lógica de negocios.

    • IsDotNetAssembly - un valor de true.

Para implementar un controlador de lógica de negocios

  • Implemente el ensamblado en el servidor donde se ejecuta el Agente de mezcla, en la ubicación del archivo especificada cuando el controlador de lógica de negocios se registró en el distribuidor. Para una suscripción de extracción, el agente se ejecuta en el suscriptor y, para una suscripción de inserción, se ejecuta en el distribuidor. Al usar la sincronización web, el agente se ejecuta en el servidor web. Si la ruta de acceso completa no estuviera incluida con el nombre de ensamblado cuando se registró el controlador de lógica de negocios, implemente el ensamblado en el mismo directorio como el ejecutable del Agente de mezcla, en el mismo directorio que la aplicación que inicia sincrónicamente dicho agente. Puede instalar el ensamblado en la GAC si hay varias aplicaciones que usen el mismo ensamblado.

Para usar un controlador de lógica de negocios con un nuevo artículo de tabla

  1. Cree una conexión al publicador mediante la clase ServerConnection.

  2. Cree una instancia de la clase MergeArticle. Establezca las siguientes propiedades:

  3. Llame al método Create. Para obtener más información, vea Cómo definir un artículo (programación con RMO).

Para usar un controlador de lógica de negocios con un artículo de tabla existente

  1. Cree una conexión al publicador mediante la clase ServerConnection.

  2. Cree una instancia de la clase MergeArticle.

  3. Establezca las propiedades Name, PublicationName y DatabaseName.

  4. Establezca la conexión del paso 1 para la propiedad ConnectionContext.

  5. Llame al método LoadProperties para obtener las propiedades del objeto. Si este método devuelve false, se definieron incorrectamente las propiedades de artículo en el paso 3, o bien el artículo no existe. Para obtener más información, vea Cómo ver y modificar propiedades de artículo (programación con RMO).

  6. Establezca el nombre descriptivo del controlador de negocios para ArticleResolver. Este es el valor de la propiedad FriendlyName especificado al registrar el controlador de lógica de negocios.

Ejemplo

En este ejemplo se muestra un controlador de lógica de negocios que registra información acerca de inserciones, actualizaciones y eliminaciones en el suscriptor.

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

Este ejemplo registra el controlador de lógica de negocios en el distribuidor.

          // 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

Este ejemplo cambia un artículo existente para usar el controlador de lógica de negocios.

           // Define the Publisher, publication, and article names.
            string publisherName = publisherInstance;
            string publicationName = "AdvWorksSalesOrdersMerge";
            string publicationDbName = "AdventureWorks";
            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 = "AdventureWorks"
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