Wie wenden Sie einen Filter in einem LINQ to SQL-Ausdruck nur an, wenn Ergebnisse vorhanden sind, wenn der Filter angewendet wird?

c# expression-trees linq linq-to-sql

Frage

Ich habe eine Funktion, die ich in einen LINQ zu SQL-Ausdruck umwandeln möchte, aber ich kann nicht herausfinden, wie. Diese Funktion wird innerhalb einer LINQ-Abfrage einmal für jede Zeile in der Ergebnismenge aufgerufen. Die ProductAreaId, die übergeben wird, kann sich auf gültige Daten beziehen oder nicht, also muss ich überprüfen und dann nur nach productAreaId filtern, wenn nach dem Anwenden des Filters Zeilen vorhanden sind:

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

Ich will es nicht so machen. Ich brauche eine Funktion, die einen Ausdruck ohne willkürlichen Code zurückgibt, so dass er zusammensetzbar ist. Die obige Funktion wird nur angezeigt, weil es die einzige Möglichkeit ist, dies in einer Funktion zu kapseln.

Ich würde gerne so etwas machen, wobei das erfundene "ApplyFilterIfAnyResultExists" durch etwas ersetzt wird, das tatsächlich funktioniert:

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

Gibt es eine Möglichkeit, diese Art von Filter innerhalb eines LINQ to SQL-Ausdrucks anzuwenden? Wenn nicht, irgendwelche Vorschläge?

Vielen Dank!
Roy

EDIT: Dies ist die Hauptabfrage, wie ich es gerne aussehen würde:

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

Beachten Sie auch, dass sich Bestellungen und Kunden in zwei verschiedenen Datenbanken auf dem Datenbankserver befinden, beide jedoch aus demselben DataContext referenziert werden.

Akzeptierte Antwort

Vielleicht in etwa so:

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

Beliebte Antwort

Für Ihre ursprüngliche Methode könnte dies besser funktionieren:

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

Dies macht es so, dass Sie nur eine einzige Datenbank-Roundtrip machen. Wenn die Produktbereichs-ID angegeben wird, geben Sie Bestellungen zurück, bei denen entweder

  • keiner der Aufträge in der ursprünglichen Abfrage hat diese Bereichs-ID oder
  • Diese Reihenfolge hat diese Bereichs-ID

Es macht die Abfrage komplexer, also würde ich es ein wenig testen, um zu sehen, ob es wirklich irgendwelche Leistungssteigerungen gibt.

Dies wird nicht sehr gut zu der Funktion, die Sie vorschlagen, aber wenn Sie mehr Informationen darüber, wie dieser Code aufgerufen wird, zu teilen, könnte ich Ihnen wahrscheinlich einen Ratschlag geben, wie Sie vermeiden, diese Funktion 20 Mal aufrufen.

Bearbeiten

So etwas sollte funktionieren:

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


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum