¿Cómo aplicar un filtro en una expresión LINQ a SQL solo si existen resultados cuando se aplica el filtro?

c# expression-trees linq linq-to-sql

Pregunta

Tengo una función que me gustaría transformar en una expresión de LINQ a SQL, pero no puedo entender cómo. Esta función se llama desde una consulta LINQ, una vez por cada fila en el conjunto de resultados. El productAreaId que se está pasando puede referirse o no a datos válidos, por lo que debo verificar, y luego solo filtrar por productAreaId si existe alguna fila después de aplicar el filtro:

//Forgive the contrived example...
public static IQueryable<Order> GetOrders(int orderNumber, int? productAreaId, 
    OSDataContext db)
{
    var orders = db.Orders.Where(o => o.OrderNumber == orderNumber &&
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted);

    if (productAreaId != null)
    {
        var orders2 = orders.Where(o => o.ProductAreaId == productAreaId);
        if (orders2.Any()) return orders2;
    }
    return orders;
}

No quiero hacerlo así. Necesito una función que devuelva una expresión sin código arbitrario, por lo que será compostable. La función anterior solo se muestra porque es la única forma en que sé cómo encapsular esto en una función.

Me gustaría hacer algo como esto, con el "ApplyFilterIfAnyResultExists" creado con algo que realmente funciona:

public static Expression<Func<Order,bool>> 
    GetOrdersExpr(int orderNumber, int? productAreaId)
{
    return o => o.OrderNumber == orderNumber && 
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted && (productAreaId == null || 

            //Making up a fictional function. Is there a real way to do this?
            o.ApplyFilterIfAnyResultExists(row => 
                row.ProductAreaId == productAreaId)
        );
}

¿Hay alguna forma de aplicar ese tipo de filtro dentro de una expresión LINQ to SQL? Si no, ¿alguna sugerencia?

¡Gracias!
Roy

EDITAR: Esta es la consulta principal que me gustaría ver:

var customerData = 
    from c in db.Customers
    select new 
    {
        id = c.Id,
        name = c.Name,

        lastOrder =
            db.Orders
            .Where(GetOrdersExpr(c.LastOrderNumber, 
                c.PreferredProductAreaId))
            .FirstOrDefault(),

        allOrders = c.OrderForms
            .Select(form => 
                db.Orders
                .Where(GetOrdersExpr(form.OrderNumber,
                    c.PreferredProductAreaId))
                .FirstOrDefault()
            )
            .Where(o => o != null)

        //How lastOrder used to be queried
        //lastOrder =
        //    GetOrders(c.LastOrderNumber, c.PreferredProductAreaId, db)
        //    .FirstOrDefault()
    };

También vale la pena señalar que los pedidos y los clientes se encuentran en dos bases de datos diferentes en el servidor de la base de datos, pero ambos están referenciados desde el mismo DataContext aquí.

Respuesta aceptada

Tal vez algo como esto:

var customerData = 
    from c in db.Customers
    let orders = db.Orders.Where(o => o.OrderNumber == c.orderNumber &&
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted)
    let orders2 = orders.Where(o => o.ProductAreaId == c.productAreaId)
    select new 
    {
        id = c.Id,
        name = c.Name,
        lastOrder = c.productAreaId != null && orders2.Any() ?
            orders2.FirstOrDefault() :
            orders.FirstOrDefault() 
    };

Respuesta popular

Para su método original, esto podría funcionar mejor:

public static IQueryable<Order> GetOrders(int orderNumber, int? productAreaId, 
    OSDataContext db)
{
    var orders = db.Orders.Where(o => o.OrderNumber == orderNumber &&
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted);
    if(productAreaId != null)
    {
        orders = orders.Where(
            o => !orders.Any(o2 => o2.ProductAreaId == productAreaId) ||
                    o.ProductAreaId == productAreaId);
    }
    return orders;
}

Esto lo hace así que solo estás haciendo una base de datos de ida y vuelta. Si se proporciona la ID del área del producto, devolverá los pedidos donde:

  • ninguna de las órdenes en la consulta original tiene esa ID de área, o
  • este orden tiene esa ID de área

Hace que la consulta sea más compleja, por lo que la probaría un poco para ver si realmente le proporciona algún aumento de rendimiento.

Esto no se traducirá muy bien a la función que está sugiriendo, pero si comparte más información sobre cómo se llama a este código, probablemente le pueda dar algunos consejos sobre cómo evitar llamar a esta función más de 20 veces.

Editar

Algo como esto debería funcionar:

var customerData = 
    from c in db.Customers
    let productAreaId = c.PreferredProductAreaId
    let orders = 
        db.Orders
        .Where(o => o.OrderNumber == c.LastOrderNumber &&
            o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
            !o.Deleted)
        .OrderBy(o => o.Date)
    let lastOrderInProductArea = productAreaId != null
        ? orders.FirstOrDefault(o => o.ProductAreaId == productAreaId)
        : null
    select new 
    {
        id = c.Id,
        name = c.Name,
        lastOrder = lastOrderInProductArea != null
            ? lastOrderInProductArea
            : orders.FirstOrDefault()
    };


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow