Laden...

Entity Framework Core 3.1.2 | Navigation Property one-to-many with Inherience

Erstellt von fluxy vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.559 Views
F
fluxy Themenstarter:in
183 Beiträge seit 2009
vor 4 Jahren
Entity Framework Core 3.1.2 | Navigation Property one-to-many with Inherience

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(IList1 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, Func3 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

16.806 Beiträge seit 2008
vor 4 Jahren

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).

F
fluxy Themenstarter:in
183 Beiträge seit 2009
vor 4 Jahren

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

  • Die Activity in der Liste der Activity des Order-Objekts hinzufüge
  • oder beides mache.

Alle 3 Varianten schmeißen den selben Fehler. Von daher bitte nochmal Feedback, ob ich Dich richtig verstanden habe.

16.806 Beiträge seit 2008
vor 4 Jahren

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:

  • Du erstellst die neue Entität
  • Verweist auf das Parent Element
  • EF soll nun nachschauen, ob die neue Entität existiert oder nicht; wenn nicht, dann macht es ein Add, ansonsten geht es von einem Update aus.

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);
F
fluxy Themenstarter:in
183 Beiträge seit 2009
vor 4 Jahren

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_0

ConfirmedAt 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....

16.806 Beiträge seit 2008
vor 4 Jahren

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...