방법: 병합 아티클에 대한 비즈니스 논리 처리기 구현(복제 프로그래밍)

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. 프로젝트를 빌드하여 비즈니스 논리 처리기 어셈블리를 만듭니다.

  7. 어셈블리를 병합 에이전트 실행 파일(replmerg.exe)이 있는 디렉터리(기본 설치의 경우 C:\Program Files\Microsoft SQL Server\100\COM)에 배포하거나 .NET GAC(전역 어셈블리 캐시)에 설치합니다. 병합 에이전트 이외의 다른 응용 프로그램에서 어셈블리에 액세스해야 하는 경우 어셈블리를 GAC에만 설치해야 합니다. .NET Framework SDK에 제공되는 전역 어셈블리 캐시 도구(Gacutil.exe)를 사용하여 GAC에 어셈블리를 설치할 수 있습니다.

    [!참고]

    비즈니스 논리 처리기는 병합 에이전트가 실행되는 모든 서버에 배포해야 합니다. 여기에는 웹 동기화를 사용할 때 replisapi.dll을 호스팅하는 IIS 서버도 포함됩니다.

비즈니스 논리 처리기를 등록하려면

  1. 게시자에서 sp_enumcustomresolvers(Transact-SQL)를 실행하여 어셈블리가 이미 비즈니스 논리 처리기로 등록되어 있지 않은지 확인합니다.

  2. 배포자에서 sp_registercustomresolver(Transact-SQL)를 실행하고 @article_resolver에 비즈니스 논리 처리기의 이름을, @is_dotnet_assembly에 true 값을, @dotnet_assembly_name에 어셈블리의 이름을, @dotnet_class_nameBusinessLogicModule을 덮어쓰는 클래스의 정규화된 이름을 지정합니다.

    [!참고]

    어셈블리가 병합 에이전트 실행 파일과 같은 디렉터리, 병합 에이전트를 동기적으로 시작하는 응용 프로그램과 같은 디렉터리, 또는 GAC(전역 어셈블리 캐시)에 배포되지 않은 경우 @dotnet_assembly_name에 어셈블리 이름을 포함하는 전체 경로를 지정해야 합니다. 웹 동기화를 사용하는 경우 웹 서버에서 어셈블리의 위치를 지정해야 합니다.

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

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

  • sp_changemergearticle(Transact-SQL)을 실행하고 @publication, @article을 지정하고 @propertyarticle_resolver 값을, @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\100\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