Laden...

EF Core Fehler beim Erzeugen neuer Datensätze

Erstellt von dscNRW vor 2 Jahren Letzter Beitrag vor 2 Jahren 446 Views
D
dscNRW Themenstarter:in
30 Beiträge seit 2021
vor 2 Jahren
EF Core Fehler beim Erzeugen neuer Datensätze

Verwendetes Datenbanksystem: MSSQL Developer Edition

Hallo zusammen,

ich habe seit ein paar Tagen ein Problem wo ich trotz Recherche nicht weiterkomme.

Ich habe ein EF Modell und die entsprechenden Klassen auf Basis einer SQL-Datenbank aufgebaut.
Wenn ich nun einen neuen Datensatz hinzufügen möchte erhalte ich folgenden Fehler:

Fehlermeldung:
System.InvalidOperationException: "The value of 'Documents.ID' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known."

Ich kann nicht wirklich ausmachen welche Tabelle das Problem verursacht - vermute aber die Tabelle Matchcodes.....

Die Klassen zu den beiden Entitäten "Matchcodes" und "Documents" sehen wie folgt aus:

Matchcodes


    public class Matchcodes
    {
        [Key]
        public int ID { get; set; }

        public string Matchcode { get; set; }

        public int DocumentsID { get; set; }

        public virtual Documents Documents { get; set; }
    }

Documents


    public class Documents
    {
        private ILazyLoader LazyLoader { get; set; }

        public  Documents() 
        {
            _matchcodes = new HashSet<Matchcodes>();
            _documentNotices = new HashSet<DocumentNotices>(); 
        }
        private Documents(ILazyLoader lazyloader)
        {
            LazyLoader = lazyloader;
        }
        [Key]
        public int ID { get; set; }

        public string DocumentName { get; set; }

        public int DocumentType { get; set; }

        public decimal DocumentSize { get; set; }

        public byte[] DocumentBinary { get; set; }

        public string DocumentPath { get; set; }

        public string DocumentOrigin { get; set; }

        public int FolderID { get; set; }

        public int Revision { get; set; }

        public DateTimeOffset? CreationDate { get; set; }

        public DateTime? DocumentDate { get; set; }
        public int OriginalDocument { get; set; }

        public bool ForAll { get; set; }

        public int UserID { get; set; }

        [NotMapped]

        public bool Concat { get; set; }
        [NotMapped]

        public bool DoOCR { get; set; }
        public FileTypes FileType { get; set; }

        public DocumentsFullText DocumentsFullText { get; set; }

        private ICollection<Matchcodes> _matchcodes;
        public ICollection<Matchcodes> Matchcodes
        {
            get => LazyLoader.Load(this, ref _matchcodes);
            set => _matchcodes = value;
        }

        private ICollection<DocumentNotices> _documentNotices;

        public ICollection<DocumentNotices> DocumentNotices
        {
            get => LazyLoader.Load(this, ref _documentNotices);
            set => _documentNotices = value;
        }

        [NotMapped]
        public string FileTypeAsString
        {
            get
            {
                return App.ModelContext.FileTypes.Where(x => x.ID == DocumentType).FirstOrDefault().FileType;
            }
        }
    }

Im Kontext selbst habe ich via Fluent API folgendes gesetzt:


    protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Users>()
                .HasOne(u => u.ProgramUsage)
                .WithOne()
                .HasForeignKey<Users>(x => x.ID);

            modelBuilder.Entity<Documents>()
                .HasOne(d => d.FileType)
                .WithOne()
                .HasForeignKey<Documents>(x => x.ID);

            modelBuilder.Entity<Documents>()
             .HasMany<Matchcodes>(x => x.Matchcodes)
             .WithOne()
             .HasForeignKey(x=>x.ID);

            modelBuilder.Entity<Documents>()
             .HasOne(d => d.DocumentsFullText)
             .WithOne()
             .HasForeignKey<Documents>(x => x.ID);
        }
    }

Ich habe schon ein paar Änderungen im Kontext durchgeführt und ein paar Dokus dazu gelesen aber ich vermute mal, dass ich irgendetwas grandios Missverstanden habe.

Habt ihr einen Denkanstoß?

Gruß,
D.

P.S.:
Falls Abt das lesen sollte - DateTime? sollte nicht verwendet werden - ist mir dank dir bekannt und wird auch noch geändert - aber zunächst möchte ich das andere Problem lösen 🙂

4.931 Beiträge seit 2008
vor 2 Jahren

Kann es sein, daß du Primary Key (PK) und Foreign Key (FK) (für Documents) durcheinandergebracht hast, s.a. Beispiele in The Fluent API HasKey Method und The Fluent API HasForeignKey Method?

PS: Die Entitäten-Klassen sollten besser in der Einzahl benannt sein, also User, MatchCode, Document.

16.807 Beiträge seit 2008
vor 2 Jahren

Wichtiger Punk bei EF Core: Mische keine Annotations mit der Fluent API.
Das ist fürchterlich für die Übersicht und kann sich schnell inhaltlich widersprechen, dann is die Bugsuche groß.
Recommendation ist: Fluent API.

Das Konstrukt sieht nach einem bösen Workaround aus, wo Du aus einer Entiätsinstanz auf eine globale Klasse zugreifen willst.


return App.ModelContext.FileTypes.Where(x => x.ID == DocumentType).FirstOrDefault().FileType;

Warum ist das da drin? Eine Entität sollte eine "dumme" Datenklasse sein und nichts ausführen.
Im schlimmsten Fall kann das zu Race Condition und Locks führen.

Ich sehe auch eine Verletzung in den Keys, aber kann auch an einem generell falschen Umgang mit Entitäten liegen, den wir in dem Codestück aber nicht sehen.

Ansonsten noch die Hinweise:
Arbeiten mit Nullable-Verweistypen – EF Core
Lazy Loading zugehöriger Daten – EF Core
Implementieren der Infrastrukturpersistenzebene mit Entity Framework Core

D
dscNRW Themenstarter:in
30 Beiträge seit 2021
vor 2 Jahren

PS: Die Entitäten-Klassen sollten besser in der Einzahl benannt sein, also User, MatchCode, Document.

Danke dir TH69 - werde ich noch korrigieren. Die Links lese ich mir gerade durch.

Wichtigster Punk bei EF Core: Mische keine Annotations mit der Fluent API.
Das ist fürchterlich für die Übersicht. Recommendation ist: Fluent API.

Habe die Annotations raus genommen und "versuche" das nun über die Flusend API zu realisieren.

Das Konstrukt sieht nach einem bösen Workaround aus, wo Du aus einer Entiätsinstanz auf eine globale Klasse zugreifen willst.

return App.ModelContext.FileTypes.Where(x => x.ID == DocumentType).FirstOrDefault().FileType;
Copy
Warum ist das da drin? Eine Entität sollte eine "dumme" Datenklasse sein und nichts ausführen.

Habe ich rausgenommen - war auch nicht in Verwendung und sollte "nur" zum testen dienen.

Ich habe die IDs innerhalb der Entitäten nun vorläufig umbenannt - ich möchte das aber auch noch nach TH69's Vorschlag in den Singular ändern.
Würde aber vorher gerne das Problem verstehen und lösen damit ich damit nicht am laufenden Band auf die Nase falle.

In wie weit meinst du Verletzung der Keys? Könntest du mir da einen kleinen Tipp geben?

16.807 Beiträge seit 2008
vor 2 Jahren

Erstmal technisch: Du arbeitest mit integer Keys, die i.d.R. durch die Datenbank erzeugt werden, nicht in der Applikation.
Das bedeutet, dass die "Id"-Eigenschaften leer (0) sind, wenn Du ein neues Objekt erzeugst. Die Id wird erst mit dem Speichern und der Antwort der Datenbank gesetzt.

Du kannst also keine Ids verwenden, bis Du die Entität gespeichert hast.
Irgendwo in Deinem Quellcode scheinst Du aber bereits mit den Werten der Id zu arbeiten (zB. Zuweisung auf einen FK in ein anderes Objekt), obwohl der Wert Id noch gar nicht feststeht.
Meistens, meine Erfahrung, liegt das daran, weil man Applikationslogik mit Datenbanklogik zu sehr vermischt.

Würde aber vorher gerne das Problem verstehen und lösen damit ich damit nicht am laufenden Band auf die Nase falle.

Basics wie ein Naming kannst Du trotzdem immer anpassen.
Macht es allen Beteiligten dann auch leichter.

D
dscNRW Themenstarter:in
30 Beiträge seit 2021
vor 2 Jahren

Hallo Abt,
hallo TH69,

danke für die Info 🙂.

Ich werde das Projekt nochmal neu anfangen - war Gott sei Dank noch nicht so weit.

Werde mir dabei eure Links und Ratschläge zu Herzen nehmen.