定义合并表项目间的逻辑记录关系

适用于SQL Server

本主题介绍如何使用 SQL Server Management Studio、Transact-SQL 或复制管理对象 (RMO) 在 SQL Server 中定义合并表项目之间的逻辑记录关系。

通过使用合并复制,您可以定义不同表中的相关行之间的关系。 然后就可以在同步过程中将这些行作为一个事务单元进行处理。 无论两个项目之间是否存在联接筛选器关系,都可以在这两个项目之间定义逻辑记录。 有关详细信息,请参阅通过逻辑记录对相关行的更改进行分组

注意

在 SQL Server的未来版本中将删除此功能。 请避免在新的开发工作中使用该功能,并着手修改当前还在使用该功能的应用程序。

本主题内容

开始之前

限制和局限

  • 如果在初始化对发布的订阅后添加、修改或删除逻辑记录,必须在更改后生成新的快照并重新初始化所有订阅。 有关属性更改要求的详细信息,请参阅更改发布和项目属性

使用 SQL Server Management Studio

可在“添加联接”对话框(可在新建发布向导和“发布属性 - <发布>”对话框中获取)中定义逻辑记录。 有关如何使用该向导和如何访问该对话框的详细信息,请参阅创建发布查看和修改发布属性

仅当逻辑记录应用于合并发布中的联接筛选器且发布遵循使用预计算分区的要求时,才可以在 “添加联接” 对话框中定义逻辑记录。 若要定义不应用于联接筛选器的逻辑记录并在逻辑记录级设置冲突检测和解决方法,必须使用存储过程。

定义逻辑记录关系

  1. 在新建发布向导的“筛选表行”页或“发布属性 - < 发布 >”对话框的“筛选行”页上,在“筛选的表”窗格中选择行筛选器。

    逻辑记录关系与扩展行筛选器的联接筛选器相关联。 因此,必须定义一个行筛选器,才能用联接来扩展该筛选器并应用逻辑记录关系。 定义一个联接筛选器后,可使用其他联接筛选器来扩展此联接筛选器。 有关定义联接筛选器的详细信息,请参阅 定义和修改合并项目间的联接筛选器

  2. 单击 “添加”,再单击 “添加联接以扩展所选筛选器”

  3. “添加联接” 对话框中定义一个联接筛选器,然后选中 “逻辑记录”复选框。

  4. 如果处于“发布属性 - <发布>”对话框中,请单击“确定”以保存并关闭该对话框。

删除逻辑记录关系

  • 只删除逻辑记录关系,或者删除逻辑记录关系及其相关联的联接筛选器。

    只删除逻辑记录关系:

    1. 在新建发布向导的“筛选行”页或“发布属性 - <发布>”对话框的“筛选行”页上,在“筛选的表”窗格中选择与逻辑记录关系关联的联接筛选器,然后单击“编辑”

    2. “编辑联接” 对话框中,清除 “逻辑记录”复选框。

    3. 选择“确定”

    删除逻辑记录关系及其相关联的联接筛选器:

    • 在新建发布向导的“筛选行”页或“发布属性 - <发布>”对话框上,在“筛选的表”窗格中选择筛选器,然后单击“删除”。 如果删除的联接筛选器自身是由其他联接扩展而成的,则也将删除那些联接。

“使用 Transact-SQL”

您可以使用复制存储过程以编程方式指定项目之间的逻辑记录关系。

在没有关联的联接筛选器的情况下定义逻辑记录关系

  1. 如果发布包含已筛选的任何项目,则执行 sp_helpmergepublication,并注意结果集中的 use_partition_groups 值。

    • 如果值为 1,则已使用预计算分区。

    • 如果值为 0,请在发布服务器上对发布数据库执行 sp_changemergepublication 。 将 @property 的值指定为 use_partition_groups 并将 @value 的值指定为 true

      注意

      如果发布不支持预计算分区,则无法使用逻辑记录。 有关详细信息,请参阅使用预计算分区优化参数化筛选器性能主题中的“使用预计算分区的要求”。

    • 如果值为 NULL,则需要运行快照代理以生成发布的初始快照。

  2. 如果将包含逻辑记录的项目不存在,请在发布服务器上对发布数据库执行 sp_addmergearticle 。 为逻辑记录指定以下冲突检测和解决选项中的一项:

    • 若要检测并解决发生在逻辑记录的相关行之间的冲突,请将 @logical_record_level_conflict_detection@logical_record_level_conflict_resolution 的值指定为 true

    • 若要使用标准行级或列级冲突检测和解决方法,请将 @logical_record_level_conflict_detection@logical_record_level_conflict_resolution 的值指定为 false,此为默认值。

  3. 为每个将包含逻辑记录的项目重复步骤 2。 您必须为逻辑记录中的每个项目使用相同的冲突检测和解决选项。 有关详细信息,请参阅 检测并解决逻辑记录中的冲突

  4. 在发布服务器上,对发布数据库执行 sp_addmergefilter。 指定 @publication,将 @article指定为该关系中一个项目的名称,将 @join_articlename指定为第二个项目的名称,将 @filtername指定为该关系的名称,将 @join_filterclause指定为定义两个项目之间关系的子句,将 @join_unique_key 指定为联接的类型,并将 @filter_type指定为以下值之一:

    • 2 - 定义逻辑关系。

    • 3 - 定义与联接筛选器的逻辑关系。

    注意

    如果未使用联接筛选器,则两个项目之间的关系方向并不重要。

  5. 为发布中剩余的每个逻辑记录关系重复步骤 2。

为逻辑记录更改冲突检测和解决方法

  1. 检测和解决发生在逻辑记录中相关行之间的冲突:

    • 在发布服务器上,对发布数据库执行 sp_changemergearticle。 将 @property 的值指定为 logical_record_level_conflict_detection ,并将 @value 的值指定为 true。 将 @force_invalidate_snapshot@force_reinit_subscription 的值指定为 1

    • 在发布服务器上,对发布数据库执行 sp_changemergearticle。 将 @property 的值指定为 logical_record_level_conflict_resolution ,并将 @value 的值指定为 true。 将 @force_invalidate_snapshot@force_reinit_subscription 的值指定为 1

  2. 使用标准行级或列级冲突检测和解决方法:

    • 在发布服务器上,对发布数据库执行 sp_changemergearticle。 将 @property 的值指定为 logical_record_level_conflict_detection ,并将 @value 的值指定为 false。 将 @force_invalidate_snapshot@force_reinit_subscription 的值指定为 1

    • 在发布服务器上,对发布数据库执行 sp_changemergearticle。 将 @property 的值指定为 logical_record_level_conflict_resolution ,并将 @value 的值指定为 false。 将 @force_invalidate_snapshot@force_reinit_subscription 的值指定为 1

删除逻辑记录关系

  1. 在发布服务器上,对发布数据库执行以下查询,以返回有关为指定的发布定义的所有逻辑记录关系的信息:

    SELECT f.* FROM sysmergesubsetfilters AS f 
    INNER JOIN sysmergepublications AS p
    ON f.pubid = p.pubid WHERE p.[name] = @publication;
    

    注意在结果集 filtername 列中的被删除逻辑记录关系的名称。

    注意

    该查询返回的信息与 sp_helpmergefilter相同;然而,该系统存储过程仅返回有关逻辑记录关系(也是联接筛选器)的信息。

  2. 在发布服务器上,对发布数据库执行 sp_dropmergefilter。 指定 @publication,为 @article指定该关系中其中一个项目的名称,并为 @filtername指定步骤 1 中的关系的名称。

示例 (Transact-SQL)

此示例对现有发布启用预计算分区,并创建包含 SalesOrderHeaderSalesOrderDetail 表的两个新项目的逻辑记录。

-- Remove ON DELETE CASCADE from FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID;
-- logical records cannot be used with ON DELETE CASCADE. 
IF EXISTS (SELECT * FROM sys.objects 
WHERE name = 'FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID')
BEGIN
    ALTER TABLE [Sales].[SalesOrderDetail] 
    DROP CONSTRAINT [FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID] 
END

ALTER TABLE [Sales].[SalesOrderDetail]  
WITH CHECK ADD CONSTRAINT [FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID] 
FOREIGN KEY([SalesOrderID])
REFERENCES [Sales].[SalesOrderHeader] ([SalesOrderID])
GO

DECLARE @publication    AS sysname;
DECLARE @table1 AS sysname;
DECLARE @table2 AS sysname;
DECLARE @table3 AS sysname;
DECLARE @salesschema AS sysname;
DECLARE @hrschema AS sysname;
DECLARE @filterclause AS nvarchar(1000);
DECLARE @partitionoption AS bit;
SET @publication = N'AdvWorksSalesOrdersMerge'; 
SET @table1 = N'SalesOrderDetail'; 
SET @table2 = N'SalesOrderHeader'; 
SET @salesschema = N'Sales';
SET @hrschema = N'HumanResources';
SET @filterclause = N'Employee.LoginID = HOST_NAME()';

-- Ensure that the publication uses precomputed partitions.
SET @partitionoption = (SELECT [use_partition_groups] FROM sysmergepublications 
    WHERE [name] = @publication);
IF @partitionoption <> 1
BEGIN
    EXEC sp_changemergepublication 
        @publication = @publication, 
        @property = N'use_partition_groups', 
        @value = 'true',
        @force_invalidate_snapshot = 1;
END  

-- Add a filtered article for the Employee table.
EXEC sp_addmergearticle 
  @publication = @publication, 
  @article = @table1, 
  @source_object = @table1, 
  @type = N'table', 
  @source_owner = @hrschema,
  @schema_option = 0x0004CF1,
  @description = N'article for the Employee table',
  @subset_filterclause = @filterclause;

-- Add an article for the SalesOrderHeader table.
EXEC sp_addmergearticle 
  @publication = @publication, 
  @article = @table2, 
  @source_object = @table2, 
  @type = N'table', 
  @source_owner = @salesschema,
  @schema_option = 0x0034EF1,
  @description = N'article for the SalesOrderHeader table';

-- Add an article for the SalesOrderDetail table.
EXEC sp_addmergearticle 
  @publication = @publication, 
  @article = @table3, 
  @source_object = @table3, 
  @source_owner = @salesschema,
  @description = 'article for the SalesOrderDetail table', 
  @identityrangemanagementoption = N'auto', 
  @pub_identity_range = 100000, 
  @identity_range = 100, 
  @threshold = 80;

-- Add a merge join filter between Employee and SalesOrderHeader.
EXEC sp_addmergefilter 
  @publication = @publication, 
  @article = @table2, 
  @filtername = N'SalesOrderHeader_Employee', 
  @join_articlename = @table1, 
  @join_filterclause = N'Employee.EmployeeID = SalesOrderHeader.SalesPersonID', 
  @join_unique_key = 1, 
  @filter_type = 1, 
  @force_invalidate_snapshot = 1, 
  @force_reinit_subscription = 1;

-- Create a logical record relationship that is also a merge join 
-- filter between SalesOrderHeader and SalesOrderDetail.
EXEC sp_addmergefilter 
  @publication = @publication, 
  @article = @table3, 
  @filtername = N'LogicalRecord_SalesOrderHeader_SalesOrderDetail', 
  @join_articlename = @table2, 
  @join_filterclause = N'[SalesOrderHeader].[SalesOrderID] = [SalesOrderDetail].[SalesOrderID]', 
  @join_unique_key = 1, 
  @filter_type = 3, 
  @force_invalidate_snapshot = 1, 
  @force_reinit_subscription = 1;
GO

使用复制管理对象 (RMO)

注意

利用合并复制,您可以指定在逻辑记录级跟踪和解决冲突,但使用 RMO 却无法设置这些选项。

在没有关联的联接筛选器的情况下定义逻辑记录关系

  1. 使用 ServerConnection 类创建与发布服务器的连接。

  2. 创建 MergePublication 类的实例,为发布设置 NameDatabaseName 属性并将 ConnectionContext 属性设置为在步骤 1 中创建的连接。

  3. 调用 LoadProperties 方法获取该对象的属性。 如果此方法返回 false,则说明步骤 2 中的发布属性定义不正确,或者此发布不存在。

  4. 如果 PartitionGroupsOption 属性设置为 False,请将其设为 True

  5. 如果要构成逻辑记录的项目不存在,请创建 MergeArticle 类的一个实例,然后设置以下属性:

    • Name设置为项目的名称。

    • PublicationName

    • (可选)如果对项目进行水平筛选,则将 FilterClause 属性指定为行筛选器子句。 使用此属性可指定静态行筛选器或参数化行筛选器。 有关详细信息,请参阅 参数化行筛选器

    有关详细信息,请参阅 定义项目

  6. 调用 Create 方法。

  7. 对构成逻辑记录的每个项目,重复执行步骤 5 和 6。

  8. 创建 MergeJoinFilter 类的一个实例以定义项目之间的逻辑记录关系。 然后,设置以下属性:

  9. 对表示此关系中的子项目的对象调用 AddMergeJoinFilter 方法。 传递步骤 8 中的 MergeJoinFilter 对象以定义此关系。

  10. 对发布中的其余每个逻辑记录关系重复执行步骤 8 和 9。

示例 (RMO)

此示例为 SalesOrderHeaderSalesOrderDetail 表创建一个由两个新项目构成的逻辑记录。

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

           // Specify article names.
           string articleName1 = "SalesOrderHeader";
           string articleName2 = "SalesOrderDetail";
           
           // Specify logical record information.
           string lrName = "SalesOrderHeader_SalesOrderDetail";
           string lrClause = "[SalesOrderHeader].[SalesOrderID] = "
               + "[SalesOrderDetail].[SalesOrderID]";

           string schema = "Sales";

           MergeArticle article1 = new MergeArticle();
           MergeArticle article2 = new MergeArticle();
           MergeJoinFilter lr = new MergeJoinFilter();
           MergePublication publication = new MergePublication();

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

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

               // Verify that the publication uses precomputed partitions.
               publication.Name = publicationName;
               publication.DatabaseName = publicationDbName;
               publication.ConnectionContext = conn;

               // If we can't get the properties for this merge publication, then throw an application exception.
               if (publication.LoadProperties())
               {
                   // If precomputed partitions is disabled, enable it.
                   if (publication.PartitionGroupsOption == PartitionGroupsOption.False)
                   {
                       publication.PartitionGroupsOption = PartitionGroupsOption.True;
                   }
               }
               else
               {
                   throw new ApplicationException(String.Format(
                       "Settings could not be retrieved for the publication. " +
                       "Ensure that the publication {0} exists on {1}.",
                       publicationName, publisherName));
               }

               // Set the required properties for the PurchaseOrderHeader article.
               article1.ConnectionContext = conn;
               article1.Name = articleName1;
               article1.DatabaseName = publicationDbName;
               article1.SourceObjectName = articleName1;
               article1.SourceObjectOwner = schema;
               article1.PublicationName = publicationName;
               article1.Type = ArticleOptions.TableBased;

               // Set the required properties for the SalesOrderDetail article.
               article2.ConnectionContext = conn;
               article2.Name = articleName2;
               article2.DatabaseName = publicationDbName;
               article2.SourceObjectName = articleName2;
               article2.SourceObjectOwner = schema;
               article2.PublicationName = publicationName;
               article2.Type = ArticleOptions.TableBased;

               if (!article1.IsExistingObject) article1.Create();
               if (!article2.IsExistingObject) article2.Create();

               // Define a logical record relationship between 
               // PurchaseOrderHeader and PurchaseOrderDetail. 

               // Parent article.
               lr.JoinArticleName = articleName1;
               
               // Child article.
               lr.ArticleName = articleName2;
               lr.FilterName = lrName;
               lr.JoinUniqueKey = true;
               lr.FilterTypes = FilterTypes.LogicalRecordLink;
               lr.JoinFilterClause = lrClause;

               // Add the logical record definition to the parent article.
               article1.AddMergeJoinFilter(lr);
           }
           catch (Exception ex)
           {
               // Do error handling here and rollback the transaction.
               throw new ApplicationException(
                   "The filtered articles could not be created", ex);
           }
           finally
           {
               conn.Disconnect();
           }
' Define the Publisher and publication names.
Dim publisherName As String = publisherInstance
Dim publicationName As String = "AdvWorksSalesOrdersMerge"
Dim publicationDbName As String = "AdventureWorks2022"

' Specify article names.
Dim articleName1 As String = "SalesOrderHeader"
Dim articleName2 As String = "SalesOrderDetail"

' Specify logical record information.
Dim lrName As String = "SalesOrderHeader_SalesOrderDetail"
Dim lrClause As String = "[SalesOrderHeader].[SalesOrderID] = " _
        & "[SalesOrderDetail].[SalesOrderID]"

Dim schema As String = "Sales"

Dim article1 As MergeArticle = New MergeArticle()
Dim article2 As MergeArticle = New MergeArticle()
Dim lr As MergeJoinFilter = New MergeJoinFilter()
Dim publication As MergePublication = New MergePublication()

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

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

    ' Verify that the publication uses precomputed partitions.
    publication.Name = publicationName
    publication.DatabaseName = publicationDbName
    publication.ConnectionContext = conn

    ' If we can't get the properties for this merge publication, then throw an application exception.
    If publication.LoadProperties() Then
        ' If precomputed partitions is disabled, enable it.
        If publication.PartitionGroupsOption = PartitionGroupsOption.False Then
            publication.PartitionGroupsOption = PartitionGroupsOption.True
        End If
    Else
        Throw New ApplicationException(String.Format( _
            "Settings could not be retrieved for the publication. " _
            & "Ensure that the publication {0} exists on {1}.", _
            publicationName, publisherName))
    End If

    ' Set the required properties for the SalesOrderHeader article.
    article1.ConnectionContext = conn
    article1.Name = articleName1
    article1.DatabaseName = publicationDbName
    article1.SourceObjectName = articleName1
    article1.SourceObjectOwner = schema
    article1.PublicationName = publicationName
    article1.Type = ArticleOptions.TableBased

    ' Set the required properties for the SalesOrderDetail article.
    article2.ConnectionContext = conn
    article2.Name = articleName2
    article2.DatabaseName = publicationDbName
    article2.SourceObjectName = articleName2
    article2.SourceObjectOwner = schema
    article2.PublicationName = publicationName
    article2.Type = ArticleOptions.TableBased

    If Not article1.IsExistingObject Then
        article1.Create()
    End If
    If Not article2.IsExistingObject Then
        article2.Create()
    End If

    ' Define a logical record relationship between 
    ' SalesOrderHeader and SalesOrderDetail. 

    ' Parent article.
    lr.JoinArticleName = articleName1
    ' Child article.
    lr.ArticleName = articleName2
    lr.FilterName = lrName
    lr.JoinUniqueKey = True
    lr.FilterTypes = FilterTypes.LogicalRecordLink
    lr.JoinFilterClause = lrClause

    ' Add the logical record definition to the parent article.
    article1.AddMergeJoinFilter(lr)
Catch ex As Exception
    ' Do error handling here and rollback the transaction.
    Throw New ApplicationException( _
            "The filtered articles could not be created", ex)
Finally
    conn.Disconnect()
End Try