EF Core - одновременное сохранение одной и той же сущности создает несколько - PullRequest
3 голосов
/ 26 мая 2020

Проблема
Моя концепция заключается в том, что когда пользователь создает сообщение с некоторыми тегами, сервер сначала проверяет, существует ли уже имя тега, если оно существует, его счетчик увеличивается, в противном случае новый тег создан.

Проблема возникает, когда несколько пользователей создают сообщение одновременно с новым тегом, скажем, 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

Просто отметим, что это работает должным образом, если один пользователь сначала создает тег, а другие просто увеличивают счетчик и используют то же самое. тег

1 Ответ

1 голос
/ 26 мая 2020

Вы ищете оператор atomi c UPSERT (комбинированный UPDATE или INSERT).

UPSERTS не поддерживается EF Core. См .: https://github.com/dotnet/efcore/issues/4526

Однако, если вы желаете отслеживать изменения go, вы можете напрямую сделать оператор слияния 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)");
    } 
}
...