2013-08-05 7 views
5

Betrachten Sie eine Datenbank mit mehreren Tabellen, die zuerst mit Entity Framework-Code erstellt wurden. Jede Tabelle enthält einen anderen Objekttyp, aber ich möchte aus Gründen der Erweiterbarkeit eine einzige generische Abfrage-Builder-Klasse erstellen. Bisher als Rahmen für diese Klasse habe ich eine generische Klasse als so bestimmt als Wrapper für Linq to SQL zu handeln:Wie erstelle ich einen C# Generic Linq Querier?

public class DBQuerier<T> 
    where T : class 
{ 
    DbSet<T> relation; 

    public DBQuerier(DbSet<T> table) 
    { 
     relation = table; 
    } 

    public bool Exists(T toCheck); 
    public void Add(T toAdd); 
    public T (Get Dictionary<String, Object> fields); 
    public bool SubmitChanges(); 
    public void Update(T toUpdate, Dictionary<String, Object> fields); 
    public void Delete(T toDelete); 
} 

Mein Problem kommt an der ersten Hürde, wenn sie versuchen, um zu prüfen, ob ein Datensatz existiert weil ich nicht zwischen generischem Typ T und einem Objekttyp konvertieren kann, mit dem ich versuche zu arbeiten. Wenn ich Basis Linq:

public bool Exists(T toCheck) 
{ 
    return (from row in relation 
     where row.Equals(toCheck) 
     select row).Any(); 
} 

Eine Laufzeitausnahme tritt auf, wenn SQL nicht mit alles andere als primitive Typen auch funktionieren kann, wenn ich IComparable implementieren und meine eigene Equals bezeichnen, die ein einzelnes Feld vergleicht. Lambda-Ausdrücke scheint näher zu kommen, aber dann bekomme ich wieder Probleme mit SQL nicht in der Lage mehr als primitive Typen selbst zu behandeln, obwohl mein Verständnis war, dass Expression.Equal sie gezwungen, die Klasse vergleichbare Funktion zu verwenden:

public bool Exists(T toCheck) 
{ 
    ParameterExpression T1 = Expression.Parameter(typeof(myType), "T1"); 
    ParameterExpression T2 = Expression.Parameter(typeof(myType), "T2"); 
    BinaryExpression compare = Expression.Equal(T1, T2); 
    Func<T, T, bool> checker = 
     Expression.Lambda<Func<T, T, bool>> 
      (compare, new ParameterExpression[] { T1, T2 }).Compile(); 
    return relation.Where(r => checker.Invoke(r, toCheck)).Any(); 
} 

Der Ausdruck tree wurde so entworfen, dass ich später eine switch-Anweisung hinzufügen konnte, um die Abfrage nach dem Typ zu erstellen, den ich mir ansehen wollte. Meine Frage ist: Gibt es einen viel einfacheren/besseren Weg, dies zu tun (oder zu beheben, was ich bisher versucht habe), da die einzigen anderen Optionen, die ich sehen kann, eine Klasse für jede Tabelle zu schreiben sind (nicht so einfach zu erweitern) oder überprüfen Sie jede Seite der Datensatzanwendung (möglicherweise schrecklich langsam, wenn Sie die gesamte Datenbank übertragen müssen)? Entschuldigung, wenn ich so sehr grundlegende Fehler gemacht habe, da ich lange überhaupt nicht mit viel davon gearbeitet habe, danke im Voraus!

+0

Beachten Sie, dass Ihre 'Exists'-Methode nur eine umbenannte' Contains'-Methode ist, ohne dass eine Implementierung so gut ist. – Servy

+0

Wenn Sie eine Primärschlüsselspalte haben, können Sie immer die Methode 'relation.Find (Id)' verwenden, um zu überprüfen, ob sie existiert. – Nilesh

Antwort

2

Entity Framework ist unwahrscheinlich, mit Ihrer benutzerdefinierten linq zu arbeiten, es ist ziemlich starr in den Befehlen, die es unterstützt. Ich werde ein wenig wandern und es ist Pseudocode, aber ich fand zwei Lösungen, die für mich funktionierten.

Ich verwendete zuerst generics Ansatz, wo mein generischer Datenbanksuchender ein Func<T, string> nameProperty akzeptierte, um auf den Namen zuzugreifen, den ich abfragen würde. EF hat viele Überladungen für den Zugriff auf Mengen und Eigenschaften, so dass ich das schaffen könnte, indem ich c => c.CatName übergebe und das nutze, um auf die Eigenschaft in einer generischen Weise zuzugreifen. Es war aber ein bisschen unordentlich, also:

Ich später umgestaltet, um Schnittstellen zu verwenden.

Ich habe eine Funktion, die eine Textsuche für jede Tabelle/Spalte durchführt, die Sie an die Methode übergeben.

Ich habe eine Schnittstelle namens INameSearchable erstellt, die einfach eine Eigenschaft enthält, die die zu suchende Namenseigenschaft sein wird. Ich habe dann meine Entity-Objekte erweitert (sie sind Teilklassen), um INameSearchable zu implementieren. So habe ich eine Entität namens Cat, die eine CatName Eigenschaft hat. Ich habe die Schnittstelle zu return CatName; als Name-Eigenschaft der Schnittstelle verwendet.

Ich kann dann eine generische Suchmethode where T : INameSearchable erstellen und es wird die Name Eigenschaft, die meine Schnittstelle ausgesetzt. Ich benutze dann einfach das in meiner Methode, um die Abfrage durchzuführen, z. (Pseudocode aus dem Speicher!)

doSearch(myContext.Cats);

und in der Methode

public IEnumerable<T> DoSearch<T>(IDbSet<T> mySet, string catName) 
{ 
    return mySet.Where(c => c.Name == catName); 
} 

Und ganz schön, es erlaubt mir zu allgemein etwas zu suchen.

Ich hoffe, das hilft.

+0

Ich bin an Ihrer Lösung interessiert. Könnten Sie so nett sein, Ihre Schnittstelle zu erläutern: INameSearchable und wie Sie es implementiert haben? Ich nehme an, dass Sie die EF Context-Klasse erweitern? welches würde jedes Mal überschrieben werden, wenn Sie das Modell aktualisieren? – DaniDev

+0

Schöne Lösung aber! Wäre greifbarer gewesen mit ein bisschen mehr Code, um es ausdrücklicher zu machen. bis 1 aber :) – Irfan

6

Kompilieren Sie es nicht. Func<T,bool> bedeutet "führen Sie dies im Speicher", während Expression<Func<T,bool>> bedeutet "behalten Sie die logische Idee, was dieses Prädikat ist", die Frameworks wie Entity Framework erlaubt, das in die Abfrage zu übersetzen.

Als Randbemerkung, ich glaube nicht, dass Sie a.Equals(b) zur Abfrage tun Entity Framework ermöglicht, so dass Sie a.Id == b.Id

0

tun müssen, wenn Sie verwenden möchten EntityFramework Sie primitive Typen verwenden. Der Grund dafür ist, dass Ihr LINQ-Ausdruck in eine SQL-Anweisung umgewandelt wird. SQL weiß nichts über Objekte, IComparables, ...

Wenn Sie es nicht in SQL benötigen, müssen Sie zuerst die Abfrage gegen SQL ausführen und dann im Speicher filtern. Sie können das mit den Methoden tun, die Sie gerade verwenden