首页 > 解决方案 > RavenDB:如何同时正确更新文档?(对同一端点的两个同时 API 请求)

问题描述

我有一个带有upload端点的 C# REST API,其唯一目的是处理二进制文件并将其元数据(作为Attachment模型)添加到List<Attachment>不同实体的属性中。

当我以如下所示的顺序方式(伪代码)从我的 Web 应用程序调用端点时,端点会按预期执行并处理每个二进制文件并将单个文件添加Attachment到提供的实体中。

const attachments = [Attachment, Attachment, Attachment];

for(const attachment of attachments) {
    await this.api.upload(attachment);
}

但是当我尝试以类似下面的并行方式(伪代码)上传附件时,每个二进制文件都会得到正确处理,但只有一个Attachment元数据对象被添加到实体中。

const attachments = [Attachment, Attachment, Attachment];

const requests = attachments.map((a) => this.api.upload(a));
await Promise.all(requests);

端点基本上执行以下操作(简化):

var attachment = new Attachment() 
{
    // Metadata is collected from the binary (FormFile)
};

using (var session = Store.OpenAsyncSession())
{
    var entity = await session.LoadAsync<Entity>(entityId);

    entity.Attachments.Add(attachment);

    await session.StoreAsync(entity);                   
    await session.SaveChangesAsync();
};

我怀疑问题是同时调用了端点。两者都请求打开(同时)数据库会话并将实体查询到内存中。他们每个人都将其添加Attachment到实体中并在数据库中更新它。您在数据库中看到的已保存附件来自最后完成的请求,例如耗时最长的请求。

我试图通过创建这个例子来重现这个问题。当您打开链接时,示例会立即运行。您可以在此数据库服务器上看到创建的实体。

打开Hogwarts数据库,然后打开联系人Harry Potter,您会看到添加了两个附件。当您打开联系人时Hermione Granger,您只会看到添加的一个附件(Second.txt),尽管它也应该有两个附件。

解决此问题的最佳方法是什么?我宁愿不必将文件作为批处理发送到端点。感谢任何帮助!

PS:您可能需要通过单击手动运行示例Run。如果服务器上不存在数据库(因为服务器自动清空),您可以使用Hogwarts名称手动创建它。而且因为它看起来像一个竞争条件,有时这两个Attachment项目都被正确添加。因此,您可能需要多次运行该示例。

标签: c#ravendb

解决方案


这是写入数据库的竞争条件的一个相当经典的例子,你是对的。

事件顺序是:

  1. 要求 1 加载文档Attachments = []
  2. 要求 1 加载文档Attachments = []
  3. 要求 1Attachments.Push()
  4. 要求 2Attachments.Push()
  5. 要求 1SaveChanges()
  6. 要求 2SaveChanges()

中的更改会5覆盖中的更改4,因此您正在丢失数据。

有两种方法可以处理这种情况。您可以为此特定场景启用乐观并发,请参阅有关该主题的文档:

https://ravendb.net/docs/article-page/4.2/csharp/client-api/session/configuration/how-to-enable-optimistic-concurrency#enabling-for-a-specific-session

session.Advanced.UseOptimisticConcurrency = true;基本上,如果文档在幕后更新,您可以采取措施导致事务失败。

然后,您可以重试事务以使其工作(确保创建一个新会话)。

或者,您可以使用修补 API,这将允许您安全地同时将项目添加到文档中。以下是相关文档:

https://ravendb.net/docs/article-page/4.2/csharp/client-api/operations/patching/single-document#add-item-to-array

请注意,这里有一个考虑因素,您不应该关心操作的顺序是什么(因为它们可以以任何顺序发生)。如果订单背后有业务用例,您可能无法轻松使用补丁 API,需要使用完整的交易路线。


推荐阅读