LINQ string 针对多个字段

假设我有一个表dataContext.Customer,其中包含以下字段

FName varchar LName varchar Phone varchar DOB datetime Address varchar 

该表填充了一些示例数据,让我们说:

  John | Smith | 3051112222 | 01/01/1978 | Roosevelt Av 787 Aron | Frank | 7871112222 | 01/01/1979 | Lambda Street 305 Dick | Bush | 9512221111 | 01/01/1980 | John Street 1 John | Allen | 7872222222 | 01/01/1981 | Liberty Av 555 

我们还有一个包含任意数量元素的字符串数组,例如:

  search[0] = "1978" search[1] = "John" 

我需要一个LINQ查询,它将使用“contains”或“any”(在SQL中表示LIKE)逐步比较表的每个字段与字符串数组中的每个项目,并仅返回与记录中所有给定条件匹配的行,基于之前的search []示例,LINQ查询应仅返回记录#1。

另一个例子可以是:

  search[0] = "Bush" search[1] = "111" search[2] = "John" 

并且只返回记录#3。 最后:

  search[0] = "John" 

记录#1,#3和#4应该返回(我认为这个想法很清楚)

有一个关于如何比较字符串[]与字段的问题: LINQ:实体字符串字段包含任何字符串数组

如果答案是50行C#例程,我更喜欢通过存储过程直接在数据库中解决这个问题。

如果有一些“reflection”技巧可以在执行查询时迭代dataContext.Customers上的所有字段(显然真实表没有5个字段),那将是非常棒的。

性能不是问题。

我很确定这不可能在单个LINQ系列中完成,因为多重匹配需要逻辑,但是问题永远不会伤害,更不用说学习任何新东西了:)

更新:好的,这是一个简单的SQL代码,可以完成任务。 请注意,为清晰起见,我已将搜索变量的数量减少到2。 在现实生活中,我们可以将参数数量限制为10个搜索参数。 我故意不使用函数(好吧,除了CONVERT)来保持SQL尽可能简单,看看是否有任何方法可以在LINQ中实现这一点。 这是SQL:

  declare @_SEARCH1 varchar(1000) select @_SEARCH1 = 'John' declare @_SEARCH2 varchar(1000) select @_SEARCH2 = '111' select * from CUSTOMER where FName + ' ' + LName + ' ' + Phone + ' ' + CONVERT(varchar, DOB, 101) + ' ' + Address like '%'+@_SEARCH1+'%' and FName + ' ' + LName + ' ' + Phone + ' ' + CONVERT(varchar, DOB, 101) + ' ' + Address like '%'+@_SEARCH2+'%' 

所以问题是,有没有办法编写一个能生成这个简单SQL的LINQ? (请注意,比较是通过’LIKE’在数据库中完成的,而不是在应用程序中)

更新2:尽管像Francisco那样的解决方案会生成“LIKE”语句,但它将无法进行比较。 将表中的所有数据提取到Web服务器的其他解决方案将正确匹配,但是完全不切实际。

RUNE FS的接受答案是最干净的解决方案,并将适用于任何领域。

假设’\ t’永远不会成为数据的一部分,您可以执行以下操作。 你当然可以替换任何其他角色。 有了这个假设,你可以这样做:

 public static IEnumerable Where(this IEnumerable sequence, string[] criteria){ var properties = typeof(T).GetProperties() .Where(p=>p.GetGetMethod() != null); return from s in sequence let text = properties.Aggregate("",(acc,prop) => acc + "\t" + prop.GetValue(s,null) ) where criteria.All(c => text.Contains(c)) select s; } 

编辑我最初没有包含用法,因为我在原始post中没有找到任何集合,但假设序列被定义为IEnumerabl并且可以作为变量db上的Persons属性进行访问。 代码看起来类似于:

 IEnumerable persons = db.Persons.Where(criteria); 

使用PredicateBuilder

 void Main() { var search = new string[] { "Romania","RO"}; var query = from c in countries.AllAny(search) orderby c.name select c; query.Dump(); } public static class QueryExtensions { public static IQueryable AllAny(this IQueryable query, string[] search) { var properties = typeof(T).GetProperties().Where(p => p.GetCustomAttributes(typeof(System.Data.Linq.Mapping.ColumnAttribute),true).Any()).Select(n=>n.Name); var andPredicate = PredicateBuilder.True(); foreach ( var term in search ) { var orPredicate = PredicateBuilder.False(); foreach (var property in properties ) orPredicate = orPredicate.Or(CreateLike(property,term)); andPredicate = andPredicate.And(orPredicate); } return query.Where(andPredicate); } private static Expression> CreateLike( PropertyInfo prop, string value) { var parameter = Expression.Parameter(typeof(T), "f"); var propertyAccess = Expression.MakeMemberAccess(parameter, prop); var toString = Expression.Call(propertyAccess, "ToString", null, null); var like = Expression.Call(toString, "Contains", null, Expression.Constant(value,typeof(string))); return Expression.Lambda>(like, parameter); } private static Expression> CreateLike( string propertyName, string value) { var prop = typeof(T).GetProperty(propertyName); return CreateLike(prop, value); } } // http://www.albahari.com/nutshell/predicatebuilder.aspx public static class PredicateBuilder { public static Expression> True () { return f => true; } public static Expression> False () { return f => false; } public static Expression> Or (this Expression> expr1, Expression> expr2) { var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast ()); return Expression.Lambda> (Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters); } public static Expression> And (this Expression> expr1, Expression> expr2) { var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast ()); return Expression.Lambda> (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters); } } 

更新此代码是以下查询的通用解决方案

 from c in countries where (c.name.ToString().Contains(search[0]) || c.name.ToString().Contains(search[1])) && (c.iso_code.ToString().Contains(search[0]) || c.iso_code.ToString().Contains(search[1])) /*&& ...*/ orderby c.name select c 

可以通过多种方式改进此代码。 对于示例,对于字符串属性,不需要在Contains之前调用ToString(这将生成convert(nvarchar)),我真的认为需要这个的人只想查看varchar,nvarchar列。

我不认为linq to sql可以有效地做到这一点,但是linq to objects可能会有所帮助,如果你可以在应用程序代码和数据库之间通过线路移动整个表,每次搜索一次。

第一步是获取一个DataReader,它将读取表中的所有记录。 使用datareader是很重要的,因为你可能不想用整个表填充内存(然后再次,也许你这样做 – 看看我关于缓存附近的缓存的说明)。

完成后,需要将其转换为IEnumerable以与linq-to-objects一起使用。 您可以使用简单的两行实用程序方法来完成此操作,如下所示:

 IEnumerable EnumerableFromDataReader(IDataReader reader) { while (reader.Read()) yield return reader; } 

我通常使用的实际代码有点复杂,但是我所做的可能与你的项目其余部分的结构不太一致,所以我现在就把这个简短的方法留给它。

一旦我们拥有了Enumerable,我们就可以利用linq的可组合特性来获得一些(相对)简单(或至少是简短的)代码,如下所示:

 IEnumerable SearchMyTable(string[] filters) { var results = EnumerableFromDataReader(GetMyTable()); foreach (string filter in filters) { results = results.Where(r => String.Join("",r.GetValues().Cast().ToArray()).Contains(filter)); } return results; } 

它不是单个linq查询,但它也不是50行代码。

忽略网络延迟,此代码的性能实际上非常好。 如果您希望在列加入后对全部或部分表进行一些缓存,则性能会非常惊人。

更新:此代码中存在(至少)一个缺陷。 对于每个filter,我将存活到该filter的所有行重新转换为字符串…即使我已经为之前的filter执行了此操作。 解决此问题的方法是首先执行投影以将行与字符串版本配对。 但是在我的时间晚上11点之后,我将保留代码,因为它代表了现在。 好消息是最终的固定代码应该是相同的长度:只需在第一行和最后一行添加一个.Select()调用,然后稍微更改一下foreach循环的中间部分。

我现在手头没有C#编译器,但我有这个想法:

像这样声明一个lambda表达式:

 public Expression> GetFilterFromString(string input) { return p=> p.FName.Contains(input) || p.LName.Contains(input) || p.Phone.Contains(input) || p.DOB.ToString().Contains(input) || p.Address.Contains(input) || } 

实现可能会根据您的需求而有所不同(例如连接所有字段,就像您对SQL查询所做的那样)。

然后在你的主要查询function:

 IQueryable customers = dataContext.Customers; foreach(string inputSearch in search) { customers = customers.Where(GetFilterFromString(inputSearch)); } IEnumerable results = customers.AsEnumerable(); 

我认为这种方法的主要优点是你必须声明一次GetFilterFromString。 希望这是你正在寻找的。

编辑:

好的,所以我读了你正在寻找的SQL语句(有点迟到……但无论如何)。 我认为很容易适应我的解决方案。 我们将不得不稍微调整lambda表达式:

 public Expression> GetFilterFromString(string input) { return p => (p.FName + " " + p.LName + " " + p.Phone + " " + p.DOB.ToString() + " " + p.Address) .Contains(input) } 

可能是我可能会做的是,首先生病从DB拥有名字的记录与我拥有的数组项目相比。

一旦我得到表数据的子集(假设数据库表的结构相同,数组结构也意味着数组[0]总是名字),那么我搜索我正在寻找的模式记忆。

我相信这不是你希望的完全解决方案。 但是,lemme进一步思考,意味着我对更多的想法开放的人们:)

类似于Francisco的答案,但只有一个where子句的应用程序:

 string[] search = new string[] { "Bush", "111", "John" }; var customers = new[] { new {FName = "Dick", Surname = "Bush", Phone = "9512221111", DOB = new DateTime(1980,01,01), Address = "John Street 1" }, new {FName = "John", Surname = "Smith", Phone = "3051112222", DOB = new DateTime(1978,01,01), Address = "Roosevelt Av 787"} }; var result = customers.Where(customer => search.All(term => customer.FName.Contains(term) || customer.Surname.Contains(term) || customer.DOB.ToString().Contains(term) || customer.Phone.Contains(term) || customer.Address.Contains(term) ));