Исключение EF Core DbUpdateConcurrencyException при вставке

#c# #entity-framework-core #azure-sql-database

#c# #entity-framework-core #azure-sql-database

Вопрос:

Я использую следующие инструменты: Azure SQL Server, .NET Core 3.1, EF Core 5.0

У меня есть вызываемая основная таблица Channels и вызываемая другая таблица ChannelWikiTopics . Всякий раз, когда происходит обновление канала в Channels , я удаляю все ChannelWikiTopics принадлежащие этому каналу, а затем вставляю вместо них новые.

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

Проблема в том, что всякий раз, когда у меня появляется новый Channel файл, иначе говоря, у меня нет никаких викитопиков, принадлежащих этому каналу (вставка в первый раз, но таблица не обязательно пуста, просто в первый раз для этого конкретного канала), вставка происходит без проблем. Но когда я пытаюсь выполнить обновление для канала, означающего, что он уже существует, вставки завершаются неудачей. Но только в ChannelWikiTopics таблице, остальное работает нормально.

Мои объекты следующие:

 public class ChannelWikiTopic
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; } ///<value>DB generated id to identify this WikiTopicCat topic.</value>
    public string TopicCategory { get; set; } ///<value>Topic id as identified by WikiTopicCat standards.</value>
    [ForeignKey("Channel")]
    public string YTChannelId { get; set; } ///<value>Channel id given by YT to the channel and also used by Channels as a primary id.</value>
    public virtual Channel Channel { get; set; } ///<value>Reference property to the channel to which this Wiki topic category belong.</value>
}

public class Channel
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public string YTChannelId { get; set; } ///<value>Id YouTube gives to the channel and it is used here as a primary key.</value>
    ... other irrelevant properties ...
    public ICollection<ChannelWikiTopic> ChannelWikiTopics { get; set; } ///<value>A collection of all the WikiTopicCategories assigned to this channel</value>
}
  

Операции, которые я выполняю в своем репозитории, следующие:

 public IDictionary<string,Channel> RecallChannels(string[] channelIds)
{
        try
        {
            //retrieve all the pertinent entities from DB
            _ChannelIdDictionary = _context.Channel.Where(c => channelIds.Contains(c.YTChannelId)).ToDictionary(c => c.YTChannelId, c => c);
            var wikiDic = _context.ChannelWikiTopics.Where(wt => channelIds.Contains(wt.YTChannelId)).ToList();

            //remove all the wikitopics to avoid duplicates in db, since I'm inserting them later on again
            if (wikiDic.Count > 0)
                _context.ChannelWikiTopics.RemoveRange(wikiDic);

            //_context.SaveChanges();

            return _ChannelIdDictionary;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ex.Message);
            throw ex;
        }
}
  

Если бы я раскомментировал _context.SaveChanges(); здесь, то это сработало бы идеально. Но я не думаю, что это должно иметь значение. Потому что EF удаляет викитопики перед удалением в любом случае перед вставкой.

Затем я обновляю объект

 public void UpdateChannel(Channel channelEntity)
{
        try
        {
            if (_context.Entry(channelEntity).State != EntityState.Added)
                _context.Channel.Update(channelEntity);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ex.Message);
            throw ex;
        }
}
  

And then I save the changes

 public async Task Commit()
{
        try
        {
            _ = await _context.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ex.Message);
            throw ex;
        }
}
  

Here is the SQL generated by EF Core according to the logs

 SET NOCOUNT ON;

UPDATE [Channel] SET ...lots of fields...
WHERE [YTChannelId] = @p16;
SELECT @@ROWCOUNT;

UPDATE [Channel] 
SET ...lots of columns...
WHERE [YTChannelId] = @p33;
SELECT @@ROWCOUNT;

UPDATE [Channel] 
SET ...lots of columns...
WHERE [YTChannelId] = @p50;
SELECT @@ROWCOUNT;

DELETE FROM [ChannelWikiTopics]
WHERE [Id] = @p144;
SELECT @@ROWCOUNT;

DELETE FROM [ChannelWikiTopics]
WHERE [Id] = @p145;
SELECT @@ROWCOUNT;

DELETE FROM [ChannelWikiTopics]
WHERE [Id] = @p146;
SELECT @@ROWCOUNT;

DELETE FROM [ChannelWikiTopics]
WHERE [Id] = @p147;
SELECT @@ROWCOUNT;

DELETE FROM [ChannelWikiTopics]
WHERE [Id] = @p148;
SELECT @@ROWCOUNT;

DELETE FROM [ChannelWikiTopics]
WHERE [Id] = @p149;
SELECT @@ROWCOUNT;

DELETE FROM [ChannelWikiTopics]
WHERE [Id] = @p150;
SELECT @@ROWCOUNT;

INSERT INTO [ChannelWikiTopics] ([Id], [TopicCategory], [YTChannelId])
VALUES (@p151, @p152, @p153),
(@p154, @p155, @p156),
(@p157, @p158, @p159),
(@p160, @p161, @p162),
(@p163, @p164, @p165),
(@p166, @p167, @p168),
(@p169, @p170, @p171);
  

Все выглядит в порядке. Ничего подозрительного. Я даже проверил, что удаляемые идентификаторы не использовались повторно, и это не так.

Идентификаторы, удаляемые для этого запуска, были:

 @p144='0c25919e-d420-4693-e170-08d8599b09fa', @p145='33aecae6-ab86-4539-e172-08d8599b09fa', 
@p146='6407d3f0-8a64-4e80-e171-08d8599b09fa', @p147='a080d930-cf94-462c-e16f-08d8599b09fa', 
@p148='abe51051-0db6-4bbf-e16d-08d8599b09fa', @p149='cb2ac5ed-cce0-426b-e16e-08d8599b09fa', 
@p150='d1bca992-4a6f-443c-e173-08d8599b09fa'
  

Идентификаторы, вставляемые для этого запуска, были:

 @p151='9340713c-c754-4215-5fda-08d8599b9bfb', @p154='8a2b8bda-15a4-4f28-5fd6-08d8599b9bfb', 
@p157='b354a6bb-8927-4cec-5fd7-08d8599b9bfb', @p160='92731e08-4d28-4076-5fd8-08d8599b9bfb',
@p163='6caa4995-79d7-4e3c-5fd9-08d8599b9bfb', @p166='e8a41a0a-476a-45d1-5fdb-08d8599b9bfb',
@p169='261d3b48-c2cf-4712-5fdc-08d8599b9bfb'
  

Я даже попытался выполнить вставку самостоятельно непосредственно в SQL Server Management Studio, заменив параметры в журнале их соответствующим значением, и это сработало. Он выполнил вставку без удаления старых. Что, конечно, имеет смысл, поскольку нет ограничения, которое говорит, что пара <[TopicCategory], [YTChannelId]> должна быть уникальной.

Также стоит отметить, что нет триггера, влияющего ни на одну из таблиц. Я видел, что это обычная проблема с такого рода ошибками. И, конечно, нет реальной проблемы параллелизма, поскольку в настоящее время я единственный пользователь, и фоновый процесс также ничего не делает.

На случай, если это полезно, вот сценарий создания таблицы

 SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[ChannelWikiTopics]
(
    [Id] [uniqueidentifier] NOT NULL,
    [TopicCategory] [nvarchar](max) NULL,
    [YTChannelId] [nvarchar](450) NULL,

    CONSTRAINT [PK_ChannelWikiTopics] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
                    WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [dbo].[ChannelWikiTopics] WITH CHECK 
    ADD CONSTRAINT [FK_ChannelWikiTopics_Channel_YTChannelId] 
        FOREIGN KEY([YTChannelId]) REFERENCES [dbo].[Channel] ([YTChannelId])
GO

ALTER TABLE [dbo].[ChannelWikiTopics] CHECK CONSTRAINT [FK_ChannelWikiTopics_Channel_YTChannelId]
GO
  

Пожалуйста, дайте мне знать, если чего-то не хватает или если что-то было недостаточно ясно

Спасибо!

Комментарии:

1. На всякий случай, 2 вопроса: 1. Что насчет сообщения, содержащегося в исключении DbUpdateConcurrencyException? Есть ли что-нибудь конкретное? 2. Вы нашли минимально воспроизводимый пример? Я имею в виду, действительно ли все столбцы ‘unimportamt’ удалены из реальных таблиц, и проблема все еще возникает?

2. @AlexeyAdadurov 1) ничего конкретного, просто общее, ожидаемое влияние на 1 строку (ы), но фактически затронувшее 0 строк (ов) 2) на самом деле удалять особо нечего, учитывая, что channelWikiTopics имеет только 3 столбца. Но если бы я должен был прокомментировать ту часть, где я вставляю викитопики , но сохраняю все остальное (удаляя викитопики и обновляя каналы), это работает. И это происходит каждый раз, независимо от того, сколько каналов я выбираю для обновления. Я пробовал что-либо от 1 до 2,5 миллионов каналов. Все это отлично работает при первой вставке в ChannelWikiTopics, но не после этого.

3. @AlexeyAdadurov пожалуйста, обратите внимание, что если я удалю викитопики , принадлежащие обновляемым каналам, и немедленно зафиксирую это удаление, это сработает. Это не работает только как часть более крупной транзакции, подробно описанной в sql, показанном в сообщении (которые происходят в таком порядке, кстати)

4. @gris Эта проблема не сложная. Проблема в том, что необходимо устранить неполадки и требуется отладка точки останова. На каждом шаге, пожалуйста, внимательно проверьте часть, которая взаимодействует с базой данных, извлеките инструкцию для выполнения, посмотрите, соответствует ли это ожидаемому эффекту, и, наконец, сообщите об ошибке для анализа.

5. @gris Если возможно, рекомендуется загрузить образец кода после сокрытия конфиденциальной информации, и проект может воспроизвести проблему.