“like” queries in Entity Framework

2020-01-26 12:01发布

问题:

How do I get wildcard text searches (like SQL's "like" statement) in ASP.net MVC using the edo entity framework?

I assumed this would work:

var elig = (from e in _documentDataModel.Protocol_Eligibility_View
            where e.criteria.Contains(query)
            select e);

But it returns no results even when searching for a query string that's definitely in the database. What am I doing wrong?

回答1:

This guy made a very nice "WhereLike" extension for Linq that accepts any wildcard character and compares two values (one of which comes from an expression) with a generic method derived from the location of the wildcard.

  • x% -> startswith
  • %x -> endswith
  • %x% -> contains

http://trentacular.com/2010/08/linq-to-entities-wild-card-like-extension-method/

EDIT: The article seems to be down. I will paste the extention code below:

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 });
        }
    }
}


回答2:

String.Contains should work appropriately. SQL's LIKE statement is typically handled via String.StartsWith, String.Contains, or String.EndsWith.

However, it is possible you're having casing issues. You could try:

var elig = (from e in _documentDataModel.Protocol_Eligibility_View
        where e.criteria.ToLower().Contains(query.ToLower())
        select e);


回答3:

Linq to entities does not support the SqlMethods method, but you could use the string functions instead:

.Where(entity => entity.Name.Contains("xyz"))

.Where(entity => entity.Name.EndsWith("xyz"))

.Where(entity => entity.Name.StartsWith("xyz"))


回答4:

The System.Data.Linq.SqlClient namespace contains the SqlMethods class. You can use it's Like method like this:

var elig = from e in _documentDataModel.Protocol_Eligibility_View
           where SqlMethods.Like(e.criteria, query)
           select e;


回答5:

I started using the code that Jon Koeter posted from the blog post in another answer that no longer appears to exist.

However, I found it didn't really work properly, especially when using an IEnumerable. Namely, it was resolving the enumerable using ToArray and using a regex to match, rather than the built in functions.

Since I only want to resolve my IEnumerable once I have finished filtering, I made some changes to convert to an IQueryable then use the rest of the code to find the correct Entity Framework method and call that. This way, the query itself is not called against the database until later, and it avoids the use of a regex.

public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, Expression<Func<T, string>> valueSelector, string value, char wildcard)
{
    return source.Where(BuildLikeExpression(valueSelector, value, wildcard));
}

public static IEnumerable<T> WhereLike<T>(this IEnumerable<T> source, Expression<Func<T, string>> valueSelector, string value, char wildcard)
{
    return source.AsQueryable().WhereLike(valueSelector, value, wildcard);
}

private static Expression<Func<T, bool>> BuildLikeExpression<T>(Expression<Func<T, 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<T, 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 data set
var data = new List<Person> {
    new Person{FirstName="John", LastName="Smith"}, 
    new Person{FirstName="Jane", LastName="Doe"}
};

data.WhereLike(x=>x.FirstName, "John", "%"); // returns John Smith
data.WhereLike(x=>x.FirstName, "J%", "%"); // returns John Smith and Jane Smith


回答6:

2019 update

For Entity Framework 6.2 you can use DBFunctions

For example:

try
{
    using (var db = new YOUREntities())
    {
        var data = db.LenderProgram.Where(i => DbFunctions.Like(i.LenderProgramCode, "OTO%"))
            .ToList();
        return data;
    }
}
catch (Exception e)
{
    e.HandleException();
}