c# - 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
项目都被正确添加。因此,您可能需要多次运行该示例。
解决方案
这是写入数据库的竞争条件的一个相当经典的例子,你是对的。
事件顺序是:
- 要求 1 加载文档
Attachments = []
- 要求 1 加载文档
Attachments = []
- 要求 1
Attachments.Push()
- 要求 2
Attachments.Push()
- 要求 1
SaveChanges()
- 要求 2
SaveChanges()
中的更改会5
覆盖中的更改4
,因此您正在丢失数据。
有两种方法可以处理这种情况。您可以为此特定场景启用乐观并发,请参阅有关该主题的文档:
session.Advanced.UseOptimisticConcurrency = true;
基本上,如果文档在幕后更新,您可以采取措施导致事务失败。
然后,您可以重试事务以使其工作(确保创建一个新会话)。
或者,您可以使用修补 API,这将允许您安全地同时将项目添加到文档中。以下是相关文档:
请注意,这里有一个考虑因素,您不应该关心操作的顺序是什么(因为它们可以以任何顺序发生)。如果订单背后有业务用例,您可能无法轻松使用补丁 API,需要使用完整的交易路线。
推荐阅读
- r - Rssa中的gapfill给我错误
- pointers - 如何将接口实现指针转换为接口指针?
- c# - C#:FileStream.SetLength(long) 失败,并显示“由于文件系统限制,无法完成请求的操作”(在 Windows 10 Home、NTFS 上)
- android - Android Studio 3.1 - 插件 Android Support 中的异常。自上次清除以来发生 2 次。未读。禁用插件
- r - 根据字符值将数据帧拆分为子集
- symfony - 使用教义:数据库:创建时,如何创建具有特定追逐和排序规则的数据库?
- c# - 带方括号的模型绑定 POST 值
- r - 合并/加入不完全匹配列的数据框
- visual-studio-code - 在 VS Code Vim 中,当光标移到代码折叠上时,它们会自动打开。我怎样才能防止这种情况?
- apache-modules - 开发 Apache 模块 - 错误:取消引用指向不完整类型“apr_socket_t {aka struct apr_socket_t}”的指针