使用批量操作消息

若要在 Microsoft Dataverse 表的多个行上执行操作时获得最佳性能,请使用以下其中一种批量操作消息:

  • CreateMultiple:在单个请求中创建同一类型的多个记录。
  • UpdateMultiple:在单个请求中更新同一类型的多个记录。
  • UpsertMultiple:在单个请求中创建或更新同一类型的多个记录。
  • DeleteMultiple:仅适用于弹性表。 在单个请求中删除同一类型的多个记录。

注释

有关执行批量操作时的选项(例如,何时使用这些 API 而不是批处理 API)的指南,请参阅 ExecuteMultiple。

例子

以下代码示例演示如何使用批量操作消息。 可以从 github.com/microsoft/PowerApps-Samples 下载示例:

CreateMultiple

在单个请求中创建同一类型的多个记录。

使用 CreateMultipleRequest 类。

/// <summary>
/// Demonstrates the use of the CreateMultiple Message
/// </summary>
/// <param name="service">The authenticated IOrganizationService instance.</param>
/// <param name="recordsToCreate">A list of records of the same table to create.</param>
/// <returns>The Guid values of the records created.</returns>
static Guid[] CreateMultipleExample(IOrganizationService service,
    List<Entity> recordsToCreate)
{

    // Create an EntityCollection populated with the list of entities.
    EntityCollection entities = new(recordsToCreate)
    {
        // All the records must be for the same table.
        EntityName = recordsToCreate[0].LogicalName
    };

    // Instantiate CreateMultipleRequest
    CreateMultipleRequest createMultipleRequest = new()
    {
        Targets = entities,
    };

    // Send the request
    CreateMultipleResponse createMultipleResponse =
                (CreateMultipleResponse)service.Execute(createMultipleRequest);

    // Return the Ids of the records created.
    return createMultipleResponse.Ids;
}

UpdateMultiple

在单个请求中更新同一类型的多个记录。

就像更新单个记录时一样,使用 UpdateMultiple 发送的数据必须仅包含要更改的值。 有关详细信息,请参阅使用 SDK for .NET 更新记录 和 使用 Web API 更新记录。

使用 UpdateMultipleRequest 类。

/// <summary>
/// Demonstrates the use of the UpdateMultiple message.
/// </summary>
/// <param name="service">The authenticated IOrganizationService instance.</param>
/// <param name="recordsToUpdate">A list of records to create.</param>
static void UpdateMultipleExample(IOrganizationService service, List<Entity> recordsToUpdate) {
    // Create an EntityCollection populated with the list of entities.
    EntityCollection entities = new(recordsToUpdate)
    {
        // All the records must be for the same table.
        EntityName = recordsToUpdate[0].LogicalName
    };

    // Use UpdateMultipleRequest
    UpdateMultipleRequest updateMultipleRequest = new()
    {
        Targets = entities,
    };

    service.Execute(updateMultipleRequest);
}

UpdateMultiple Targets 参数中的重复记录

UpdateMultiple 不支持有效负载中具有相同主键或备用键值的多个记录。 如果参数中的 Targets 多个记录由主键或备用键唯一标识,则仅对第一条记录执行该作。 该操作会忽略在有效负载中键值相同的后续记录。 此行为不同于 UpsertMultiple.

UpsertMultiple

当不知道表是否存在于 Dataverse 中时,用于 Upsert 将数据与外部源集成。 Upsert 操作通常依赖备用键来标识记录。 使用 UpsertMultiple 批量地执行 Upsert 操作。

使用 UpsertMultipleRequest 类。

此静态 UpsertMultipleExample 方法取决于一个 samples_bankaccount 表,该表具有一个名为“备用键”的 samples_accountname 字符串列。 它还具有一个名为 samples_description 的字符串列。 此代码使用 Entity 构造函数来设置 keyName 和 keyValue 来指定备用键值。

/// <summary>
/// Demonstrates using UpsertMultiple with alternate key values
/// </summary>
/// <param name="service">The authenticated IOrganizationService instance</param>
static void UpsertMultipleExample(IOrganizationService service)
{
    var tableLogicalName = "samples_bankaccount";
    // samples_accountname string column is configued as an alternate key
    // for the samples_bankaccount table
    var altKeyColumnLogicalName = "samples_accountname";

    // Create one record to update with upsert
    service.Create(new Entity(tableLogicalName)
    {
        Attributes =
        {
            {altKeyColumnLogicalName, "Record For Update"},
            {"samples_description","A record to update using Upsert" }
        }
    });

    // Using the Entity constructor to specify alternate key
    Entity toUpdate = new(
            entityName: tableLogicalName,
            keyName: altKeyColumnLogicalName,
            // Same alternate key value as created record.
            keyValue: "Record For Update");
    toUpdate["samples_description"] = "Updated using Upsert";

    Entity toCreate = new(
        entityName: tableLogicalName,
        keyName: altKeyColumnLogicalName,
        keyValue: "Record For Create");
    toCreate["samples_description"] = "A record to create using Upsert";

    // Add the records to a collection
    EntityCollection records = new()
    {
        EntityName = tableLogicalName,
        Entities = { toUpdate, toCreate }
    };

    // Send the request
    UpsertMultipleRequest request = new()
    {
        Targets = records
    };

    var response = (UpsertMultipleResponse)service.Execute(request);

    // Process the responses:
    foreach (UpsertResponse item in response.Results)
    {
        Console.WriteLine($"Record {(item.RecordCreated ? "Created" : "Updated")}");
    }
}

Output:

Record Updated
Record Created

此示例中创建或更新记录取决于是否存在具有匹配 sample_keyattribute 值的记录。 不返回任何数据以指示是创建记录还是更新记录。

SDK 示例

在 Sample:SDK for .NET 使用批量操作中,查找 UpsertMultiple 项目。

可用性

支持 UpsertMultiple 和 CreateMultiple 的表可使用 UpdateMultiple。 此支持包括所有弹性表。 在“使用标准表的可用性”中找到的查询不会返回UpsertMultiple的结果,但你可以使用它们来检测某个表是否同时支持CreateMultiple和UpdateMultiple。

这些查询不会为 UpsertMultiple 消息返回结果。 支持CreateMultiple和UpdateMultiple的表格也支持UpsertMultiple。

UpsertMultiple Targets 参数中的重复记录

UpsertMultiple 不支持有效负载中具有相同主键或备用键值的多个记录。 当 Targets 参数中的多个记录被唯一地由备用键或主键标识时,UpsertMultiple 返回错误。 此行为不同于 UpdateMultiple.

删除多个

使用单个请求删除弹性表中的多行数据。

使用 OrganizationRequest 类 ,因为 SDK for .NET 没有类 DeleteMultipleRequest 。 有关详细信息,请参阅 在 .NET SDK 中使用消息。

以下 DeleteMultipleExample 静态方法使用 DeleteMultiple 消息与 OrganizationRequest 类通过替代键包含 partitionid 唯一标识行,从而从 contoso_SensorData 弹性表中删除多行。

public static void DeleteMultipleExample(IOrganizationService service)
{
    string tableLogicalName = "contoso_sensordata";

    List<EntityReference> entityReferences = new() {
        {
            new EntityReference(logicalName: tableLogicalName,
               keyAttributeCollection: new KeyAttributeCollection
               {
                  { "contoso_sensordataid", "3f56361a-b210-4a74-8708-3c664038fa41" },
                  { "partitionid", "deviceid-001" }
               })
        },
        { new EntityReference(logicalName: tableLogicalName,
               keyAttributeCollection: new KeyAttributeCollection
               {
                  { "contoso_sensordataid", "e682715b-1bba-415e-b2bc-de9327308423" },
                  { "partitionid", "deviceid-002" }
               })
        }
    };

    OrganizationRequest request = new(requestName:"DeleteMultiple")
    {
        Parameters = {
            {"Targets", new EntityReferenceCollection(entityReferences)}
        }
    };

    service.Execute(request);
}

DeleteMultiple 可用性

DeleteMultiple 仅支持弹性表。 弹性表不支持 表关系级联行为,这可能会导致删除操作的不可预见的执行时间。 如果在 DeleteMultiple 标准表上使用,则会收到错误: DeleteMultiple has not yet been implemented.

删除多个示例

可以在 github.com/microsoft/PowerApps-Samples 中找到 GitHub 上的示例代码:

标准表和弹性表的使用情况

使用批量操作信息时,标准表和弹性表的性能都得到了显著的提升,但需要以不同的方式使用这些表。 下表总结了它们之间的不同。

差额 标准 Elastic
记录数 使用大量记录可以使操作更高效。 记录数没有限制,但存在消息大小和时间限制。 一次发送 100 到 1,000 条记录。 一次发送 100 条记录。
关于错误行为 所有操作在错误时均回滚。 部分成功是可能的。
可用性 并非所有标准表都支持这些消息。 消息适用于所有弹性表。
删除多个 暂无 请改用 SDK BulkDeleteRequest 类 或 Web API BulkDelete 操作。 了解如何批量删除数据。 使用 SDK DeleteMultipleRequest 类 或 Web API DeleteMultiple 操作

标准表和弹性表使用情况不同,因为标准表使用 Azure SQL 并支持事务。 弹性表使用Azure Cosmos DB,它不支持事务,但处理大量高吞吐量的数据,且延迟较低。 以下部分提供了更多详细信息。 详细了解弹性表上的批量操作。

记录数

应随每个请求一起包含的记录数取决于使用的是标准表还是弹性表。

小窍门

在 并行发送批量作消息时,标准和弹性表的吞吐量都更高。

标准表记录数

标准表上的批量操作经过优化,可在单个事务中对多行执行。 随着每个请求的操作量增加,操作变得更加高效,性能会整体提高。 此优化还使为批量操作注册的任何插件步骤更高效。 每次调用插件执行单个操作时,进程需要一些毫秒来调用包含逻辑的插件类。 注册批量操作消息的插件时,该类会被调用一次,从而更有效地处理所有操作。 了解如何为 CreateMultiple 和 UpdateMultiple 编写插件。

性能优势使您愿意在每个请求中发送尽可能多的记录。 但是,随着记录数的增加,请求的大小也会增加,并且请求需要更长的时间来处理。 最终,遇到 消息大小和时间限制。 如果达到这些限制,则整个作将失败。 可以发送的记录数没有设置限制。 可能需要进行试验才能找到最佳数字。 通常,每个请求 100 到 1,000 条记录是一个合理的开始位置(如果记录数据的大小较小且没有插件)。通常可以通过为每个请求发送更少的记录来解决可能会遇到的错误类型。 包括配置发送的实体数量的功能,以便可以通过减少发送数量进行调整。

弹性表记录数

由于弹性表不支持事务,尝试单次请求发送大量记录不会带来性能优势。 每个请求发送 100 个操作,并行发送请求以实现最大吞吐量。

关于错误处理行为

发生错误时的行为取决于使用的是标准表还是弹性表。

标准表的错误处理行为

标准表批量操作中发生的任何错误都会导致整个操作回滚。 只有在对所有操作都会成功有高度信心时,才在标准表上使用批量操作。 若要在批量操作失败时允许将操作集合回滚,请考虑使用 SDK ExecuteMultipleRequest 类 或 Web API $batch。 如果初始尝试的成功率较低,则此策略会导致性能下降。 仅当预期大多数操作成功时,才使用此回退策略。

弹性表的错误处理行为

使用弹性表时,批量操作可能会部分成功。 可以使用错误详细信息来确定哪些记录失败。

使用 SDK 对弹性表执行批量作时,如果发生故障,将引发 OrganizationServiceFault 类型的 FaultException。 使用以下代码获取每个记录的状态。

if (ex.Detail.ErrorDetails.TryGetValue("Plugin.BulkApiErrorDetails", out object errorDetails))
{
    List<BulkApiErrorDetail> bulkApiErrorDetails = JsonConvert.DeserializeObject<List<BulkApiErrorDetail>>(errorDetails.ToString());
}

public class BulkApiErrorDetail
{
    public int RequestIndex { get; set; }
    public string Id { get; set; }
    public int StatusCode { get; set; }
}

可用性

批量操作消息的可用性取决于您使用的是标准表还是弹性表。 所有弹性表都支持CreateMultiple和UpdateMultipleUpsertMultipleDeleteMultiple消息。

标准表的可用性

可以使用 CreateMultiple 和 UpdateMultiple 批量操作消息与自定义标准表和许多常用标准表一起使用,但并非适用于所有情况。 测试单个标准表是否支持这些消息。 以下示例演示如何进行操作。

使用此静态方法检测给定表是否支持 CreateMultiple 或 UpdateMultiple。

/// <summary>
/// Detect whether a specified message is supported for the specified table.
/// </summary>
/// <param name="service">The IOrganizationService instance.</param>
/// <param name="entityLogicalName">The logical name of the table.</param>
/// <param name="messageName">The name of the message.</param>
/// <returns></returns>
public static bool IsMessageAvailable(
    IOrganizationService service,
    string entityLogicalName,
    string messageName)
{
    QueryExpression query = new("sdkmessagefilter")
    {
        ColumnSet = new ColumnSet("sdkmessagefilterid"),
        Criteria = new FilterExpression(LogicalOperator.And)
        {
            Conditions = {
            new ConditionExpression(
                attributeName:"primaryobjecttypecode",
                conditionOperator: ConditionOperator.Equal,
                value: entityLogicalName)
            }
        },
        LinkEntities = {
            new LinkEntity(
                linkFromEntityName:"sdkmessagefilter",
                linkToEntityName:"sdkmessage",
                linkFromAttributeName:"sdkmessageid",
                linkToAttributeName:"sdkmessageid",
                joinOperator: JoinOperator.Inner)
            {
                    LinkCriteria = new FilterExpression(LogicalOperator.And){
                    Conditions = {
                        new ConditionExpression(
                            attributeName:"name",
                            conditionOperator: ConditionOperator.Equal,
                            value: messageName)
                        }
                    }
            }
        }
    };

    EntityCollection entityCollection = service.RetrieveMultiple(query);

    return entityCollection.Entities.Count.Equals(1);
}

消息管道已合并

每个批量操作消息都有一个对单个行操作的对应消息:Create、Update和Delete。 这些消息已存在很长时间,许多组织具有自定义逻辑,具体取决于使用这些消息时发生的事件。

批量操作消息的关键要求是组织不应在两个地方维护自定义逻辑。 若要具有相同的自定义逻辑并将其保存在一个位置,Dataverse 会合并 这些消息的消息处理管道。 此合并意味着什么?

  • 使用批量操作消息时,每个Create参数中的Update实例会触发相应的和Targets事件。 相应的单个事件的任何插件或其他事件处理程序都像往常一样继续工作。 无需编写新插件来管理这些消息引发的事件。

  • 使用单个操作消息时,相应的批量操作事件将包含 EntityCollection,其中包含通过 Targets 参数传递的单个实体实例。 可以将响应单个操作事件的任何逻辑移动到更高效的批量操作事件中,并且将该逻辑同时应用于单个操作和多个操作。

在引入批量操作消息之前,所有自定义逻辑都基于单个操作消息。 当客户端应用程序使用批量操作消息时,将继续应用该逻辑。 对于用于高容量批量操作的表,将任何同步逻辑从单个消息事件移动到批量操作事件。 如果要引入新逻辑,请使用批量操作事件,而非单个操作事件。

注意

使用此设计,可能会对单个和多个版本的事件应用重复逻辑。 Dataverse 不会尝试阻止这种情况,因为它不知道你的意图。

您需确保将适用于单一事件版本的逻辑迁移至多版本事件并从单一事件版本中移除。 否则,逻辑会被应用两次。

了解如何为 CreateMultiple 和 UpdateMultiple 编写插件。

局限性

在使用批量操作消息时,请记住以下限制。

消息大小和时间限制

对于标准表,每次请求发送更多记录时,性能会更好。 但是,有效负载的大小以及处理作所需的时间会限制可以发送的记录数。

消息大小限制

如果为任何消息注册插件,则当请求的总大小超过 116.85 MB 时,可能会遇到 “向沙盒发送上下文时超出消息大小” 错误。 使用批量操作消息时,可以更轻松地达到此限制,因为发送的数据负载更大。

如果未为事件注册插件,则不会发生此错误。 若要避免此错误,请使用可选参数禁用任何插件或发送请求BypassCustomPluginExecution。

时间限制

如果使用 Dataverse ServiceClient,可能会遇到此错误:

The request channel timed out attempting to send after 00:04:00. 
Increase the timeout value passed to the call to Request or increase the SendTimeout value on the Binding. 
The time allotted to this operation may have been a portion of a longer timeout.

使用 ServiceClient 设置的默认超时时间为 4 分钟,这对于任何同步操作来说时间过长。 可以使用静态 ServiceClient.MaxConnectionTimeout 属性更改此值。 使用 CrmServiceClient 的默认超时时间为 2 分钟。

注释

在增加时间限制的数值之前,请考虑减少在 Targets 参数中传递的记录数。

不支持在插件中使用

目前,Dataverse 不支持插件代码中使用批量操作消息。 有关详细信息,请参阅 不要在插件和工作流活动中使用批处理请求类型。

但是,您应当按照CreateMultiple中描述的方法,为 UpdateMultiple 和 消息编写插件。

排查常见错误

如果在使用批量作时遇到错误,请参阅以下文章:

常见问题 (FAQ)

如果在本文中找不到关于使用批量操作消息的问题解答,请使用页面底部的按钮提交并查看此页面的反馈。 需要一个 GitHub 帐户才能提交反馈。

Retrieve 和 RetrieveMultiple 逻辑是否会合并?

Microsoft不打算更改 Retrieve 和 RetrieveMultiple 消息行为。 这些消息多年来都是独立的消息,开发人员始终需要单独维护这些消息的逻辑。 尝试合并消息管道会非常有问题。 此外,请避免为这些消息应用自定义逻辑,因为它们 可能会影响性能。

如何应用 API 限制?

应用两种类型的 API 限制。 批量操作消息不提供任何方法来绕过任何类型。

服务保护限制

如 服务保护 API 限制中所述,限制有三个方面。 其中两项限制在五分钟滑动窗口内评估,使用这些消息时生效。

  • 请求数:在 5 分钟时段内,每条批量操作消息都算作一个请求,并计入每个用户、每个服务器每 5 分钟最多 6,000 个请求的限制。 由于这些请求对单个操作进行分组,因此 降低了发生此限制的可能性。
  • 执行时间:由于每个批量作消息请求通常需要更长的时间,因此,如果在 5 分钟时段内并行发送请求,则更有可能达到每个用户、每个服务器 20 分钟的 执行时间限制 。

Power Platform 请求(API 使用权)限制

这些限制基于数据更改:Create、Update和Delete操作。 批量操作请求的Targets参数中包含的每个项都会计入此限制。 详细了解请求限制和分配。

另请参阅

弹性表
为 CreateMultiple 和 UpdateMultiple 编写插件
示例:用于 .NET 的 SDK 使用批量操作
示例:Web API 使用批量操作
示例:CreateMultiple 和 UpdateMultiple 插件
将消息与用于 .NET 的 SDK 配合使用
优化批量操作的性能