gpt4 book ai didi

c# - EF Core - 同一实体的并发保存创建多个

转载 作者:行者123 更新时间:2023-12-04 04:09:22 24 4
gpt4 key购买 nike

问题
我的概念是,当用户创建带有某些标签的帖子时,服务器首先检查标签名称是否已经存在,如果存在,则计数器递增,否则创建一个新标签。

当多个用户同时使用新标签创建帖子时,问题就来了,比方说 new_tag,然后数据库中会保留多个同名标签,而不是 1 个带有 counter = # 的标签使用此标签的用户数

正如您所看到的,对于每个用户,都会在数据库中创建一个新的标签记录:

--------------------------------
| id | tagName | counter |
|------|-----------|-----------|
| 1 | new_tag | 1 |
| 2 | new_tag | 1 |
| 3 | new_tag | 1 |
| 4 | new_tag | 1 |
--------------------------------

我的期望:

--------------------------------
| id | tagName | counter |
|------|-----------|-----------|
| 1 | new_tag | 4 |
--------------------------------

这段代码展示了我是如何实现持久化的:


PostRepository

public async Task<bool> AddAsync(Post entity)
{
await AddNewTagsAsync(entity);
_context.Attach(entity.Event);
await _context.AddAsync(entity);
await _context.Database.BeginTransactionAsync();
var result = await _context.SaveChangesAsync();
_context.Database.CommitTransaction();
return result > 0;
}

public async Task AddNewTagsAsync(Post post)
{
// store tags name in lower case
if ((post.PostTags == null) || (post.PostTags.Count==0))
return;
post.PostTags.ForEach(pt => pt.Tag.TagName = pt.Tag.TagName.ToLower());

for(var i =0; i<post.PostTags.Count; i++)
{
var postTag = post.PostTags[i];

// here lays the main problem, when many concurrent users check for tag existence
// all get null and new tag will be created, workaround needed!
var existingTag = await _context.Tags.SingleOrDefaultAsync(x => x.TagName == postTag.Tag.TagName);

// if tag exists, increment counter
if (existingTag != null)
{
existingTag.Counter++;
postTag.Tag = existingTag;
continue;
}

// else the new Tag object will be peristed
}
}

这是我的 ER 图的一部分:

This

顺便提一下,如果一个用户先创建标签,然后其他人只是递增计数器并使用相同的标签,它会按预期工作

最佳答案

您正在寻找原子 UPSERT 语句(组合的 UPDATE 或 INSERT)。

EF Core 不支持 UPSERTS。请参阅:https://github.com/dotnet/efcore/issues/4526

但是,如果您愿意放弃更改跟踪,您可以直接创建 SQL 合并语句,如下所示:

    MERGE INTO dbo.Tags AS target  
USING (VALUES ({TagName})) AS source (TagName)
ON target.TagName = source.TagName
WHEN MATCHED THEN
UPDATE SET Counter = Counter + 1
WHEN NOT MATCHED BY TARGET THEN
INSERT (TagName, Counter) VALUES (TagName, 1);

你可以这样调用它:

public async Task AddNewTagsAsync(Post post)
{
foreach (var tag in post.PostTags)
{
await _context.Database.ExececuteInterpolatedAsync($@"
MERGE INTO dbo.Tags AS target
USING (VALUES ({tag.TagName})) AS source (TagName)
ON target.TagName = source.TagName
WHEN MATCHED THEN
UPDATE SET Counter = Counter + 1
WHEN NOT MATCHED BY TARGET THEN
INSERT (TagName, Counter) VALUES (TagName, 1)");
}
}

关于c# - EF Core - 同一实体的并发保存创建多个,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62010832/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com