Laden...

EntityFramework 5 - komplexe Queries

Erstellt von Cannon vor 10 Jahren Letzter Beitrag vor 10 Jahren 1.098 Views
C
Cannon Themenstarter:in
282 Beiträge seit 2008
vor 10 Jahren
EntityFramework 5 - komplexe Queries

Das EF macht einen vieles leichter. Die Abfragen sind einfach und bringen schnell Ergebnisse. Aber wenn ich komplexe Abfragen machen möchte, dann geht das nicht. Beispiel:

_dbSet ist ein IDbSet<T>


public IEnumerable<T> Query(Expression<Func<T, bool>> filter)
{
	return _dbSet.Where(filter).ToList();
}

funktioniert:


var phones = WorkUnit.Phones.Query(e => e.UserId == CurrentUser.UserId);

funktioniert nicht, da nur primitive Typen unterschützt werden:


var phones = WorkUnit.Phones.Query(e => e.User == CurrentUser);

Das ist nur ein vereinfachtes Beispiel, da es mir nur um den Sinn an sich geht. Das Ziel soll ja auch sein sich von den Ids zu lösen und es gibt ja auch nicht immer ein Id-Spalte. Nämlich dann, wenn es um reine Jointabelle (Pure Join Table, PJT) handelt.

Habt ihr da eine Idee zu?

M
402 Beiträge seit 2005
vor 10 Jahren

Hi...

ja ich hab dazu eine Idee. 😉

Die Queries gehen ja alle an den SQL-Server oder eine andere unterstützte relationale Datenbank.

Aus diesem Grund müssen alle Expressions irgendwie in einen puren SQL-Query "übersetzt" werden. Diesen könnte man dann z.b. mit dem Management-Studio auch direkt am SQL-Server ausführen.

Wie sollte nun "Query(e => e.User == CurrentUser);" in einen validen SQL-Query umgewandelt werden?

742 Beiträge seit 2005
vor 10 Jahren

@M@TUK: Wieso sollte das nicht gehen? Bei Linq muss der Expression Tree komplett analysiert werden und EF könnte ohne Probleme erkennen, dass es sich hier um eine Fremdschlüsselbeziehung handelt und das Query entsprechend bauen.

ALLERDINGS wäre das nur eine Zusatzoption. Es ist keine wirkliche Option z.B. bei iener Webseite, bei der die ID des Users per Url/Session/Cookie übergeben wird, erst den User abzufragen, nur weil man die Nachrichten des Users abfragen möchte.

C
Cannon Themenstarter:in
282 Beiträge seit 2008
vor 10 Jahren

Es geht aber nicht immer eine Id zu übergeben. Nämlich genau dann, wenn keine Id existiert, die ich abfragen könnte, da die Tabelle EmployeeOffice nur eine Join-Tabele ohne Ids ist und die in EF auch ausgeblendet wird.


var employees = WorkUnit.Employees.GetAll();
foreach (Employee employee in employees) {
	if (employee.Offices.Contains(CurrentOffice)) Employees.Add(employee);
}

In diesem Beispiel, muss ich alle Employees aus der Datenbank laden, nur um ein paar zu wählen, die diesem bestimmten Office zugeordnet sind. Sinnvoll wäre es die doch irgendwie in eine Query zu packen. Allerdings ist natürlich die Frage, ob das überhaupt in SQL so umsetzbar wäre, ohne alle Datensätze erst einmal zu laden.

Sinnvoll wäre doch:


var employees = WorkUnit.Employees.Query(e => e.Offices == CurrentOffice);

oder:


var employees = WorkUnit.Employees.Query(e => e.Offices.Contains(CurrentOffice));

Aber selbst, wenn ich die zerlege fällt mir keine Möglichkeit ein zu verhindern alle Datensätze zu laden.

16.834 Beiträge seit 2008
vor 10 Jahren

Die Queries gehen ja alle an den SQL-Server oder eine andere unterstützte relationale Datenbank.

Nein, nicht zwangsläufig.
Sobald ToList() oder irgendeine andere Materialisierung ausgeführt wird, wird nicht mehr auf dem SQL Server gefiltert.

entitySet.ToList().Where(....) // Ausführung auf dem Client
entitySet.Where(xxx).ToList() // Ausführung auf dem Server
entitySet.Where(xxx).ToList().Where(...) // Ausführung des 1. Where auf dem Server, 2. auf dem Client.

Die Implementierung

public IEnumerable<T> Query(Expression<Func<T, bool>> filter)
 {
     return _dbSet.Where(filter).ToList();
 } 

ist also nicht sauber und würde ich sogar als fatal bezeichnen.
Warum? Nach einem Query(....) wird jede weitere Einschränkung durch ein Where auf dem Client ausgeführt und nicht in der DB. Damit unnötige Ladezeiten ezc.

Ob ein Contains auf dem Server oder auf dem Client ausgeführt wird, das weiß ich aktuell nicht. Ich hatte bei der Verwendung mit dem EF immer auf die referenzierende ID (also den FK) geprüft statt mit den fertigen Methoden ein Objekt zu vergleichen. Ich weiß wie gesagt nicht, ob das so intelligent umgesetzt wurde (ich bezweifels ehrlich gesagt).

Wenn man Code First nutzt ist aber das nachträgliche Hinzufügen der FK-Spalte ins Modell kein Problem und aus mancher Ansicht des ADO.NET Teams sogar empfohlen, da es bei gewissen Operationen Geschwindigskeitvorteile hat (zB Massinserts).

C
Cannon Themenstarter:in
282 Beiträge seit 2008
vor 10 Jahren

Das klingt alles logisch. Aber irgendwann muss ich ToList() aufrufen, sonst erhalte ich Fehlermeldungen, dass irgendwelche DataReader noch geöffnet sind - vermutlich weil der Lesevorgang noch nicht abgeschlossen ist.

Grundsätzlich geht es ja auch nicht darum mehrere Queries verschachtelt aufzurufen, sondern die komplexen überhaupt nutzen zu können.

Die FK-Spalte kann ich nicht zufügen, da im Model die "tabelle" ja garnicht mehr direkt vorhanden ist - es handelt sich hier wie gesagt nur um eine reine Join table, die keien FK enthalten darf. Gibt es den zu dem Thema noch weitere Quellen, wo ich mal lesen könnte?

Mir erschließt sich nur der Sinn nicht ganz. Wenn das EF so "bequem" entwickelt wurde, dass man eben diese Join-Tables ausblendet, wenn man Tabellen mit n:m - Beziehungen anspricht, dann stelle ich mir die Frage, wie man die überhaupt jemals filtern können soll, ohne alle Daten zu laden und die auf diem Client einzeln durch zu iterieren.

16.834 Beiträge seit 2008
vor 10 Jahren

Also ich kann Dir nicht ganz folgen, was Du meinst.
Auch wenn ich EF aus anderen unbequemen Gründen nicht mehr nutze: Queries auf n:m funktionieren absolut problemlos. Das hätte ich gemerkt, wenn das nicht sauber funktionieren würde.

C
Cannon Themenstarter:in
282 Beiträge seit 2008
vor 10 Jahren

Die Queries gehen ja auch problemlos. Aber eben nur mit den bereits beschriebenden Einschränkungen.

Gibt es denn eine gute Alternative zum EF? Es gibt ja doch immer wieder neue Haken, z.B.: CodeFirst oder DbFirst -> Handarbeit ist immer angesagt, wenn man die Db aus dem Model generieren will oder das Model aus der Db, weil entweder StoreGeneratedPattern im Model nachträglich gesetzt werden muss oder in dem Db-Skript der Default-Wert für den Primärschlüssel gesetzt werden muss, wenn man mit GuiIds arbeitet.

16.834 Beiträge seit 2008
vor 10 Jahren

Ich kenne keinen ORM, der keine Haken hat.