2013-11-01 18 views
11

Ich versuche, meinen Code etwas zu bereinigen, indem ich eine Erweiterungsmethode für die generische Filterung erstelle.Ist es möglich, Reflexion mit LINQ zu Entität zu verwenden?

Hier ist der Code, den ich versuche zu reinigen.

var queryResult = (from r in dc.Retailers select r); 
if (!string.IsNullOrEmpty(firstName)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(firstName.Trim(), ex.FirstName.Trim()) > 0); 
if (!string.IsNullOrEmpty(lastName)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(lastName.Trim(), ex.LastName.Trim()) > 0); 
if (!string.IsNullOrEmpty(companyName)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(companyName.Trim(), ex.CompanyName.Trim()) > 0); 
if (!string.IsNullOrEmpty(phone)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(phone.Trim(), ex.Phone.Trim()) > 0); 
if (!string.IsNullOrEmpty(email)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(email.Trim(), ex.Email.Trim()) > 0); 
if (!string.IsNullOrEmpty(city)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(city.Trim(), ex.City.Trim()) > 0); 
if (!string.IsNullOrEmpty(zip)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(zip.Trim(), ex.Zip.Trim()) > 0); 
if (!string.IsNullOrEmpty(country)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(country.Trim(), ex.Country.Trim()) > 0); 
if (!string.IsNullOrEmpty(state)) 
    queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(state.Trim(), ex.State.Trim()) > 0); 

Es ist eindeutig sehr repetitiv. Also habe ich versucht, eine Erweiterungsmethode zu erstellen, die nach einer Eigenschaft gefiltert wurde. Hier ist die Methode.

var queryResult = (from r in dc.Retailers select r); 
queryResult.FilterByValue("firstname", firstName); 

Aber ich eine Fehlermeldung erhalten, wenn die Linq ausgeführt wird, die besagt, dass "GetValue" in Linq zu Einheit nicht erkannt:

public static void FilterByValue<T>(this IQueryable<T> obj, string propertyName, string propertyValue) 
{ 
    if (!string.IsNullOrEmpty(propertyValue)) 
    { 
     obj = 
      obj.Where(
       ex => 
        SqlFunctions.PatIndex(propertyValue.Trim(), (string)typeof(T).GetProperty(propertyName, 
         BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase).GetValue(ex)) > 0 
        ); 
    } 
} 

und es ist zu wie so genannt werden.

Also, gibt es einen anderen Weg, um dies aufzuräumen, oder muss ich es hässlich verlassen?

Antwort

15

Technisch gesehen, ja, Sie könnten es tun, aber Sie müssten die Expression selbst erstellen, um an Where zu übergeben.

Das heißt, anstatt die Eigenschaft als String-Wert zu akzeptieren, sollten Sie stattdessen einen Expression<Func<T, string>> als Parameter akzeptieren, so dass Sie Unterstützung für die Kompilierung haben, um zu überprüfen, ob das ausgewählte Objekt gültig ist.

Wir beginnen mit einem Ausdruck, der den generischen Teil darstellt; es wird eine Funktion mit * zwei * Parametern, dem Objekt und dem Wert der gegebenen Eigenschaft darstellen. Wir können dann alle Instanzen dieses zweiten Parameters durch den Eigenschaftsselektor ersetzen, den wir in den Parametern der tatsächlichen Methode definiert haben.

public static IQueryable<T> FilterByValue<T>(
    this IQueryable<T> obj, 
    Expression<Func<T, string>> propertySelector, 
    string propertyValue) 
{ 
    if (!string.IsNullOrEmpty(propertyValue)) 
    { 
     Expression<Func<T, string, bool>> expression = 
      (ex, value) => SqlFunctions.PatIndex(propertyValue.Trim(), 
       value.Trim()) > 0; 

     var newSelector = propertySelector.Body.Replace(
      propertySelector.Parameters[0], 
      expression.Parameters[0]); 

     var body = expression.Body.Replace(expression.Parameters[1], 
      newSelector); 
     var lambda = Expression.Lambda<Func<T, bool>>(
      body, expression.Parameters[0]); 

     return obj.Where(lambda); 
    } 
    else 
     return obj; 
} 

Und diese Methode verwendet eine Funktion, um alle Instanzen eines Ausdrucks mit einem anderen in einem bestimmten Ausdruck zu ersetzen. Die Umsetzung das heißt:

public class ReplaceVisitor : ExpressionVisitor 
{ 
    private readonly Expression from, to; 
    public ReplaceVisitor(Expression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 
    public override Expression Visit(Expression node) 
    { 
     return node == from ? to : base.Visit(node); 
    } 
} 

public static Expression Replace(this Expression expression, 
    Expression searchEx, Expression replaceEx) 
{ 
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression); 
} 

Wenn Sie wirklich den Namen der Eigenschaft als String zu übernehmen wollen, dann ersetzen Sie einfach die Definition von newSelector mit den folgenden:

var newSelector = Expression.Property(expression.Parameters[0], propertyName); 
+1

Yep, das es tat. Vielen Dank. Jetzt muss ich deine Antwort noch ein paar Mal wiederholen, um es zu verstehen. – Smeegs

+1

@Smeegs Es kann helfen, es mit einigen tatsächlichen Werten zu debuggen und einen Blick auf die Ausdrücke zu werfen, die erzeugt werden (und möglicherweise sogar einige der Zwischenwerte, die ich nicht in Variablen ablege). Es hilft, es zu visualisieren. Ausdrücke haben im Allgemeinen sinnvolle 'ToString'-Implementierungen. – Servy

+0

Welche Baugruppen/Namespaces verwenden Sie? Ich sehe SqlFunctions ist sowohl in "EntityFramework.SqlServer" und "System.Data.Entity" ... Ich nehme an, dass diese unterschiedlich sind, oder? – David

Verwandte Themen