2011-08-15 7 views
8

supongamos que tengo una forma particular de decidir si algunas cadenas "coincidencia", así:¿Cómo permanecer seco mientras usa LINQ para Entidades y métodos auxiliares?

public bool stringsMatch(string searchFor, string searchIn) 
{ 
    if (string.IsNullOrEmpty(searchFor)) 
    { 
    return true; 
    } 

    return searchIn != null && 
    (searchIn.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || 
    searchIn.Contains(" " + searchFor)); 
} 

me gustaría tirar de partidos fuera de una base de datos utilizando LINQ a las entidades y esta ayuda. Sin embargo, cuando intento esto:

IQueryable<Blah> blahs = query.Where(b => stringsMatch(searchText, b.Name); 

consigo "LINQ a Entidades no reconoce el método ..."

Si me re-escribir el código como:

IQueryable<Blah> blahs = query.Where(b => 
     string.IsNullOrEmpty(searchText) || 
     (b.Name != null && 
     (b.Name.Trim().ToLower().StartsWith(searchText.Trim().ToLower()) || 
     b.Name.Contains(" " + searchText))); 

Qué es lógicamente equivalente, entonces las cosas funcionan bien. El problema es que el código no es tan legible, y tengo que volver a escribirlo para cada entidad diferente que quiera emparejar.

Por lo que puedo decir por preguntas como this one, lo que quiero hacer es imposible en este momento, pero espero que me esté perdiendo algo, ¿o sí?

+0

try [Predicate Builder] (http://www.albahari.com/nutshell/predicatebuilder.aspx) – Eranga

Respuesta

4

Usando una biblioteca de libre acceso llamada LINQKit (mencionada por @Eranga) esta tarea se vuelve razonable. Usando LINQKit el código que tengo ahora queda como:

protected Expression<Func<T, bool>> stringsMatch(string searchFor, Expression<Func<T, string>> searchIn) 
{ 
    if (string.IsNullOrEmpty(searchFor)) 
    { 
    return e => true; 
    } 

    return 
    e => 
    (searchIn.Invoke(e) != null && 
     (searchIn.Invoke(e).Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || 
     searchIn.Invoke(e).Contains(" " + searchFor))); 
} 

Y tiene que ser llamado así (tenga en cuenta la llamada AsExpandable())

IQueryable<Blah> blahs = query().AsExpandable().Where(StringsMatch(searchText, b => b.Name)); 

Las piezas mágicas son el SEARCHIN.Llamadas Invoke (e) y el uso de AsExpandable() que agrega una capa contenedora que les permite trabajar.

El bit AsExpandable() se explica en detalle por el autor original here.

Tenga en cuenta que todavía estoy un poco confuso con algunos de los detalles de las expresiones, así que agregue un comentario/edite esta respuesta si puede hacerse mejor/más corto/más claro.

5

Si todas las 'blahs' (clases) que va a filtrar tienen la misma estructura, puede usar un método simple como este. La diferencia principal es que devuelve una Expresión que Linq debería poder analizar y trae toda la instancia y filtros en Nombre en lugar de traer solo el nombre de la cadena.

public static Expression<Func<T, bool>> BuildStringMatch<T>(string searchFor) where T : IHasName 
    { 
     return b => 
       string.IsNullOrEmpty(searchFor) || 
       (b.Name != null && 
       (b.Name.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || 
       b.Name.Contains(" " + searchFor))); 
    } 

puede utilizar ese método como este:

IQueryable<Blah> blahs = query.Where(BuildStringMatch<Blah>(searchText)); 

que asume todas las clases que te gustaría filtrar en poner en práctica alguna de las interfaces, tales como:

public interface IHasName 
    { 
     string Name { get; } 
    } 

Si Quiero filtrar en diferentes propiedades, no creo que sea algo que puedas hacer con un código simple como este. Creo que tendrás que construir la Expresión tu mismo con la reflexión (o con la ayuda de una biblioteca que usa la reflexión): todavía es posible pero mucho más difícil.

Editar: Suena como que necesita un comportamiento dinámico, por lo que tomó prestado algo de lógica de dtb 's respuesta a this question y se acercó con esto:

public static Expression<Func<T, bool>> BuildStringMatch<T>(Expression<Func<T, string>> property, string searchFor) 
{ 
    var searchForExpression = Expression.Constant(searchFor, typeof(string)); 
    return 
     Expression.Lambda<Func<T, bool>>(
      Expression.OrElse(
       Expression.Call(typeof(string), "IsNullOrEmpty", null, searchForExpression), 
       Expression.AndAlso(
        Expression.NotEqual(property.Body, Expression.Constant(null, typeof(string))), 
        Expression.OrElse(
         Expression.Call(Expression.Call(Expression.Call(property.Body, "Trim", null), "ToLower", null), "StartsWith", null, 
          Expression.Call(Expression.Call(searchForExpression, "Trim", null), "ToLower", null)), 
         Expression.Call(property.Body, "Contains", null, Expression.Call(typeof(string), "Concat", null, Expression.Constant(" "), searchForExpression)) 
        ) 
       ) 
      ), 
      property.Parameters 
     ); 
} 

Usted podría utilizarlo como:

 IQueryable<Blah> blahs2 = query.Where(BuildStringMatch<Blah>(b => b.Name, searchText)); 

Es largo y detallado pero se puede ver cómo es similar al método original escrito en código C# directo. Nota: No probé este código, por lo que podría haber algunos pequeños problemas, pero esa es la idea general.

+0

Gracias por responder, consideré algo como esto, pero la falta de flexibilidad es desagradable (no todo lo que querer emparejar se llama "Nombre"). ¿Alguna pista sobre por dónde empezar de la manera más complicada? – Dan

+0

He editado mi respuesta para incluir algún código de muestra. Ciertamente parece más complicado, pero si solo tiene que escribirlo una vez ... tal vez no sea tan malo. –

+0

Gracias, eso es realmente útil (lástima que solo puedo votar una vez). Sin embargo, he encontrado otro código en nuestra base de código que usa una biblioteca llamada LINQKit para hacer algo muy similar en (creo) de una manera más ordenada. Agregaré una nueva respuesta con todos los detalles. – Dan

Cuestiones relacionadas