Wie zum Hohn die Grenzen von Entity Framework ist die Implementierung von IQueryable
Ich bin derzeit am schreiben von unit tests für meine repository-Implementierung in einem MVC4 Anwendung. Um zu verspotten die Daten Kontext, ich begann durch die Annahme einige Ideen aus dieser Beitrag, aber ich habe jetzt entdeckt, einige Einschränkungen zu machen, dass sich mir die Frage, ob es überhaupt möglich richtig zu mock IQueryable
.
Insbesondere habe ich einige Situationen, in denen die tests übergeben, aber der code nicht in der Produktion, und ich habe nicht in der Lage gewesen zu finden, jeden Weg zu verspotten, das Verhalten, die Ursachen dieser Fehler.
Beispielsweise den folgenden Codeausschnitt wird verwendet, um wählen Sie Post
Entitäten, die sich innerhalb einer vordefinierten Liste von Kategorien:
var posts = repository.GetEntities<Post>(); //Returns IQueryable<Post>
var categories = GetCategoriesInGroup("Post"); //Returns a fixed list of type Category
var filtered = posts.Where(p => categories.Any(c => c.Name == p.Category)).ToList();
In meiner Testumgebung, ich habe versucht, Spott posts
mit dem fake DbSet
Umsetzung oben erwähnt, und auch durch die Schaffung einer List
von Post
Instanzen und deren Umwandlung in IQueryable
mit der AsQueryable()
- Erweiterung-Methode. Beide Ansätze arbeiten unter Bedingungen testen, aber der code nicht wirklich in der Produktion, mit der folgenden Ausnahme:
System.NotSupportedException : Unable to create a constant value of type 'Category'. Only primitive types or enumeration types are supported in this context.
Obwohl LINQ Fragen wie diese sind einfach genug zu lösen, die wirkliche Herausforderung ist, Sie zu finden, da Sie sich nicht offenbaren sich in der test-Umgebung.
Bin ich unrealistisch, zu erwarten, dass ich verspotten das Verhalten von Entity Framework ist die Implementierung von IQueryable
?
Vielen Dank für Eure Ideen,
Tim.
- Es wird nicht eine unit-test-aber was, wenn Sie haben ToString() (auf der DbQuery) oder ToTraceString() (auf ObjectQuery)? Es wird dump der SqlQuery entsprechend Ihrer Abfrage-Bedeutung, die Sie gehen Sie durch die ganze EF-Abfrage pipeline, aber nicht senden Sie die Abfrage an die Datenbank. Es sollte zeigen Fälle wie diese.
- Vielen Dank - das ist ein großer Schritt in die richtige Richtung, obwohl es wäre schön, wenn ich könnte irgendwie automatisieren.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich denke, es ist sehr sehr schwer, wenn gar unmöglich, zu verspotten Entity Framework-Verhalten. Vor allem, weil es erfordern würde, die profunde Kenntnis aller Besonderheiten und Ausnahmefälle, bei linq-to-entites unterscheidet sich von linq-to-objects. Wie Sie sagen: die wirkliche Herausforderung ist, Sie zu finden. Lassen Sie mich die drei wichtigsten Bereiche ohne Anspruch auf auch nur annähernd erschöpfend:
Fällen, in denen die Linq-to-Objects erfolgreich ist, und Linq-to-Entities nicht:
.Select(x => x.Property1.ToString()
. LINQ to Entities nicht erkennt die Methode 'System.String ToString ()' - Methode... Dies gilt für fast alle Methoden, die in nativen .Net-Klassen und natürlich, um die eigenen Methoden. Nur ein paar .Net-Methoden angezeigt werden, übersetzt werden in SQL. Sehen CLR-Methode, Kanonische Funktion-Mapping. Als der EF 6.1,ToString
wird unterstützt durch die Art und Weise. Aber nur der parameterlosen überladung.Skip()
ohne vorhergehendeOrderBy
.Except
undIntersect
: kann produzieren monströse Abfragen, werfen Sie einen Teil Ihrer SQL-Anweisung ist zu tief geschachtelt. Schreiben Sie die Abfrage um, oder brechen Sie in kleinere Abfragen.Select(x => x.Date1 - x.Date2)
: DbArithmeticExpression Argumente muss einen numerischen verbreitet..Where(p => p.Category == category)
: Nur primitive Typen oder Aufzählungstypen unterstützt in diesem Zusammenhang.Nodes.Where(n => n.ParentNodes.First().Id == 1)
: Die Methode der 'Ersten' kann nur verwendet werden, als eine Letzte Abfrage Betrieb.context.Nodes.Last()
: LINQ to Entities nicht erkennt die Methode '...die Letzte...'. Dies gilt für viele andereIQueryable
Erweiterung Methoden. Sehen Unterstützte und nicht unterstützte LINQ-Methoden..Select(x => new A { Property1 = (x.BoolProperty ? new B { BProp1 = x.Prop1, BProp2 = x.Prop2 } : new B { BProp1 = x.Prop1 }) })
: Der Typ 'B' wird in zwei strukturell unvereinbar Initialisierungen innerhalb einer einzigen LINQ to Entities-Abfrage... von hier.context.Entities.Cast<IEntity>()
: Unable to cast der Typ 'Person' zu geben 'IEntity'. LINQ to Entities unterstützt nur casting EDM primitive oder Aufzählungstypen..Select(p => p.Category?.Name)
. Mit null propagation in einem Ausdruck wirft CS8072 Ausdruck Baum lambda enthalten möglicherweise keine null-Vermehrung-operator. Diese vielleicht bekommen Feste einen Tag.Fällen, in denen die Linq-to-Objekte schlägt fehl, und Linq-to-Entities gelingt:
.Select(p => p.Category.Name)
: wennp.Category
null ist L2E gibt null zurück, aber L2O wirft der Objektverweis wurde nicht auf eine Instanz eines Objekts. Dies kann nicht behoben werden mit null propagation (siehe oben).Nodes.Max(n => n.ParentId.Value)
mit einigen null-Werten fürn.ParentId
. L2E gibt einen max Wert, L2O wirft Nullable Objekt muss einen Wert haben.EntityFunctions
(DbFunctions
als der EF-6) oderSqlFunctions
.Fällen, in denen beide gelingen/scheitern, Verhalten sich aber anders:
Nodes.Include("ParentNodes")
: L2O hat keine Implementierung beinhalten. Es wird ausgeführt und der return-Knoten (wennNodes
istIQueryable
), aber ohne übergeordneten Knoten.Nodes.Select(n => n.ParentNodes.Max(p => p.Id))
einige leereParentNodes
Sammlungen: beide scheitern, aber mit verschiedenen Ausnahmen.Nodes.Where(n => n.Name.Contains("par"))
: L2O-und Kleinschreibung, L2E, hängt von der Datenbank-Sortierung (oft nicht case-Sensitiv).node.ParentNode = parentNode
: mit einer bidirektionalen Beziehung, in L2E dies wird auch fügen Sie den Knoten der Knoten-Auflistung des übergeordneten Elements (Beziehung Korrektur). Nicht in L2O. (Siehe Unit-Tests ein zwei-Wege-EF Beziehung)..Select(p => p.Category == null ? string.Empty : p.Category.Name)
: das Ergebnis ist das gleiche, aber die erzeugte SQL-query enthält auch den null-check und möglicherweise schwieriger zu optimieren.Nodes.AsNoTracking().Select(n => n.ParentNode
. Dieser ist sehr tricky!. MitAsNoTracking
EF schafft neueParentNode
Objekte für jedeNode
, so kann es zu Duplikaten. OhneAsNoTracking
EF wiederverwendet bestehendenParentNodes
, weil jetzt die entity-state-manager und entity-Schlüssel beteiligt sind.AsNoTracking()
genannt werden kann, in der L2O, aber es nicht tun, so wird es nie einen Unterschied mit oder ohne.Und was ist mit Spott lazy/eager loading und die Wirkung von Kontext-Lebens-Zyklus auf lazy loading exceptions? Oder die Wirkung von einigen Abfrage-Konstrukte auf die Leistung (wie Konstrukte, die trigger N+1 SQL-Abfragen). Oder Ausnahmen durch doppelte oder fehlende entity keys? Oder Verhältnis-Korrektur?
Meine Meinung: niemand fake, die. Der alarmierende Bereich ist, wo L2O gelingt und L2E ausfällt. Nun, was ist der Wert von grün-unit-tests? Es wurde gesagt, dass EF nur zuverlässig getestet werden, die in integration tests (z.B. hier) und ich eher Zustimmen.
Jedoch, das bedeutet nicht, dass wir vergessen sollten über unit-tests in Projekten mit EF als Daten-layer. Es gibt Möglichkeiten, es zu tun, aber ich denke, nicht ohne Integrationstests.
.Select(x => new A { Property1 = (x.BoolProperty ? new B { BProp1 = x.Prop1, BProp2 = x.Prop2 } : new B { BProp1 = x.Prop1 }) })
: Der Typ 'B' wird in zwei strukturell unvereinbar Initialisierungen innerhalb einer einzigen LINQ to Entities-Abfrage, etc., etc. (von hier: stackoverflow.com/questions/10904375/...)Ich geschrieben habe, ein paar Unit-Tests mit
Entity Framework 6.1.3
mitMoq
und benutzte es, um zu überschreibenIQueryable
. Beachten Sie, dass alleDbSet
, die getestet werden sollen, muss markiert werden, wievirtual
. Beispiel von Microsoft selbst:Abfrage:
Einfügen:
Beispiel service:
Quelle: https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking