verwendetes Datenbanksystem: SQL Server 2017
Hi,
ich habe eine Anwendung, die Bestellungen hereinbekommt. Zu der Bestellung sollen nun Activities gespeichert werden. Eine Activity ist eine abstrakte Klasse, für die es die Möglichkeiten 'printed', 'accepted' und 'denied' gibt. Wenn ich nun eine Bestellung ändern möchte, bekomme ich eine fiese Exception:
Fehlermeldung:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException : Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See
> for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagation(Int32 commandIndex, RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable1 commandBatches, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList
1 entries)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList1 entriesToSave) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func
3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at Heliprinter.Orders.ReadModel.Denormalizer.OrderDenormalizer.When(OrderPrinted domainEvent)
at Heliprinter.Orders.WebUI.ReadModel.Tests.Tests.Test1() in D:\Source\FluentSoftware\Heliprinter_neu\Heliprinter.Monitoring\Source\Heliprinter.Orders.WebUI.ReadModel.Tests\UnitTest1.cs:line 30
Der Code dazu ist
var savedOrder = _context.Orders.Where(order => order.Id == domainEvent.AggregateId).ToList().SingleOrDefault();
if (savedOrder != null)
{
var activity = new OrderPrintedActivity()
{
Id = domainEvent.Id,
PrinterStatus = domainEvent.PrinterStatus.ToString(),
ResultCode = domainEvent.ResultCode,
Order = savedOrder
};
_context.SaveChanges();
}
public class OrderEntity
{
public Guid Id { get; set; }
// andere Properties ...
public virtual ICollection<CommunicationActivity> Activities { get; set; }
}
public abstract class CommunicationActivity
{
public Guid Id { get; set; }
public virtual OrderEntity Order { get; set; }
public int ResultCode { get; set; }
}
Ich habe extra einen Unit-Test erstellt, um auszuschlißen, dass ein Nebenläufigkeitsproblem existiert. Aber scheinbar tritt das Problem auch Single-Threaded auf. Könnt ihr mir da helfen?
VG
Was Du machst dürfte unter "Update or ignore" behavior fallen, was so in der Form in EF Core nicht unterstützt wird.
Da Du die gesamte Order Entity lädst, kannst Du auch einfach das Element der Liste in der Order hinzufügen.
Das wäre dann gemäß den EF Empfehlungen.
In der Art und Weise ist es dann auch zu empfehlen, dass Du die Activity dem DbSet hinzufügst, und dann speicherst (und alles asynchron umsetzen).
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ich kann mit dme Begriff "Update or ignore" nichts anfangen. Aber wenn du das meinst: Ich hatte damit angefangen, die Activity der Liste in der Order hinzuzufügen. Allerdings macht es keinen (sichtbaren) unterschied, ob ich:
-Die Order in der Activity setze
Alle 3 Varianten schmeißen den selben Fehler. Von daher bitte nochmal Feedback, ob ich Dich richtig verstanden habe.
Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Dieser Fehler wird meistens geworfen wenn man im Context versucht etwas zu schreiben oder zu aktualisieren, das es aber nicht gibt.
Die meisten Fälle sind, dass man versucht ein Element zu aktualisieren, das aber in der Datenbank nicht existiert.
Ich bin neulich erst selbst in diese "Falle" gelaufen.
Beispiel: Du versuchst ein Element zu aktualisieren, das es aber nicht gibt.
Es könnte daher der Fall sein, dass Du in eine Race Condition mit solch oder einem ähnlichen Fall hast.
Was ich weiterhin sagte ist, dass Du hier nur ein impliziertes Add hast:
Besser wäre es, wenn Du vor dem Save eben noch die Entität der jeweiligen Collection hinzufügst.
await _context.Activities.AddAsync(entity)
_context.SaveChangesAsync(CancellationToken);
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Beispiel: Du versuchst ein Element zu aktualisieren, das es aber nicht gibt.
Damit hast Du Recht. Entschuldige, dass hatte ich nicht geschrieben, aber mir fehlt der Insert auf die Activities. Ich sehe das die Order gefunden wird (das ist der select vor dem add):
Executed DbCommand (207ms) [Parameters=[@__domainEvent_AggregateId_0='1d49e3b2-a121-471f-93d6-fd1445fa250d'], CommandType='Text', CommandTimeout='30']
SELECT [o].[Id], [o].[ConfirmedAt], [o].[CreatedAt], [o].[DeviceId], [o].[FirstDeliveredAt], [o].[LastDeliveredAt], [o].[OrderNumber], [o].[OrderStatus], [o].[OrderText], [o].[PrintedAt]
FROM [Orders] AS [o]
WHERE [o].[Id] = @__domainEvent_AggregateId_0ConfirmedAt CreatedAt DeviceId FirstDeliveredAt LastDeliveredAt Id OrderNumber OrderStatus OrderText PrintedAt
1 NULL 26.02.2020 09:21:33 21E5A51A-E966-49F4-AD3B-195DB96A2EAB NULL NULL C8740EBF-F935-4377-804A-C949BBABF6E4 0100 1 test NULL
Und dann sehe ich den Update auf die Activities:
Executing DbCommand [Parameters=[@p3='d60a4e49-4b40-40aa-8053-2cd8ed4b9593', @p0='1d49e3b2-a121-471f-93d6-fd1445fa250d' (Nullable = true), @p1='1', @p2='0' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [OrderActivities] SET [OrderId] = @p0, [ResultCode] = @p1, [PrinterStatus] = @p2
WHERE [Id] = @p3;
SELECT @@ROWCOUNT;
Aber ich sehe keinen Insert auf die Activities.
Ich sträube mich auch etwas dagegen ein DbSet dafür anzulegen. Die Activities sind nur im Subbaum der Orders interessant und keine eigenständige Entität. Von daher wäre es mir Recht, wenn es auch ohne ein DbSet gehen würde....
Interessante Argumentation; aber macht ja wenig Sinn.
Die Informationen vom DbSet zum Kontext hilft EF Core bei der Generierung der Operationen; sind teilweise auch Performance-entscheidend.
Daher erschließt sich mir nicht, wieso Du keine Lust auf das DbSet hast...
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code