假设我有一个表dataContext.Customer,其中包含以下字段
FName varchar
LName varchar
Phone varchar
DOB datetime
Address varchar
Run Code Online (Sandbox Code Playgroud)
该表填充了一些示例数据,让我们说:
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
Run Code Online (Sandbox Code Playgroud)
我们还有一个包含任意数量元素的字符串数组,例如:
search[0] = "1978"
search[1] = "John"
Run Code Online (Sandbox Code Playgroud)
我需要一个LINQ查询,它将使用"contains"或"any"(在SQL中表示LIKE)逐步比较表的每个字段与字符串数组中的每个项目,并仅返回与记录中所有给定条件匹配的行,基于之前的search []示例,LINQ查询应仅返回记录#1.
另一个例子可以是:
search[0] = "Bush"
search[1] = "111"
search[2] = "John"
Run Code Online (Sandbox Code Playgroud)
并且只返回记录#3.最后:
search[0] = "John"
Run Code Online (Sandbox Code Playgroud)
记录#1,#3和#4应该返回(我认为这个想法很清楚)
有一个关于如何比较字符串[]与字段的问题: LINQ:实体字符串字段包含任何字符串数组
如果答案是50行C#例程,我更喜欢通过存储过程直接在数据库中解决这个问题.
如果有一些"反射"技巧可以在执行查询时迭代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+'%'
Run Code Online (Sandbox Code Playgroud)
所以问题是,有没有办法编写一个LINQ来生成这个简单的SQL?(请注意,比较是通过'LIKE'在数据库中完成的,而不是在应用程序中)
更新2:尽管像Francisco那样的解决方案会生成"LIKE"语句,但它将无法进行比较.将所有数据从表中提取到Web服务器的其他解决方案将正确匹配,但这是完全不切实际的.
接受RUNE FS的答案,因为它是最干净的解决方案,可以在任何领域工作.
使用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<T> AllAny<T>(this IQueryable<T> 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<T>();
foreach ( var term in search )
{
var orPredicate = PredicateBuilder.False<T>();
foreach (var property in properties )
orPredicate = orPredicate.Or(CreateLike<T>(property,term));
andPredicate = andPredicate.And(orPredicate);
}
return query.Where(andPredicate);
}
private static Expression<Func<T,bool>> CreateLike<T>( 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<Func<T, bool>>(like, parameter);
}
private static Expression<Func<T,bool>> CreateLike<T>( string propertyName, string value)
{
var prop = typeof(T).GetProperty(propertyName);
return CreateLike<T>(prop, value);
}
}
// http://www.albahari.com/nutshell/predicatebuilder.aspx
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T> () { return f => true; }
public static Expression<Func<T, bool>> False<T> () { return f => false; }
public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
}
}
Run Code Online (Sandbox Code Playgroud)
更新 此代码是以下查询的通用解决方案
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
Run Code Online (Sandbox Code Playgroud)
可以通过多种方式改进此代码.对于示例,对于字符串属性,不需要在Contains之前调用ToString(这将生成convert(nvarchar)),我真的认为需要这个的人只想查看varchar,nvarchar列.
假设“\t”永远不会成为数据的一部分,您可以执行以下操作。您当然可以用任何其他角色替换。有了这个假设,你可以执行以下操作:
public static IEnumerable<T> Where<T>(this IEnumerable<T> 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;
}
Run Code Online (Sandbox Code Playgroud)
编辑
我最初没有包含用法,因为我在原始帖子中没有找到集合,但假设序列被定义为IEnumerabl<Person>并且可以作为变量数据库上名为 Persons 的属性进行访问。代码看起来类似于:
IEnumerable<Person> persons = db.Persons.Where(criteria);
Run Code Online (Sandbox Code Playgroud)