使用 Linq PredicateBuilder 连接字符串成员以进行文本搜索
Concatenate string members using Linq PredicateBuilder for text search
我有一个 REST WebAPI,首先使用 EntityFramework
数据库。所有代码都是从 EDMX
文件、实体、存储库 类 和 API 控制器等
生成的
我添加了一些过滤功能,允许用户通过查询字符串添加条件,查询字符串转换为 LinqKit PredicateBuilder / Linq
表达式,在访问数据库时过滤结果。
e.g. /api/Users?FirstName_contains=Rog
这将 return 所有在 User.FirstName
成员中具有 'Rog' 的用户。这使用 PredicateBuilder
动态构建适当的 Linq
表达式,然后用作针对 DbSet
的 Where
子句。
例如:
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
中搜索 'Rog' 的匹配项,这样我就可以搜索 'Roger Sm' 并得到名字 = Roger 和姓氏 = Smith 的结果。
如果我使用 fluent 查询 DbSet
,它看起来像:
users.Where(u => (u.FirstName + " " + u.LastName).Contains("Rog"));
我正在努力的是创建一个 predicate / linq
表达式,它将动态处理字符串成员 FirstName + " " + LastName
的连接。
尝试以下操作(我还没有针对数据库进行测试):
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)));
}
这里真的不需要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");
我有一个 REST WebAPI,首先使用 EntityFramework
数据库。所有代码都是从 EDMX
文件、实体、存储库 类 和 API 控制器等
我添加了一些过滤功能,允许用户通过查询字符串添加条件,查询字符串转换为 LinqKit PredicateBuilder / Linq
表达式,在访问数据库时过滤结果。
e.g. /api/Users?FirstName_contains=Rog
这将 return 所有在 User.FirstName
成员中具有 'Rog' 的用户。这使用 PredicateBuilder
动态构建适当的 Linq
表达式,然后用作针对 DbSet
的 Where
子句。
例如:
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
中搜索 'Rog' 的匹配项,这样我就可以搜索 'Roger Sm' 并得到名字 = Roger 和姓氏 = Smith 的结果。
如果我使用 fluent 查询 DbSet
,它看起来像:
users.Where(u => (u.FirstName + " " + u.LastName).Contains("Rog"));
我正在努力的是创建一个 predicate / linq
表达式,它将动态处理字符串成员 FirstName + " " + LastName
的连接。
尝试以下操作(我还没有针对数据库进行测试):
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)));
}
这里真的不需要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");