2010-06-17 19 views
5

Ich versuche, ein generisches Repository für meine Modelle zu erstellen. Derzeit habe ich 3 verschiedene Modelle, die keine Beziehung zwischen ihnen haben. (Kontakte, Notizen, Erinnerungen).Zugriff auf Eigenschaften über generischen Typparameter

class Repository<T> where T:class 
{ 
    public IQueryable<T> SearchExact(string keyword) 
    { 
     //Is there a way i can make the below line generic 
     //return db.ContactModels.Where(i => i.Name == keyword)   
     //I also tried db.GetTable<T>().Where(i => i.Name == keyword) 
     //But the variable i doesn't have the Name property since it would know it only in the runtime 
     //db also has a method ITable GetTable(Type modelType) but don't think if that would help me 
    } 
} 

In MainViewModel, nenne ich die Suchmethode wie folgt aus:

Repository<ContactModel> _contactRepository = new Repository<ContactModel>(); 

public void Search(string keyword) 
{ 
    var filteredList = _contactRepository.SearchExact(keyword).ToList(); 
} 

Lösung:

schließlich mit Ray Dynamic Expression Lösung ging:

public IQueryable<TModel> SearchExact(string searchKeyword, string columnName) 
{ 
    ParameterExpression param = Expression.Parameter(typeof(TModel), "i"); 
    Expression left = Expression.Property(param, typeof(TModel).GetProperty(columnName)); 
    Expression right = Expression.Constant(searchKeyword); 
    Expression expr = Expression.Equal(left, right); 
} 

query = db.GetTable<TModel>().Where(Expression.Lambda<Func<TModel, bool>>(expr, param)); 

Antwort

13

Schnittstelle Lösung

Wenn Sie Ihrem Objekt eine Schnittstelle hinzufügen können, können Sie das verwenden. Zum Beispiel könnten Sie definieren:

public interface IName 
{ 
    string Name { get; } 
} 

Dann könnte Ihr Repository deklariert werden als:

class Repository<T> where T:class, IName 
{ 
    public IQueryable<T> SearchExact(string keyword) 
    { 
    return db.GetTable<T>().Where(i => i.Name == keyword); 
    } 
} 

Alternate-Interface-Lösung

Alternativ Sie die "where" auf SearchExact Methode setzen könnte durch Verwenden eines zweiten generischen Parameters:

class Repository<T> where T:class 
{ 
    public IQueryable<T> SearchExact<U>(string keyword) where U: T,IName 
    { 
    return db.GetTable<U>().Where(i => i.Name == keyword); 
    } 
} 

Dadurch kann die Repository-Klasse mit Objekten verwendet werden, die IName nicht implementieren, während die SearchExact-Methode nur mit Objekten verwendet werden kann, die IName implementieren.

Reflection Lösung

Wenn Sie keine Iname-ähnliche Oberfläche, um Ihre Objekte hinzufügen können, können Sie Reflexion statt:

class Repository<T> where T:class 
{ 
    static PropertyInfo _nameProperty = typeof(T).GetProperty("Name"); 

    public IQueryable<T> SearchExact(string keyword) 
    { 
    return db.GetTable<T>().Where(i => (string)_nameProperty.GetValue(i) == keyword); 
    } 
} 

Dies ist langsamer als eine Schnittstelle verwendet, aber manchmal Es ist der einzige Weg.

Weitere Hinweise zur Interface-Lösung und warum Sie es vielleicht

In Ihrem Kommentar verwenden Sie erwähnen, dass Sie nicht über eine Schnittstelle verwenden können, aber nicht erklären, warum. Du sagst: "Nichts ist in den drei Modellen vorhanden. Daher denke ich, dass es unmöglich ist, eine Schnittstelle daraus zu machen." Aus Ihrer Frage habe ich verstanden, dass alle drei Modelle eine "Name" -Eigenschaft haben. In diesem Fall ist es ist möglich, eine Schnittstelle auf allen drei zu implementieren. Implementieren Sie einfach die Schnittstelle wie gezeigt und ", IName" zu jeder Ihrer drei Klassendefinitionen. Dadurch erhalten Sie die beste Leistung für lokale Abfragen und SQL-Generierung.

Auch wenn die fraglichen Eigenschaften nicht alle als "Name" bezeichnet werden, können Sie die nterface-Lösung dennoch verwenden, indem Sie jeder Eigenschaft eine "Name" -Eigenschaft hinzufügen und deren Getter und Setter auf die andere Eigenschaft zugreifen.

Expression Lösung

Wenn die Iname Lösung nicht funktionieren, und Sie müssen die SQL-Umwandlung zu arbeiten, können Sie dies tun, indem Sie Ihre LINQ-Abfrage mit Ausdrücken zu bauen. Dies ist mehr Arbeit und ist für die lokale Verwendung deutlich weniger effizient, wird aber gut zu SQL konvertieren. Der Code würde wie folgt sein:

class Repository<T> where T:Class 
{ 
    public IQueryable<T> SearchExact(string keyword, 
            Expression<Func<T,string>> getNameExpression) 
    { 
    var param = Expression.Parameter(typeof(T), "i"); 
    return db.GetTable<T>().Where(
       Expression.Lambda<Func<T,bool>>(
        Expression.Equal(
        Expression.Invoke(
         Expression.Constant(getNameExpression), 
         param), 
        Expression.Constant(keyword), 
        param)); 
    } 
} 

und es würde thusly genannt werden:

repository.SearchExact("Text To Find", i => i.Name) 
+0

Quer denken, ich mag es. Linq2Sql bedeutet, dass sie wahrscheinlich einige Nachbearbeitung verwenden müssen, um die Schnittstelle auf die Klassen anzuwenden. – Spence

+0

Der zweite Ansatz (Constraint auf Methode) scheint mir besser, würde aber wahrscheinlich dann die Methode SearchExactName umbenennen. –

+0

@Spence - es gibt zwei Möglichkeiten, dies zu tun; Sie können entweder partielle Klassen verwenden, um '' IName' hinzuzufügen, oder Sie können die DBML bearbeiten und den globalen Basistyp für Entitäten festlegen (falls alle Ihre Typen dies implementieren sollten). –

3

Rays Methode ist recht gut, und wenn Sie die Möglichkeit haben, eine Schnittstelle auf jeden Fall die bessere hinzuzufügen jedoch, wenn Aus irgendeinem Grund können Sie keine Schnittstelle zu diesen Klassen hinzufügen (Teil einer Klassenbibliothek, die Sie nicht bearbeiten können oder etwas). Sie könnten auch in Betracht ziehen, einen Func zu übergeben, der ihm sagt, wie er den Namen erhält.

Beispiel:

class Repository<T> 
{ 
    public IQueryable<T> SearchExact(string keyword, Func<T, string> getSearchField) 
    { 
    return db.GetTable<T>().Where(i => getSearchField(i) == keyword); 
    } 
} 

Sie würden dann rufen müssen, um es als:

var filteredList = _contactRepository.SearchExact(keyword, cr => cr.Name).ToList(); 

Anders als diese beiden Optionen, die Sie immer in die Reflexion aussehen könnte die Eigenschaft Name ohne Schnittstelle für den Zugriff auf , aber das hat den Nachteil, dass es keine Kompilierzeitprüfung gibt, die sicherstellt, dass die Klassen, die Sie übergeben, tatsächlich eine Name-Eigenschaft haben und den Nebeneffekt haben, dass LINQ nicht in SQL übersetzt wird und die Filterung stattfindet .NET (Bedeutung der SQL-Server konnte getroffen werden m Erz als benötigt wird).

Sie können auch eine dynamische LINQ-Abfrage verwenden, um diesen SQL-Side-Effekt zu erzielen, aber es hat dieselben nicht typsicheren Probleme, die oben aufgeführt sind.

+0

+1 für die Verwendung der alternativen funktionalen Ansatz, anstatt OO-Ansatz von Ray-Lösung. –

+0

@Tim Schneider: +1 Das sollte die Lösung sein. Lassen Sie mich heute bestätigen. – Amsakanna

+0

Ja, Func ist ein weiterer guter Weg, es zu tun. Beachten Sie, dass es den gleichen Nachteil hat wie die Reflexion in Bezug auf die Übersetzung: Da 'getSearchField' bereits ein Delegat ist (ein Zeiger auf den tatsächlichen Maschinencode), kann LINQ2SQL sie nicht analysieren, sodass die Filterung in .NET erfolgt. Der Interface-Ansatz wird erfolgreich in SQL übersetzt, aber um dies zu übersetzen, müssen Sie einen Ausdruck übergeben, aber dies wird die lokale Ausführung verlangsamen. –

Verwandte Themen