使用Linq PredicateBuilder连接字符串成员进行文本搜索

时间:2016-07-03 08:07:05

标签: c# entity-framework linq predicatebuilder

我首先使用EntityFramework数据库使用REST WebAPI。所有代码都是从EDMX文件,实体,存储库类和API控制器等生成的。

我添加了一些过滤功能,允许用户通过查询字符串添加条件,该字符串转换为LinqKit PredicateBuilder / Linq表达式,用于在命中数据库时过滤结果。

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

这将返回User.FirstName成员中具有“Rog”的所有用户。这使用PredicateBuilder动态构建适当的Linq表达式,然后将其用作Where的{​​{1}}子句。

例如:

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 搜索'Rog'的匹配项,这样我就可以搜索'Roger Sm'并获得名字= Roger和姓氏= Smith的结果。

如果我使用流利的方式查询first name + last name,它将会是:

DbSet

我正在努力的是创建一个users.Where(u => (u.FirstName + " " + u.LastName).Contains("Rog")); 表达式,它将动态处理字符串成员predicate / linq的串联。

2 个答案:

答案 0 :(得分: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");

答案 1 :(得分: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)));
}