Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Like operator or using wildcards in LINQ to Entities

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 answered it in my previous question. But now as I'm using L2E not L2S, I need other solution.

like image 279
ns12345 Avatar asked Feb 02 '11 19:02

ns12345


4 Answers

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)
like image 166
BG100 Avatar answered Sep 28 '22 23:09

BG100


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.

like image 29
Legolomaniac Avatar answered Sep 28 '22 22:09

Legolomaniac


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, '*');
like image 21
EvgK Avatar answered Sep 28 '22 23:09

EvgK


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);
            }
        }
    }
}
like image 31
syllogism Avatar answered Sep 28 '22 23:09

syllogism