Laden...

[gelöst] EF 6: DbSet<MyEntity>.Add arbeitet nicht wie erwartet [==> Verständnisproblem]

Letzter Beitrag vor 10 Jahren 4 Posts 1.800 Views
[gelöst] EF 6: DbSet<MyEntity>.Add arbeitet nicht wie erwartet [==> Verständnisproblem]

verwendetes Datenbanksystem: LocalDB

Wenn ich einem DbSet ein neues Objekt zufüge, dann müsste es doch sofort, auch vor dem Speichern, in diesem DbSet zu finden sein; und so ist es auch bei meinen paar wenigen EF-Anwendungen bisher immer gewesen.

Jetzt auf einmal klappt das nicht mehr. Hier ist Test-Code, so primitiv es ging:


    public class Detail
    {
        public int DID { get; set; }
        public string DName { get; set; }

        public override string ToString()
        {
            return String.Format("{0} (ID = {1})", DName, DID);
        }
    }

    public class DetailMap : EntityTypeConfiguration<Detail>
    {
        public DetailMap()
        {
            HasKey(d => d.DID);
            Property(d => d.DName).IsRequired().HasMaxLength(50);
        }
    }

    public class Shopping : DbContext
    {
        public DbSet<Detail> Details { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            modelBuilder.Configurations.Add(new DetailMap());
        }
    }

    class Program
    {
        static string ShowDetails(Shopping context)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Bisher {0} Bedingungen\n", context.Details.Count());
            foreach (Detail d in context.Details)
            {
                sb.AppendLine(d.ToString());
            }
            return sb.ToString();
        }

        static void PrintPropertyValues(DbPropertyValues values)
        {
            foreach (var propertyName in values.PropertyNames)
            {
                Console.WriteLine(" - {0}: {1}", propertyName, values[propertyName]);
            }
        }

        static void PrintChangeTrackingInfo(Shopping _context, Detail entity)
        {
            var entry = _context.Entry<Detail>(entity);
            Console.WriteLine(entry.Entity.DName);
            Console.WriteLine("State: {0}", entry.State);
            Console.WriteLine("\nCurrent values:");
            PrintPropertyValues(entry.CurrentValues);
            Console.WriteLine("\nDatabase values:");
            // Exception: GetDatabaseValues cannot be used for entities in the Added state   <--- (1)
            // PrintPropertyValues(entry.GetDatabaseValues());
        }

        static void Main(string[] args)
        {
            using (Shopping context = new Shopping())
            {
                Detail neu = new Detail { DName = "XXX" };
                context.Details.Add(neu);
                PrintChangeTrackingInfo(context, neu);
                Console.WriteLine(ShowDetails(context));    <--- (2)
                context.SaveChanges();
            }
            Console.WriteLine("Weiter mit Enter ...");
            Console.ReadLine();
        }
    }
}


(1) Die Exception widerspricht m.E. der Dokumentation, in der es zu DbEntityEntry.GetDatabaseValues() heißt: "If the entity is not found in the database then null is returned."

(2) Hier werden nur die Detail-Sätze ausgegeben, die schon am Anfang in der Datenbank waren, der neue (mit DName XXX) fehlt und wird auch nicht mitgezählt.

Die EF-Version ist 6.1.1; es scheint eher nicht an dieser Version zu liegen. Ich habe in einem anderen Programm EF auch auf die neueste Version gebracht, ohne dass vergleichbare Probleme aufgetreten wären.

Was in dieser Vereinfachung nicht zu sehen ist: Normalerweise müsste ich ja frisch zugefügte Entities sofort als Navigationseigenschaften in Entities aus anderen Klassen benutzen können. Der Versuch führt zu einer Exception.

SaveChanges() speichert alles richtig.

Was mache ich da plötzlich falsch?

Dass nicht alle Exceptions in der Dokumentation gelistet sind, das ist leider normal.
null wird dazu nur zurückgegeben, wenn keine unbehandelte Ausnahme, wie in diesem Fall, auftritt.
Es wäre hilfreich, wenn Du auch den ExceptionTyp mit nennen würdest und nicht nur die Meldung.
Schau Dir auch [Hinweis] Wie poste ich richtig? Punkt 6, an: verwende bitte die Error-Tags.

Zum Problem: es ist ein Verständisproblem Deinerseits.
Es findet in GetDatabaseValues eine Überprüfung statt, ob der State der Entity auf "Added" steht. Wenn ja, dann gibt es eine Exception.
Das hat den Grund, dass zwar die Entität bekannt ist (und damit gar kein null zurück gegeben werden kann) aber im AddedState es eben nur CurrentValues gibt, aber keine DatabaseValues.
Siehe dazu:

Added: the entity is being tracked by the context but does not yet exist in the database

Nach dem SaveChanges() würde es funktionieren, weil sich hier auch der State der Entität auf Unchanged verändert.

Das ist der Funktionsweise des EF geschuldet.

Ok, damit wäre mein Punkt (1) geklärt.

Es bleibt (2): warum werden in der Funktion ShowDetails die neuen Sätze nicht angezeigt? Dass sie noch keine ID haben, ist klar, sollte aber nichts ausmachen (bzw. es müsste 0 angezeigt werden). Ein Objekt wird einer Auflistung zugefügt, danach werden alle Elemente dieser Auflistung angezeigt und das neue Objekt ist nicht darunter. Was ist da los?

Was in dieser Vereinfachung nicht zu sehen ist: Normalerweise müsste ich ja frisch zugefügte Entities sofort als Navigationseigenschaften in Entities aus anderen Klassen benutzen können. Der Versuch führt zu einer Exception.

Das braucht eine Präzisierung: wenn ich mit der Entity selbst direkt weiter arbeite, klappt das schon. Nur wenn ich sie mit Hilfe eines geeigneten Suchkriteriums, hier DName, im DbSet suche, bekomme ich eine InvalidOperationException: die Sequenz enthält keine Elemente. Da dürfte aber die genaue Art der Exception davon abhängen, was genau ich mit dem Suchergebnis anfange, hier (mit dem eben vergebenen Wert für DName im String p):

           
 Detail f = context.Details.First(d => d.DName == p);

Es sieht aus, als gehöre eine Entity im Zustand Added zwar zum Kontext, aber nicht zu ihrem zuständigen DbSet, obwohl sie genau diesem hinzugefügt wurde. Kann das so sein? Soll das so sein? Und wenn ja, wo ist es dokumentiert?

Es sieht aus, als gehöre eine Entity im Zustand Added zwar zum Kontext, aber nicht zu ihrem zuständigen DbSet, obwohl sie genau diesem hinzugefügt wurde. Kann das so sein? Soll das so sein? Und wenn ja, wo ist es dokumentiert?

Jedenfalls mal im (elektronischen) Bücherschrank: Lerman, Julia & Miller, Rowan, Programming Entity Framework: DbContext. O'Reilly Media 2012.

Because a query is sent to the database to find the items in a DbSet, iterating a DbSet will only contain items that exist in the database. Any objects that are sitting in memory waiting to be saved to the database will not be returned. To ensure added objects are included you can use the techniques described in Querying Local Data.

Lesen bildet, aber nur immer wieder lesen hilft wirklich. In der MSDN-Dokumentation bin ich nicht auf diese Aussage gestoßen, aus welchen Gründen immer.