C #, Linq2Sql: ¿Es posible concatenar dos consultables en uno?

c# expression-trees iqueryable lambda linq-to-sql

Pregunta

Tengo una consulta donde he usado varias declaraciones Where y WhereBetween para limitar la colección a un conjunto determinado. Ahora necesito agregar una especie de Where || WhereBetween En otras palabras, no puedo simplemente encadenarlos como lo he hecho hasta ahora, porque eso funcionará como un And. Entonces, ¿cómo puedo hacer esto?

Veo dos posibilidades:

  1. Cree dos consultas a partir de la que tengo, una usando el lugar Where y una usando el WhereBetween . Y luego concatenarlos . ¿No sabes si esto es posible? Además, aunque no en mi caso particular, lo más probable es que termine con duplicados ...
  2. De alguna manera, fusione la expresión Where y la expresión creada en WhereBetween con algún tipo de Or.

El primero, como he mencionado, no estoy seguro es posible. Y si lo fue, no estoy tan seguro de que sea una buena manera de hacerlo.

El segundo, puedo verlo como una opción, pero no estoy totalmente seguro de todos los detalles. A continuación se muestra el método WhereBetween de mi otra pregunta, que ahora uso y funciona muy bien:

    public static IQueryable<TSource> WhereBetween<TSource, TValue>(
        this IQueryable<TSource> source,
        Expression<Func<TSource, TValue>> selector,
        IEnumerable<Range<TValue>> ranges)
    {
        var param = Expression.Parameter(typeof(TSource), "x");
        var member = Expression.Invoke(selector, param);
        Expression body = null;
        foreach (var range in ranges)
        {
            var filter = Expression.AndAlso(
                Expression.GreaterThanOrEqual(member,
                     Expression.Constant(range.A, typeof(TValue))),
                Expression.LessThanOrEqual(member,
                     Expression.Constant(range.B, typeof(TValue))));
            body = body == null ? filter : Expression.OrElse(body, filter);
        }
        return body == null ? source : source.Where(
            Expression.Lambda<Func<TSource, bool>>(body, param));
    }

Estoy pensando que tal vez podría extraer la parte de construcción de expresión en un nuevo método. Tal vez así:

    public static IQueryable<TSource> WhereBetween<TSource, TValue>(
        this IQueryable<TSource> source,
        Expression<Func<TSource, TValue>> selector,
        IEnumerable<Range<TValue>> ranges)
    {
        return source.Where(WhereBetween(selector, ranges));
    }

    public static Expression<Func<TSource, bool>> WhereBetween<TSource, TValue>(
        Expression<Func<TSource, TValue>> selector,
        IEnumerable<Range<TValue>> ranges)
    {
        var param = Expression.Parameter(typeof(TSource), "x");
        var member = Expression.Invoke(selector, param);
        Expression body = null;
        foreach (var range in ranges)
        {
            var filter = Expression.AndAlso(
                Expression.GreaterThanOrEqual(member,
                     Expression.Constant(range.A, typeof(TValue))),
                Expression.LessThanOrEqual(member,
                     Expression.Constant(range.B, typeof(TValue))));
            body = body == null ? filter : Expression.OrElse(body, filter);
        }
        return body == null 
            ? ø => true
            : Expression.Lambda<Func<TSource, bool>>(body, param);
    }

Entonces podría usar ese nuevo método para obtener la expresión en lugar de la consulta. Entonces, digamos que tengo el WhereBetween(ø => ø.Id, someRange) y por ejemplo ø => ø.SomeValue == null . ¿Cómo puedo combinar esos dos con Or? Estoy mirando el Expression.OrElse utilizado en el método WhereBetween , y creo que podría ser lo que necesito, o tal vez este el Expression.Or . Pero soy muy inestable en estas expresiones, por lo que no estoy seguro de qué elegir aquí, o incluso si estoy en el camino correcto: p

¿Podría alguien darme algunos consejos aquí?

Respuesta aceptada

Tiene dos opciones aquí: Queryable.Union , o combinación de expresiones. Por lo general, prefiero lo último, a través de OrElse , que (con al menos LINQ-to-SQL) se puede hacer con 2 expresiones (ver a continuación), pero en cualquier caso, debe componerse:

    using(var ctx = new DataClasses1DataContext())
    {
        ctx.Log = Console.Out;
        Expression<Func<Customer, bool>> lhs =
            x => x.Country == "UK";
        Expression<Func<Customer, bool>> rhs =
            x => x.ContactName.StartsWith("A");

        var arr1 = ctx.Customers.Where(
            lhs.OrElse(rhs)).ToArray();

        var arr2 = ctx.Customers.Where(lhs)
            .Union(ctx.Customers.Where(rhs)).ToArray();
    }

Tanto arr1 como arr2 solo realizan 1 hit de base de datos (aunque el TSQL es diferente; el primero tiene un OR en la cláusula WHERE ; el segundo tiene dos consultas separadas con UNION ).

Aquí está el método de extensión que utilicé:

static Expression<Func<T, bool>> OrElse<T>(
    this Expression<Func<T, bool>> lhs,
    Expression<Func<T, bool>> rhs)
{
    var row = Expression.Parameter(typeof(T), "row");
    var body = Expression.OrElse(
        Expression.Invoke(lhs, row),
        Expression.Invoke(rhs, row));
    return Expression.Lambda<Func<T, bool>>(body, row);
}


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué