使用 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 表达式,然后用作针对 DbSetWhere 子句。

例如:

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