Laden...

Warum kann ich einen Datensatz mit EFCore & SQLite eintragen, aber nicht löschen?

Erstellt von GeneVorph vor 3 Jahren Letzter Beitrag vor 3 Jahren 612 Views
G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 3 Jahren
Warum kann ich einen Datensatz mit EFCore & SQLite eintragen, aber nicht löschen?

verwendetes Datenbanksystem: EntityFrameworkCore 6, SQLite

Hallo,

ich habe meine Datenbank mit Hilfe von EFCore per Code-First erstellt. Ich kann neue Einträge hinzufügen, bekomme beim Löschen aber folgende Fehlermeldung

Fehlermeldung:
System.InvalidOperationException: "The property 'SchoolClassData.SchoolClassDataId' has a temporary value while attempting to change the entity's state to 'Deleted'. Either set a permanent value explicitly, or ensure that the database is configured to generate values for this property."

Leider habe ich keine Ahnung, wie ich den temporären Wert in einen nicht temporären umwandeln könnte, bzw. wie ich die die Datenbank konfigurieren könnte (--> außer den im Folgenden beschriebenen Schritten, die ich unternommen habe).

Die Methode, in der der Fehler ausgelöst wird (in meinem DataService):


public static void DeleteSchoolClass(SchoolClassData _schoolClass)
        {           
            using (var context = new DataBaseContext())
            {
                //Die nun folgende Zeile löst den Fehler aus
                var entity = context.SchoolClasses.Remove(_schoolClass);

                entity.State = Microsoft.EntityFrameworkCore.EntityState.Deleted;

                context.SaveChanges();
            }
        }

Erst dachte ich, der Fehler sein einfach zu beheben - leider hatte ich mit den folgenden Methoden keinen Erfolg:

  • Datenbank mit SQLite-Studio verbunden und den Key-Constraint 'ON DELETE RESTRICTED' zu 'CASCADE' geändert
  • Key-Constraint zu 'DEFAULT VALUE' geändert
  • Key-Constraints entfernt

Hier mein Daten-Modell:


public class SchoolClassData : ISchoolClass
    {
        [Required]
        public int SchoolClassDataId { get; set; }

        [Required]
        public SchoolClassInternalData SchoolClassInternals { get; set; }
    }

Nachdem ich die Datenbank erstmalig erstellt hatte, hatte ich eine Migration vorgenommen, bzw. die Datenbank geupdated. Hier der Code:

Mein DataContext:


 public class DataBaseContext : DbContext
    {
        public DbSet<SchoolClassData> SchoolClasses { get; set; }
        public DbSet<SchoolClassInternalData> SchoolClassInternals { get; set; }
        public DbSet<StudentData> Students { get; set; }
        public DbSet<PersonalData> Personals { get; set; }
        public DbSet<AddressData> Addresses { get; set; }
        public DbSet<EducationalData> Educationals { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //optionsBuilder.UseSqlite("Data Source=Test_lite.sqlite");
            optionsBuilder.UseSqlite(connectionString: "FileName =./Test_DataBase01.sqlite");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<SchoolClassData>().ToTable("SchoolClasses");

            modelBuilder.Entity<SchoolClassInternalData>().ToTable("SchoolClassInternals");

            modelBuilder.Entity<StudentData>().ToTable("Students");

            modelBuilder.Entity<PersonalData>().ToTable("Personals");

            modelBuilder.Entity<AddressData>().ToTable("Addresses");

            modelBuilder.Entity<EducationalData>().ToTable("Educationals");
        }
    }


Meine initiale Migration:


public partial class InitialState : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Addresses",
                columns: table => new
                {
                    AddressDataId = table.Column<int>(type: "INTEGER", nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    Street = table.Column<string>(type: "TEXT", maxLength: 128, nullable: true),
                    PostalCode = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true),
                    City = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Addresses", x => x.AddressDataId);
                });

            migrationBuilder.CreateTable(
                name: "Educationals",
                columns: table => new
                {
                    EducationalDataId = table.Column<int>(type: "INTEGER", nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    StudentType = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true),
                    ClassLabel = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false),
                    ClassLevel = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true),
                    EducationalProgram = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Educationals", x => x.EducationalDataId);
                });

            migrationBuilder.CreateTable(
                name: "Personals",
                columns: table => new
                {
                    PersonalDataId = table.Column<int>(type: "INTEGER", nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    FirstName = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
                    LastName = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
                    FullName = table.Column<string>(type: "TEXT", maxLength: 128, nullable: true),
                    DateOfBirth = table.Column<string>(type: "varchar(15)", maxLength: 15, nullable: true),
                    Age = table.Column<int>(type: "INTEGER", nullable: false),
                    Gender = table.Column<string>(type: "TEXT", maxLength: 20, nullable: true),
                    PhoneNumber = table.Column<string>(type: "varchar(128)", maxLength: 128, nullable: true),
                    EMail = table.Column<string>(type: "TEXT", maxLength: 128, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Personals", x => x.PersonalDataId);
                });

            migrationBuilder.CreateTable(
                name: "SchoolClassInternals",
                columns: table => new
                {
                    SchoolClassInternalDataId = table.Column<int>(type: "INTEGER", nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    SchoolClassLabel = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false),
                    SchoolClassLevel = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true),
                    SchoolClassHead = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_SchoolClassInternals", x => x.SchoolClassInternalDataId);
                });

            migrationBuilder.CreateTable(
                name: "SchoolClasses",
                columns: table => new
                {
                    SchoolClassDataId = table.Column<int>(type: "INTEGER", nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    SchoolClassInternalsSchoolClassInternalDataId = table.Column<int>(type: "INTEGER", nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_SchoolClasses", x => x.SchoolClassDataId);
                    table.ForeignKey(
                        name: "FK_SchoolClasses_SchoolClassInternals_SchoolClassInternalsSchoolClassInternalDataId",
                        column: x => x.SchoolClassInternalsSchoolClassInternalDataId,
                        principalTable: "SchoolClassInternals",
                        principalColumn: "SchoolClassInternalDataId",
                        onDelete: ReferentialAction.Restrict);
                });

            migrationBuilder.CreateTable(
                name: "Students",
                columns: table => new
                {
                    StudentDataId = table.Column<int>(type: "INTEGER", nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    StudentPersonalPersonalDataId = table.Column<int>(type: "INTEGER", nullable: true),
                    StudentAddressAddressDataId = table.Column<int>(type: "INTEGER", nullable: true),
                    StudentEducationalEducationalDataId = table.Column<int>(type: "INTEGER", nullable: true),
                    SchoolClassInternalDataId = table.Column<int>(type: "INTEGER", nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Students", x => x.StudentDataId);
                    table.ForeignKey(
                        name: "FK_Students_Addresses_StudentAddressAddressDataId",
                        column: x => x.StudentAddressAddressDataId,
                        principalTable: "Addresses",
                        principalColumn: "AddressDataId",
                        onDelete: ReferentialAction.Restrict);
                    table.ForeignKey(
                        name: "FK_Students_Educationals_StudentEducationalEducationalDataId",
                        column: x => x.StudentEducationalEducationalDataId,
                        principalTable: "Educationals",
                        principalColumn: "EducationalDataId",
                        onDelete: ReferentialAction.Restrict);
                    table.ForeignKey(
                        name: "FK_Students_Personals_StudentPersonalPersonalDataId",
                        column: x => x.StudentPersonalPersonalDataId,
                        principalTable: "Personals",
                        principalColumn: "PersonalDataId",
                        onDelete: ReferentialAction.Restrict);
                    table.ForeignKey(
                        name: "FK_Students_SchoolClassInternals_SchoolClassInternalDataId",
                        column: x => x.SchoolClassInternalDataId,
                        principalTable: "SchoolClassInternals",
                        principalColumn: "SchoolClassInternalDataId",
                        onDelete: ReferentialAction.Restrict);
                });

            migrationBuilder.CreateIndex(
                name: "IX_SchoolClasses_SchoolClassInternalsSchoolClassInternalDataId",
                table: "SchoolClasses",
                column: "SchoolClassInternalsSchoolClassInternalDataId");

            migrationBuilder.CreateIndex(
                name: "IX_Students_SchoolClassInternalDataId",
                table: "Students",
                column: "SchoolClassInternalDataId");

            migrationBuilder.CreateIndex(
                name: "IX_Students_StudentAddressAddressDataId",
                table: "Students",
                column: "StudentAddressAddressDataId");

            migrationBuilder.CreateIndex(
                name: "IX_Students_StudentEducationalEducationalDataId",
                table: "Students",
                column: "StudentEducationalEducationalDataId");

            migrationBuilder.CreateIndex(
                name: "IX_Students_StudentPersonalPersonalDataId",
                table: "Students",
                column: "StudentPersonalPersonalDataId");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "SchoolClasses");

            migrationBuilder.DropTable(
                name: "Students");

            migrationBuilder.DropTable(
                name: "Addresses");

            migrationBuilder.DropTable(
                name: "Educationals");

            migrationBuilder.DropTable(
                name: "Personals");

            migrationBuilder.DropTable(
                name: "SchoolClassInternals");
        }
    }

Der Migration zu folge sollte SchoolClassDataId PrimaryKey sein, nicht nullable, und AutoIncrement true.

Kann der Fehler noch eine andere Ursache haben? Was genau bedeutet 'temporärer Key' und wie kann ich das beheben.
Vielen Dank im Voraus,

Gruß
vorph

16.806 Beiträge seit 2008
vor 3 Jahren

Du arbeitest mit EntityFrameworkCore 6? Dann scheinst Du in der Zukunft zu sein, denn die Version gibts erst in 6 Monaten 😉
Es gibt aktuell EF Core 5 oder EF 6.

Hinweis: Der DbContext ist dazu da, dass er übergreifend geshared wird.
Dass Du hier mit statischer Klassen arbeitest widerspricht zum einen natürlich völlig OOP und zum anderen dem Umgang vom DbContext.
Das wird Dir also sehr bald Probleme machen; soviel vorweg.

Zum Fehler:
Der Fehler kommt meistens, wenn Relationen nicht korrekt gesetzt sind.
Alle Datenbank-Relationen müssen in EF Core auch im Modell bekannt sein; es bringt nichts, wenn Du das nur in der DB pflegst.
Ich sehe bei Dir aber nirgends hier eine Relations-Konfiguration im Modell.
Das wird womöglich der Fehler sein.

PS: Du bist Dir hoffentlich bewusst, dass die Migrationen in Sqlite nur begrenzt funktionieren, da Sqlite kein Schema Change unterstützt.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 3 Jahren

Du arbeitest mit EntityFrameworkCore 6? Dann scheinst Du in der Zukunft zu sein, denn die Version gibts erst in 6 Monaten 😉
Es gibt aktuell EF Core 5 oder EF 6.

Mit über 40 Lenzen bin ich eher ein Mann der Vergangenheit 😉 War ein Typeo'; es muss freilich EF Core 5 heißen.

Hinweis: Der DbContext ist dazu da, dass er übergreifend geshared wird.
Dass Du hier mit statischer Klassen arbeitest widerspricht zum einen natürlich völlig OOP und zum anderen dem Umgang vom DbContext.
Das wird Dir also sehr bald Probleme machen; soviel vorweg.

Danke für den Hinweis - auch wenn ich schon eine Zeit hier bin, bin ich immer noch ein Hobby-Coder, der sich aus hunderten von Quellen alles zusammenstoppelt. Weiß nicht, ob es hier passt, aber kannst du da kurz bissl ins Detail gehen?

Ich sehe bei Dir aber nirgends hier eine Relations-Konfiguration im Modell.
Das wird womöglich der Fehler sein.

OK - was genau bedeutet "Relations-Konfiguration im Modell"? Ich habe dich jetzt so verstanden, dass das Modell EFCore "zeigt", wie die DB konfiguriert sein soll, also: Klassenname + Endung Id = Primär-Key, Properties = Columns, Objekt-Verweise = neuer Table (Mediator-Table?) usw. Ist es das, was du meinst? Ich hatte jetzt nur das SchoolClass-Modell angegeben...

Einstweilen vielen Dank - ich werde meine Modells nochmal auf logische Fehler hin abklopfen; womöglich werden wirklich Relationen falsch gesetzt. Auch wenn das - für mich - in der Migration erstmal so aussieht, als ob EFCore das so umgesetzt hätte, wie es meiner Meinung nach sein sollte (aber die kann ja falsch sein).

Gruß
vorph

PS: Du bist Dir hoffentlich bewusst, dass die Migrationen in Sqlite nur begrenzt funktionieren, da Sqlite kein Schema Change unterstützt.

16.806 Beiträge seit 2008
vor 3 Jahren

Die Migration ist nur dazu da, dass das Datenbank-Schema gepflegt wird.

  • Es wird erstellt, wenn es nicht da ist
  • Es wird aktualisiert, wenn sich Deine Modelle ändern (Achtung: das ist eben in Sqlite nicht möglich, aber mit anderen Datenbanken wie dem SQL Server; das ist eine Einschränkung von Sqlite - nicht EF).

Das Modell ist aber dazu da, dass der EF Core Context weiß, wie die Welt aussieht.

OK - was genau bedeutet "Relations-Konfiguration im Modell"?

Du musst EF mitteilen, wie Deine Relationen aussehen und was bei gewissen Aktionen passieren soll.
Gibst Du das nicht manuell an, dann "errät" die EF Core Engine, wie die Zusammenhänge sind. Dazu gibts ein festgelegtes Regelwerk an Namen, woran sich EF orientiert.

Beispiel:
Wenn die Klasse "SchoolClassData" heisst dann sucht EF Core, wenn nichts anderes mitgeteilt wurde automatisch nach einer Eigenschaft, die die Id sein könnte.
Standardmäßig einfach nach "Id" sowie nach "<EntityName>Id" - daher funktioniert auch Dein SchoolClassDataId obwohl die eigentliche Deklaration sogar falsch ist.
Normalerweise sollte die Annotation ein [Key] sein und nicht [Required]; denn ein Key ist immer auch [Required].
EF Core fügt, wenn Du gewisse Dinge nicht manuell bekannt gibst, zB. Foreign Keys, automatisch hinzu, die Du im Code selbst nicht siehst aber in der Datenbank. Bei Dir siehst Du das zB. als SchoolClassInternalsSchoolClassInternalDataId

Du hast aber absolut korrekt erklärt, wie die Zusammenhänge von Id, Properties und Objekten sind.

An dieser Stelle der Hinweis, dass Annotations ohnehin nicht der empfohlene Weg in EF sind (und auch nie waren), sondern solche Informationen über den ModelBuilder bekannt gemacht werden sollen.

Ich persönlich habe aber ehrlich gesagt noch nie ein Projekt gesehen, bei dem auf Dauer die Relationen ohne Mapping funktioniert haben.
Früher oder später (meist früher) gabs Issues mit den ganzen Hidden Fields und Migrations etc - daher verwendet auch keiner meiner Projekte Annotations, Hidden Fields oder EF Core Migrations.

Weiß nicht, ob es hier passt, aber kannst du da kurz bissl ins Detail gehen?

Der Context (hier DataBaseContext) merkt sich den Zustand Deiner Datenbank-Entitäten.
Das heisst, wenn Du eine Entität (hier zB. SchoolClassData) aus der Datenbank holst und Werte veränderst, dann erkennt das der Context bei entsprechenden Aktionen.

Gleichzeitig erkennt der Context auch viele andere Dinge, zB. wenn Du eine neue Entität hinzufügst etc.
Bei gewissen Dingen muss man aber den Context gezielt informieren, damit das funktioniert.

Die Idee hier ist auch, dass eine Entität immer so lange lebt, wie der Kontext. Wenn Du Entitäten über verschiedene Contexte verwendest, dann mag das EF das nicht so sehr; EF Core aber etwas besser als EF Legacy.

Du erstellst nun bei jeder Aktion einen neuen DbContext; verwendest also eine Entität über mehrere Contexte hinweg: genau so wie man das eigentlich nicht tun soll.

Wenn Du nur einen einzigen Context hast, dann reicht einfach ein


var entity = context.SchoolClasses.Remove(_schoolClass);
context.SaveChanges();

zum Entfernen der Entität.

Die Remove Methode löscht nichts; das passiert erst beim Save.
Der einzige Sinn von der Remove-Methode ist, dass dem aktuellen Context gesagt wird, dass die Entität nun gelöscht werden soll.

  • Es fügt die Entität zum Context hinzu ( Entity.Attach() ), wenn die Entität nicht zum aktuellen Context gehört (bei Dir ist letzteres der Fall)
  • Es setzt den State auf Delete; genau das, was Du mit (entity.State = Microsoft.EntityFrameworkCore.EntityState.Deleted;) manuell machst. Im Endeffekt setzt Du hier doppelt den State auf Deleted, was unnötig ist.

Der Fehler selbst liegt meistens an einer ungültigen Relation oder, dass die Entität im Context selbst ungültig ist.

Meine Vermutung ist jetzt aber doch eher - beim Schreiben dieses Posts - dass die Entität "unsauber" ist.

Kann das sein, dass Du versuchst eine Entität zu löschen, die so gar nicht in der Datenbank ist oder verändert wurde?
Wenn ich mir die grundlegende Beschreibung des Fehlers (EF Core 2 INSERT error (The property 'ID' on entity type 'xxx' has a temporary value)) durchlesen, dann scheint der State der Entitäten im Context nicht korrekt zu sein.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 3 Jahren

Zunächst mal - vielen Dank, Abt! Das war jetzt mal verständlich erklärt: tatsächlich bin ich bei der ein oder anderen Sache von falschen Annahmen ausgegangen. Um nur eine zu nennen: ich hatte jetzt in diversen Videos, bzw. einem Blog mehrfach gesehen, wie Leute die mit SQLite arbeiten, sich einen Service auf einer static class aufbauen, weil ja der Zugriff eh nur "one at a time" ist. Bei genauerer Betrachtung macht es aber natürlich Sinn, nicht jedes Mal einen neuen Kontext zu erstellen.

Der Fehler selbst liegt meistens an einer ungültigen Relation oder, dass die Entität im Context selbst ungültig ist.

Meine Vermutung ist jetzt aber doch eher - beim Schreiben dieses Posts - dass die Entität "unsauber" ist.

Kann das sein, dass Du versuchst eine Entität zu löschen, die so gar nicht in der Datenbank ist oder verändert wurde?

Gerade letzteres ist für mich schwer zu sagen (mit meinem Kenntnisstand): das Erstellen eines SchoolClassData-Objekts, bzw. das Speichern in der Datenbank funktioniert so, wie es sollte - zumindest, wenn wir hier rein von der formalen Funktionalität sprechen. Das heißt ja nicht zwangsläufig, dass es auch "richtig" gemacht wurde.

Ich habe mir jedenfalls die Struktur und den Datensatz in der Datenbank im SQLite-Studio angesehen - die nötigen Tables sind da, auch die Ids sind korrekt vergeben, bzw. korrekt attribuiert.

Ich werde jetzt erst mal in guter alter Pen&Paper-Manier meine Modelle durchgehen, um rauszufinden, ob da evtl. wirklich was falsch "verdrahtet" ist.

16.806 Beiträge seit 2008
vor 3 Jahren

ich hatte jetzt in diversen Videos, bzw. einem Blog mehrfach gesehen, wie Leute die mit SQLite arbeiten, sich einen Service auf einer static class aufbauen

Dann bleibt mir da nichts anderes zu sagen, dass das sehr schlechte Beispiele sind.
static hat durchaus legitime Anwendungsfälle; aber nich für sowas.

Das fängt schon dabei an, dass der Code genau 0 testbar ist in solch einer Umsetzung und endet, dass der Context so nicht angesprochen werden soll.
Ich persönlich kein kein Tutorial, das das so macht. Die meist geklickten auf YouTube machen das zumindest auch nicht.

, weil ja der Zugriff eh nur "one at a time" ist.

Das hat damit nichts zutun.

Gerade letzteres ist für mich schwer zu sagen (mit meinem Kenntnisstand): das Erstellen eines SchoolClassData-Objekts, bzw. das Speichern in der Datenbank funktioniert so, wie es sollte - zumindest, wenn wir hier rein von der formalen Funktionalität sprechen.

Zeig das mal.

Ansonsten mach Dir mal ein Testprojekt.
[Tutorial] Vertrackte Fehler durch Vergleich von echtem Projekt mit minimalem Testprojekt finden

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 3 Jahren

Also, ich hänge total mit diesem Fehler^^

Nur, um noch mal auf die Fehlermeldung einzugehen:

The property 'SchoolClassData.SchoolClassDataId' has a temporary value while attempting to change the entity's state to 'Deleted'.

Was heißt 'temporary value'? Ich meine, ich kann das übersetzen, aber was soll das letztlich bedeuten? Mein Wert kommt ja in der Datenbank an und hat eine eindeutig zugewiesene ID, die laut SQLite Studio not null, unique, und Primary Key ist.

Auch den nächsten Part verstehe ich nicht

Either set a permanent value explicitly, or ensure that the database is configured to generate values for this property.

Was sollte hier ein permanenter Wert bringen? Gerade das will man doch nicht, oder? Und den zweiten Teil (...that the databse is configured to generate...) lese ich so, dass AutoIncrement 'true' sein muss. Auch das ist der Fall.

Aber es wird noch seltsamer:
Die erste Version meines Projekts hatte noch versucht ohne DataAccess-Layer auszukommen; dabei hatte der DataContext freien Zugriff auf die Models. Aber --> in dieser Version kann ich Daten speichern, löschen und updaten.
Dann habe ich Daten-Models angelegt, die nun an stelle der Models im DataContext sind. Und seit dem kommt dieser Fehler.

Ich habe seit Eröffnung dieses Posts noch diese Maßnahme ergirffen:

  • DataContext, Models und DataModels Zeile um Zeile mit dem Vorgänger-Modell verglichen aber keine Unterschiede gefunden. Einziger offensichtlicher Unterschied: es gab in Version 1 keine DataModels. Diese sind aber komplett getrennt von den Models, bzw. dem DataContext. Der DC weiß nicht einmal, dass andere Models außer den DataModels existieren!

  • zwei weitere TestProjekte angelegt, die dann aber letztlich alle denselben Fehler warfen. Dabei hatte ich die Datenbank, bzw. Datenbankobjekte komplett vereinfacht;

Bit es noch irgendeine Datei, einen Verweis o.ä. den ich sichten könnte? Nach meinem derzeitigen Kenntnisstand verlangt der Compiler einen nicht-temporären ID-Wert, den er aber bekommt. Zumindest hat mein Objekt eine eindeutige ID, wenn ich im Debug-Modus das Objekt unmittelbar vor der Ausführung von

var entity = context.SchoolClassesDM.Remove(_schoolClass)

ansehe.

16.806 Beiträge seit 2008
vor 3 Jahren

Poste mal bitte den vollständigen Code von SchoolClassData und SchoolClassInternalData .
Ich bin weiterhin der Meinung, dass Du ein Relationsfehler hast, wie es auch im GitHub Link beschrieben ist.

Was nämlich fischig aussieht ist, dass SchoolClassData die Parent-Relation ist, aber die FK von SchoolClassInternalData hält.
Würde man normalerweise anders rum machen.

Aber ich finde die Annotation-Attribute auch selten dämlich, daher verwende ich sie nie; aber ist meine persönliche Meinung.
Mit den Model Buildern hat man einfach einen viel besseren Überblick und eine viel bessere Kontrolle.
Du mischt ja leider beides.

PS: Du solltest an Deinen Entitätsnamen etwas arbeiten; ist schwer nachzuvollziehen, weil Deine Namen schon weit weg vom Standard/den Empfehlungen sind.

Compiler einen nicht-temporären ID-Wert

Der Compiler hat damit nichts zutun; die EF Engine will das so, aufgrund der Relationen.
Der Fehler wird auch durch den Context ausgelöst, nicht von der Datenbank oder gar Compiler; das siehst Du anhand des StackTrace.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 3 Jahren

Sorry, die Feiertage hielten mich davon ab mich näher mit dem Problem zu beschäftigen. Gestern Abend hatte ich aber eine Stunde Zeit gefunden und tatsächlich den Fehler gefunden!

Nochmal zu der Klasse, die den Fehler auswarf:


public static void DeleteSchoolClass(SchoolClassData _schoolClass)
        {
            using (var context = new DataBaseContext())
            {
                //Die nun folgende Zeile löst den Fehler aus
                var entity = context.SchoolClasses.Remove(_schoolClass);

                entity.State = Microsoft.EntityFrameworkCore.EntityState.Deleted;

                context.SaveChanges();
            }
        }

Das _schoolClass-Objekt konnte bei der Übergabe mit einem vollständigen Datensatz aufwarten - trotzdem dann der unerwartete Fehler. Und genau darin lag das Problem:

Die Fehlermeldung brachte mich auf eine vollkommen falsche Fährte: alles deutete auf einen Logik-Fehler oder eine Fehlkonfiguration der Datenbank oder einer Fehlkonfiguration durch EFC. Am meisten störte mich, dass ich anhand der Migrationen sehen konnte, dass die Datenbank so erstellt wurde, wie sie meiner Meinung nach hätte beschaffen sein müssen!
Die Lösung fand ich jetzt durch Zufall: vor der Übergabe erstelle ich ein neues SchoolClass-Objekt, das alle Daten enthält, die an die DB übergeben werden sollen. Die SchoolClassId war 0, die SchoolClassInternalsId war 0. Als ich dann gestern händisch ein Objekt in die DB eingegeben hatte, stellte ich fest, dass die vergebenen Ids aber jeweils 1 waren.
Wo lag der Fehler? Ich hatte das Datenobjekt an der falschen Stelle übergeben, so dass die Ids (die laut Deklaration ja nicht null sein durften) auf den Wert 0 gesetzt wurden. Diesen Verweis gab es in der DB natürlich nicht.

Mit anderen Worten: es wurde versucht ein Objekt aus der DB zu löschen, dessen Ids nicht vorhanden waren.

Aber ich finde die Annotation-Attribute auch selten dämlich, daher verwende ich sie nie; aber ist meine persönliche Meinung.
Mit den Model Buildern hat man einfach einen viel besseren Überblick und eine viel bessere Kontrolle.

Das kann ich - jetzt - vollkommen verstehen und nachvollziehen! Ich werde das in der finalen Version auch dementsprechend ändern - die Annotations habe ich erst vor kurzem gefunden, als ich erfuhr, dass es ansonsten Probleme bei der Performance geben kann. So wird für ein string-Property z. B. der maximale Speicherbedarf für string reserviert, was zwar für meine kleine App völlig unerheblich ist, aber ich finde, es ist schon wichtig das zu wissen.

Danke, für den Support, Abt! Vielleicht ist es ja auch für dich eine Neuigkeit, dass diese Fehlermeldung durch nicht vorhandene Ids geworfen werden kann?

viele Grüße und schon mal alles Gute für's neue Jahr!

16.806 Beiträge seit 2008
vor 3 Jahren

Dass es entweder am Dirty State oder an einem Config Error liegen muss; das war mir bewusst - nur was davon aus der Ferne is halt schwer 😉
Aber gut, dass Du es gelöst hast und danke, dass Du Feedback gegeben hast.

Aber ja, die Fehlermeldungen von EF sind nicht sooo super.
Ich hab auch immer wieder Excecptions (vor allem mit komplexen Queries (zB. nested sub queries mit projections, da passierts mir oft), die Fehler werfen, die keinerlei Verbindung zum eigentllichen Fehler am Ende haben.