2016-07-03 24 views
1

我有一個REST WebAPI,首先使用EntityFramework數據庫。所有代碼產生斷EDMX文件,實體,存儲庫的類和使用Linq PredicateBuilder進行文本搜索的連接字符串成員

我增加了一些過濾功能,其允許用戶添加通過查詢字符串擊中分貝時轉換爲LinqKit PredicateBuilder/Linq表達式過濾結果條件API控制器等。

e.g. /api/Users?FirstName_contains=Rog 

這將返回User.FirstName成員中'Rog'的所有用戶。這使用PredicateBuilder來動態構建適當的Linq表達式,然後將其作爲Where子句用於DbSet

例如:

var fieldName = "FirstName"; 
var value = "Rog"; 

var stringContainsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) }); 

var parameter = Expression.Parameter(typeof(User), "m"); 
var fieldAccess = Expression.PropertyOrField(parameter, fieldName); 
var fieldType = typeof(User).GetProperty(fieldName, BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public).PropertyType; 

var expression = Expression.Lambda<Func<User, bool>>(Expression.Call(fieldAccess, stringContainsMethod, Expression.Constant(value, fieldType)) 
    , parameter) 

var andPredicate = PredicateBuilder.True<User>(); 
andPredicate = andPredicate.And(expression); 

var query = Db.Users 
    .AsQueryable() 
    .AsExpandable() 
    .Where(andPredicate); 

現在的問題。我希望客戶能夠根據成員構成匹配結果。

e.g. /api/Users?api_search[FirstName,LastName]=Rog 

即搜索first name + last name爲「羅格」的比賽,這樣我就可以搜索「羅傑釤」,並獲得頭名=羅傑和姓=史密斯的結果。

如果我用流利的查詢DbSet它會是什麼樣子:

users.Where(u => (u.FirstName + " " + u.LastName).Contains("Rog")); 

我所用是創造一個predicate/linq表達式來處理字符串成員FirstName + " " + LastName動態的串聯掙扎。

+0

儘管t-sql中有一個CONCAT函數,但我不認爲EF(特別是L2SQL)功能支持這種翻譯。您應該首先使用.ToList()將所有條目取出,然後在內存中執行選擇。 – DevilSuichiro

+0

嗯真的嗎?從我的旅行中,我很肯定上面流利的例子將轉化爲'WHERE FirstName +''+ LastName LIKE'%Rog%'的T-SQL。 –

+0

爲什麼不'u => u.FirstName.Contains(query)|| u.LastName.Contains(查詢)'? – haim770

回答

1

PredicateBuilder。

可以使用由EF支持string.Concat方法調用來生成字符串連接表達式:

static Expression<Func<T, string>> GenerateConcat<T>(IEnumerable<string> propertyNames) 
{ 
    var parameter = Expression.Parameter(typeof(T), "e"); 
    // string.Concat(params string[] values) 
    var separator = Expression.Constant(" "); 
    var concatArgs = Expression.NewArrayInit(typeof(string), propertyNames 
     .SelectMany(name => new Expression[] { separator, Expression.PropertyOrField(parameter, name) }) 
     .Skip(1)); 
    var concatCall = Expression.Call(typeof(string).GetMethod("Concat", new[] { typeof(string[]) }), concatArgs); 
    return Expression.Lambda<Func<T, string>>(concatCall, parameter); 
} 

該字符串包含可通過簡單的string.Contains方法調用來產生謂詞:

static Expression<Func<T, bool>> GenerateContains<T>(Expression<Func<T, string>> member, string value) 
{ 
    var containsCall = Expression.Call(member.Body, "Contains", Type.EmptyTypes, Expression.Constant(value)); 
    return Expression.Lambda<Func<T, bool>>(containsCall, member.Parameters); 
} 

結合他們連同你的例子:

var predicate = GenerateContains(GenerateConcat<User>(new[] { "FirstName", "LastName" }), "Rog"); 
+0

謝謝伊萬 - 輝煌。我已經改變了接受的答案,是的你是正確的問題是真的只是創建適當的lambda表達式。 PredicateBuilder只是處理我的各種表達式的鏈接,以針對集合執行。 –

0

試試下面的(我沒有測試它針對數據庫):是不是真的在這裏需要

public class User 
{ 
public string FirstName { get; set; } 
public string LastName { get; set;} 
} 
void Main() 
{ 
    List<User> users = new List<User> { 
          new User { FirstName = "john", LastName = "smith" }, 
          new User { FirstName = "siler", LastName = "johnston" } }; 
    string searchName = "ja smi"; 
    String[] terms = searchName.Split(' '); 
    var items = users.Where(x => terms.Any(y => x.FirstName.Contains(y)) 
           || terms.Any(y => x.LastName.Contains(y))); 
} 
+0

謝謝。如果我無法實現連接,拆分術語並執行基於Any/Or的搜索肯定會退步。現在我想到了它,我可以試着看看它是否能給出足夠好的結果。 –

+0

我已經接受了這一點作爲前進的方向,儘管技術上已經可以使用我的功能,儘管如此冗長,例如, '/ API /用戶?or_firstName_contains = JA&or_firstName_contains = SMI&or_lastName_contains = JA&or_lastName_contains = smi'。我知道它一定是可能的,因爲Linq已經使用常規的流暢或查詢語法來處理它,但是現在這應該讓我越過這條線。 –