#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 Если возможно, рекомендуется загрузить образец кода после сокрытия конфиденциальной информации, и проект может воспроизвести проблему.