I'm using LINQ 2 Entities. Following is the problem:
string str = '%test%.doc%'
.Contains(str) // converts this into LIKE '%~%test~%.doc~%%'
Expected Conversion: LIKE '%test%.doc%'
If it was LINQ 2 SQL, I could have used SqlMethods.Like as somebody answe开发者_C百科red it in my previous question. But now as I'm using L2E not L2S, I need other solution.
The SQL method PATINDEX provides the same functionality as LIKE. Therefore, you can use the SqlFunctions.PatIndex method:
.Where(x => SqlFunctions.PatIndex("%test%.doc%", x.MySearchField) > 0)
Following on from Magnus' correct answer, here is an extension method that can be re-used, as I needed in my project.
public static class LinqExtensions
{
public static Expression<Func<T, bool>> WildCardWhere<T>(this Expression<Func<T, bool>> source, Expression<Func<T, string>> selector, string terms, char separator)
{
if (terms == null || selector == null)
return source;
foreach (string term in terms.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries))
{
string current = term;
source = source.And(
Expression.Lambda<Func<T, bool>>(
Expression.Call(selector.Body, "Contains", null, Expression.Constant(current)),
selector.Parameters[0]
)
);
}
return source;
}
}
Usage:
var terms = "%test%.doc%";
Expression<Func<Doc, bool>> whereClause = d => d;
whereClause = whereClause.WildCardWhere(d => d.docName, terms, '%');
whereClause = whereClause.WildCardWhere(d => d.someOtherProperty, "another%string%of%terms", '%');
var result = ListOfDocs.Where(whereClause).ToList();
The extension makes use of the predicate builder at http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/. The resulting sql does a single table scan of the table, no matter how many terms are in there. Jo Vdb has an example you could start from if you wanted an extension of iQueryable instead.
You can try use this article, where author describes how to build a LIKE statement with wildcard characters in LINQ to Entities.
EDIT: Since the original link is now dead, here is the original extension class (as per Jon Koeter in the comments) and usage example.
Extension:
public static class LinqHelper
{
//Support IQueryable (Linq to Entities)
public static IQueryable<TSource> WhereLike<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, string>> valueSelector, string value, char wildcard)
{
return source.Where(BuildLikeExpression(valueSelector, value, wildcard));
}
//Support IEnumerable (Linq to objects)
public static IEnumerable<TSource> WhereLike<TSource>(this IEnumerable<TSource> sequence, Func<TSource, string> expression, string value, char wildcard)
{
var regEx = WildcardToRegex(value, wildcard);
//Prevent multiple enumeration:
var arraySequence = sequence as TSource[] ?? sequence.ToArray();
try
{
return arraySequence.Where(item => Regex.IsMatch(expression(item), regEx));
}
catch (ArgumentNullException)
{
return arraySequence;
}
}
//Used for the IEnumerable support
private static string WildcardToRegex(string value, char wildcard)
{
return "(?i:^" + Regex.Escape(value).Replace("\\" + wildcard, "." + wildcard) + "$)";
}
//Used for the IQueryable support
private static Expression<Func<TElement, bool>> BuildLikeExpression<TElement>(Expression<Func<TElement, string>> valueSelector, string value, char wildcard)
{
if (valueSelector == null) throw new ArgumentNullException("valueSelector");
var method = GetLikeMethod(value, wildcard);
value = value.Trim(wildcard);
var body = Expression.Call(valueSelector.Body, method, Expression.Constant(value));
var parameter = valueSelector.Parameters.Single();
return Expression.Lambda<Func<TElement, bool>>(body, parameter);
}
private static MethodInfo GetLikeMethod(string value, char wildcard)
{
var methodName = "Equals";
var textLength = value.Length;
value = value.TrimEnd(wildcard);
if (textLength > value.Length)
{
methodName = "StartsWith";
textLength = value.Length;
}
value = value.TrimStart(wildcard);
if (textLength > value.Length)
{
methodName = (methodName == "StartsWith") ? "Contains" : "EndsWith";
}
var stringType = typeof(string);
return stringType.GetMethod(methodName, new[] { stringType });
}
}
Usage Example:
string strEmailToFind = "%@yahoo.com"
IQueryable<User> myUsers = entities.Users.WhereLike(u => u.EmailAddress, strEmailToFind, '%');
or, if you expect your users to be more accustomed to Windows Explorer-styled wildcards:
string strEmailToFind = "*@yahoo.com"
IQueryable<User> myUsers = entities.Users.WhereLike(u => u.EmailAddress, strEmailToFind, '*');
Use a regular expression...
The following will print out all of the files in the current directory that match test.doc* (dos wildcard style - which I believe is what you're asking for)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
namespace RegexFileTester
{
class Program
{
static void Main(string[] args)
{
string[] _files = Directory.GetFiles(".");
var _fileMatches = from i in _files
where Regex.IsMatch(i, ".*test*.doc.*")
//where Regex.IsMatch(i, ".*cs")
select i;
foreach(var _file in _fileMatches)
{
Console.WriteLine(_file);
}
}
}
}
Split the String
var str = "%test%.doc%";
var arr = str.Split(new[]{'%'} ,StringSplitOptions.RemoveEmptyEntries);
var q = tblUsers.Select (u => u);
foreach (var item in arr)
{
var localItem = item;
q = q.Where (x => x.userName.Contains(localItem));
}
So I was trying the same thing - trying to pair down a List to return all candidates that matched a SearchTerm. I wanted it so that if a user typed "Arizona" it would return everything regardless of case that had Arizona. Also, if the user typed "Arizona Cha", it would return items like "Arizona License Change". The following worked:
private List<Certification> GetCertListBySearchString()
{
string[] searchTerms = SearchString.Split(' ');
List<Certification> allCerts = _context.Certifications.ToList();
allCerts = searchTerms.Aggregate(allCerts, (current, thisSearchString) => (from ac in current
where ac.Name.ToUpper().Contains(thisSearchString.ToUpper())
select ac).ToList());
return allCerts;
}
精彩评论