방법: 병합 아티클에 대한 비즈니스 논리 처리기 구현(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

    GAC(.NET Framework의 구성 요소)

    System.Data.Common

    GAC(.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 클래스의 인스턴스를 만듭니다. 1단계에서 만든 ServerConnection을 전달합니다.

  3. EnumBusinessLogicHandlers를 호출하고 반환되는 ArrayList 개체를 검사하여 어셈블리가 아직 비즈니스 논리 처리기로 등록되지 않았는지 확인합니다.

  4. BusinessLogicHandler 클래스의 인스턴스를 만듭니다. 다음 속성을 지정합니다.

    • DotNetAssemblyName -.NET 어셈블리의 이름. 어셈블리가 병합 에이전트 실행 파일과 같은 디렉터리, 병합 에이전트를 동기적으로 시작하는 응용 프로그램과 같은 디렉터리, 또는 GAC에 배포되지 않은 경우 어셈블리 이름에 전체 경로를 포함해야 합니다. 웹 동기화에서 비즈니스 논리 처리기를 사용하는 경우 어셈블리 이름에 전체 경로를 포함해야 합니다.

    • DotNetClassName - BusinessLogicModule을 덮어쓰고 비즈니스 논리 처리기를 구현하는 클래스의 정규화된 이름

    • FriendlyName - 비즈니스 논리 처리기에 액세스할 때 사용하는 이름

    • IsDotNetAssembly - true 값

비즈니스 논리 처리기를 배포하려면

  • 병합 에이전트가 실행되는 서버에서 비즈니스 논리 처리기가 배포자에 등록될 때 지정된 파일 위치에 어셈블리를 배포합니다. 끌어오기 구독의 경우 에이전트가 구독자에서 실행되고 밀어넣기 구독의 경우에는 배포자에서 실행됩니다. 웹 동기화를 사용할 경우 에이전트는 웹 서버에서 실행됩니다. 비즈니스 논리 처리기가 등록될 때 어셈블리 이름에 전체 경로를 포함하지 않은 경우 병합 에이전트 실행 파일과 같은 디렉터리, 병합 에이전트를 동기적으로 시작하는 응용 프로그램과 같은 디렉터리에 어셈블리를 배포합니다. 동일한 어셈블리를 사용하는 응용 프로그램이 여러 개인 경우 GAC에 어셈블리를 설치할 수 있습니다.

새 테이블 아티클에서 비즈니스 논리 처리기를 사용하려면

  1. ServerConnection 클래스를 사용하여 게시자 연결을 만듭니다.

  2. MergeArticle 클래스의 인스턴스를 만듭니다. 다음 속성을 설정합니다.

  3. Create 메서드를 호출합니다. 자세한 내용은 방법: 아티클 정의(RMO 프로그래밍)를 참조하십시오.

기존 테이블 아티클에서 비즈니스 논리 처리기를 사용하려면

  1. ServerConnection 클래스를 사용하여 게시자 연결을 만듭니다.

  2. MergeArticle 클래스의 인스턴스를 만듭니다.

  3. Name, PublicationNameDatabaseName 속성을 설정합니다.

  4. ConnectionContext 속성에 대해 1단계에서 만든 연결을 설정합니다.

  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 = "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