假设我有一个包含以下字段的表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: Entity string field contains any of an array of strings
如果答案是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+'%'
所以问题是,有没有办法编写一个能生成这个简单SQL的LINQ? (请注意,比较是通过“LIKE”在数据库中完成的,而不是在应用程序中完成)
更新2:尽管像Francisco那样的解决方案会生成“LIKE”语句,但它将无法进行比较。将表格中的所有数据提取到网络服务器的其他解决方案将正确匹配,但是完全不切实际。
RUNE FS接受的答案,因为它是最干净的解决方案,可以在任何领域工作。
答案 0 :(得分:5)
使用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);
}
}
<强>更新强> 此代码是以下查询的通用解决方案
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列。
答案 1 :(得分:1)
我不认为linq to sql可以有效地做到这一点,但如果你可以在应用程序代码和数据库之间通过线路移动整个表,每次搜索只需要一次,linq到对象就可以提供帮助。
第一步是获取一个DataReader,它将读取表格中所有的记录。使用datareader很重要,因为你可能不想用整个表填充内存(然后再说一遍,也许你这样做 - 看看我关于缓存附近的缓存的说明)。
一旦有了这个,你需要将它转换为IEnumerable以与linq-to-objects一起使用。您可以使用简单的两行实用程序方法来实现,如下所示:
IEnumerable<IDataRecord> EnumerableFromDataReader(IDataReader reader)
{
while (reader.Read())
yield return reader;
}
我通常使用的实际代码稍微复杂一些,但是我所做的可能与你的项目其余部分的结构不太一致,所以我现在就把它放在这个简短的方法上。
一旦我们拥有了Enumerable,我们就可以利用linq的可组合特性来获得一些(相对)简单(或至少是简短的)代码:
IEnumerable<IDataRecord> SearchMyTable(string[] filters)
{
var results = EnumerableFromDataReader(GetMyTable());
foreach (string filter in filters)
{
results = results.Where(r => String.Join("",r.GetValues().Cast<string>().ToArray()).Contains(filter));
}
return results;
}
这不是一个单独的linq查询,但它也不是50行代码。
忽略网络延迟,此代码的性能实际上非常好。如果你想在列加入后对全部或部分表进行一些缓存,那么性能就是惊人的。
更新:此代码中存在(至少)一个缺陷。对于每个过滤器,我将存活到该过滤器的所有行重新转换为字符串...即使我已经为之前的过滤器执行了此操作。解决此问题的方法是首先进行投影以将行与字符串版本配对。但是因为它是在我的时间晚上11点之后,我将保留现在的代码。好消息是最终的固定代码应该是相同的长度:只需在第一行和最后一行添加.Select()
调用,然后稍微更改foreach循环的中间位置。
答案 2 :(得分:1)
我现在手头没有C#编译器,但我有这个想法:
声明一个像这样的lambda表达式:
public Expression<Func<Customer, bool>> 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查询所做的那样)。
然后在你的主要查询功能中:
IQueryable<Customer> customers = dataContext.Customers;
foreach(string inputSearch in search)
{
customers = customers.Where(GetFilterFromString(inputSearch));
}
IEnumerable<Customer> results = customers.AsEnumerable();
我认为这种方法的主要优点是你必须声明一次GetFilterFromString。希望这是你正在寻找的。 p>
修改强>
好的,所以我读了你正在寻找的SQL语句(有点迟到......但无论如何)。 我认为很容易适应我的解决方案。我们将不得不稍微调整lambda表达式:
public Expression<Func<Customer, bool>> GetFilterFromString(string input)
{
return p => (p.FName + " " +
p.LName + " " +
p.Phone + " " +
p.DOB.ToString() + " " +
p.Address)
.Contains(input)
}
答案 3 :(得分:1)
假设'\ 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;
}
修改强>
我最初没有包含用法,因为我在原始帖子中没有找到任何集合,但假设序列被定义为IEnumerabl<Person>
,并且可以作为变量db上的Persons属性进行访问。代码看起来类似于:
IEnumerable<Person> persons = db.Persons.Where(criteria);
答案 4 :(得分:0)
可能是我可能会做的是,首先生病从DB拥有名字的记录与我拥有的数组项目相比。
一旦我得到了表数据的子集(假设DB表的结构相同,数组结构也意味着数组[0]总是名字),那么我搜索我的模式在记忆中寻找。
我确信这不是您希望的完全解决方案。但是,lemme进一步思考,意味着当我对更多的想法开放时:)
答案 5 :(得分:0)
类似于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)
));