2013-11-01 47 views
11

我試圖通過創建一般處理過濾的擴展方法來清理我的代碼。是否有可能使用linq反射到實體?

這是我正在嘗試清理的代碼。

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

這顯然很重複。所以我嘗試創建一個擴展方法,通過使用反射的屬性進行過濾。這是方法。

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

,它是被稱爲像這樣:

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

但是,LINQ的執行時,我得到一個錯誤,指出「的GetValue」是不是在LINQ公認的實體。

那麼,有沒有其他的方式來清理它,還是我不得不離開它醜陋?

回答

15

從技術上講,是的,你可以這樣做,但你需要自己構造Expression傳遞給Where

也就是說,不要接受屬性作爲字符串值,而應考慮接受Expression<Func<T, string>>作爲參數,以便您有編譯時支持來驗證所選對象是否有效。

我們將從一個表示泛型部分的表達式開始;它將代表具有* 2 *參數的函數,即給定屬性的對象和值。然後,我們可以用我們在實際方法參數中定義的屬性選擇器替換第二個參數的所有實例。

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

並且此方法使用函數將給定表達式中的一個表達式的所有實例替換爲另一個表達式的所有實例。的實現是:

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

如果你真的要接受屬性名作爲一個字符串然後就用下面的替換newSelector定義:

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

是的,這做到了。謝謝。現在我只需再次重新閱讀您的答案來理解它。 – Smeegs

+1

@Smeegs它可以幫助調試一些實際值,並看看創建的表達式(甚至可能有一些中間值我不存儲在變量中)。它有助於將其可視化。表達式通常具有明智的'ToString'實現。 – Servy

+0

你正在使用哪個程序集/名稱空間?我看到SqlFunctions在'EntityFramework.SqlServer'和'System.Data.Entity'中......我假設這些是不同的,對吧? – David

相關問題