2013-11-01 17 views
11

Próbuję trochę uprzątnąć mój kod, tworząc metodę rozszerzenia, aby ogólnie obsługiwać filtrowanie.Czy można użyć odbicia z linq do encji?

Oto kod, który próbuję wyczyścić.

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); 

Jest wyraźnie powtarzalny. Próbowałem więc stworzyć metodę rozszerzenia, która była filtrowana przez właściwość za pomocą odbicia. Oto metoda.

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 
        ); 
    } 
} 

i to się nazywać tak:

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

Ale pojawia się błąd, gdy wykonuje LINQ, stwierdzając, że "GetValue" nie jest rozpoznawana w LINQ do jednostki.

Czy jest jakiś inny sposób, aby to posprzątać, czy muszę go brzydko zostawić?

Odpowiedz

15

Technicznie, tak, możesz to zrobić, ale musisz samodzielnie zbudować Expression, aby przejść do Where.

To powiedziawszy, zamiast akceptować właściwość jako wartość ciągu, należy rozważyć zamianę parametru Expression<Func<T, string>> jako parametru, aby zapewnić obsługę czasu kompilacji w celu sprawdzenia, czy wybrany obiekt jest poprawny.

Zaczniemy od wyrażenia, które reprezentuje część ogólną; będzie reprezentował funkcję z * dwoma * parametrami, obiektem i wartością danej właściwości. Następnie możemy zastąpić wszystkie wystąpienia tego drugiego parametru selektorem właściwości, który zdefiniowaliśmy w parametrach faktycznej metody.

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; 
} 

Ta metoda używa funkcji zastępującej wszystkie wystąpienia jednego wyrażenia innym w danym wyrażeniu. Wdrożenie to jest:

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); 
} 

Jeśli naprawdę chcą przyjąć nazwę właściwości jako ciąg potem po prostu zastąpić definicję newSelector z następujących czynności:

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

Tak, że to zrobił. Dzięki. Teraz muszę jeszcze raz przeczytać twoją odpowiedź jeszcze kilka razy, aby to zrozumieć. – Smeegs

+1

@Smeegs Może pomóc w debugowaniu z pewnymi rzeczywistymi wartościami i przyjrzeć się utworzonym wyrażeniom (a nawet niektórym pośrednim wartościom, które nie przechowuję w zmiennych). Pomaga to go zwizualizować. Wyrażenia generalnie mają sensowne implementacje "ToString". – Servy

+0

Z jakich złożeń/obszarów nazw używasz? Widzę SqlFunctions jest zarówno w 'EntityFramework.SqlServer' i' System.Data.Entity' ... Zakładam, że są różne, prawda? – David

Powiązane problemy