2 Stimmen

Wie erhält man Linq2SQL OR zwischen Bedingungen?

Nehmen wir an, wir müssen zwei Sätze aus einer Tabelle auswählen: "Dinge"

var GradeA = db.Things.Where(t=>  condition1);
var GradeB = db.Things.Where(t=> !condition1 && condition2);
var DesiredList = GradeA.union(GradeB);

alternativ müssen wir eine einzige Anweisung schreiben, um zu vermeiden union Kosten:

var DesiredList = db.Things.Where(t=> condtion1 || (!condition1 && condition2));

Das Problem ist, dass der Abfrageoptimierer den Ausdruck anscheinend auf Bedingung2 nur.

Wie lässt sich die Priorität zwischen Zustand1 und Bedingung2

ein praktisches Beispiel für eine Umgehung ist :

/// <summary>
/// Gets only first BookTag for each tag word, chooses the one of this user (if exists).
/// </summary>
/// <param name="book"></param>
/// <returns></returns>
public static IQueryable<BookTag> UniqueByTags(this IQueryable<BookTag> bookTags, User user)
{
    return bookTags.GroupBy(BT => BT.TagId)
        .Select(g => g.Any(bt => bt.UserId == user.Id) ? 
            new BookTag() { User = user, Tag = g.First().Tag, Book = bookTags.First().Book } : 
            new BookTag() {User = g.First().User, Tag = g.First().Tag, Book = bookTags.First().Book}
            );
}

Bearbeiten:

Ein Beispiel ist das Abrufen der Auto-Vervollständigen-Liste:

  • Eingang: str
  • Ausgabe: Dinge, die mit str und Dinge, die Folgendes enthalten str (keine Duplikate)

Ein weiteres Beispiel: Auswahl von ThingTags die 3 Eigenschaften haben:

  • ThingID
  • UserID
  • TagID

wir wollen nur einen auswählen ThingTag für jede TagID unter der Bedingung, dass wir diejenige auswählen, die mit UserID ist gleich Parameter, wenn existiert , sonst zuerst wählen ThingTag dafür TagID .

Immer noch bei mir? Ich hoffe doch :)


2voto

Jon Skeet Punkte 1325502

Gibt es einen Grund, dies nicht zu schreiben?

var DesiredList = db.Things.Where(t=> condition1 || condition2);

Das ist ja logischerweise die gleiche Menge an Elementen. Da es sich um einen einfacheren Ausdruck handelt, ist es wahrscheinlicher, dass der Abfragegenerator es richtig macht. Trotzdem bin ich überrascht, dass er es falsch macht. Haben Sie ein vollständiges Beispiel, das Sie zur Verfügung stellen können?

0voto

Justin Niessner Punkte 235353

Es sieht für mich so aus, als ob Sie eher ein XOR (Exklusiv oder) als ein reguläres ODER zwischen Ihren beiden Bedingungen durchführen wollen (mit anderen Worten, Sie wollen die Elemente, die nur die Anforderungen des einen ODER des anderen erfüllen... nicht beide).

Ich bin mir nicht sicher, was LINQ to SQL angeht, aber ich weiß, dass LINQ to Objects das XOR unterstützt... Sie können es also ausprobieren. Hier ist die Syntax:

var DesiredList = db.Things.Where(t => condition1 ^ condition2);

0voto

Marc Gravell Punkte 970173

Um das Beispiel wörtlich zu nehmen, könnten Sie die in der Frage genannte Kombination durchführen, indem Sie eine Expression auf die Schnelle:

    static IQueryable<T> WhereXElseY<T>(
        this IQueryable<T> query,
        Expression<Func<T, bool>> predicateA,
        Expression<Func<T, bool>> predicateB)
    {
        var condA = predicateA.Body;
        var param = predicateA.Parameters[0];

        var body = Expression.OrElse(
            condA,
            Expression.AndAlso(
                Expression.Not(condA),
                Expression.Invoke(
                    predicateB, param)));
        var lambda = Expression.Lambda<Func<T, bool>>(
            body, param);
        return query.Where(lambda);
    }

Während dies jedoch mit LINQ-to-SQL funktionieren kann, wird es nicht mit EF funktionieren, da EF leider keine Expression.Invoke . Aber wie Jon bemerkt; wenn Sie dies an ein Datenbank-Backend senden, ist die Priorität irrelevant und Sie könnten genauso gut mit dem logisch äquivalenten gehen condition1 || condition2 . Sie können Ausdrücke kombinieren wie:

    static IQueryable<T> WhereAny<T>(
        this IQueryable<T> query,
        params Expression<Func<T, bool>>[] predicates)
    {
        if (predicates == null) throw new ArgumentNullException("predicates");
        if (predicates.Length == 0) return query.Where(x => false);
        if (predicates.Length == 1) return query.Where(predicates[0]);

        var param = predicates[0].Parameters[0];
        var body = predicates[0].Body;
        for (int i = 1; i < predicates.Length; i++)
        {
            body = Expression.OrElse(
                body, Expression.Invoke(predicates[i], param));
        }
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return query.Where(lambda);
    }

Wenn ich etwas nicht verstanden habe, bitte ich um Aufklärung...

0voto

eglasius Punkte 35447

Erstens: Ich würde wirklich wieder überprüfen Sie Ihren ursprünglichen Code für die Bedingungen, während es möglich ist, gibt es einen Fehler in der Abfrage-Optimierer seine wahrscheinlicher es war ein Fehler auf den Ausdruck verwendet und es wirklich nicht die unten darstellen:

var DesiredList = db.Things.Where(t=> condition1 || (!condition1 && condition2));

Das Problem ist der Abfrageoptimierer scheint den Ausdruck nur auf Bedingung 2 zu kürzen zu beschränken.

Damit sollten Sie eigentlich diejenigen erhalten, die unabhängig von Bedingung2 mit Bedingung1 übereinstimmen ... und diejenigen, die nicht mit Bedingung1 und Bedingung2 übereinstimmen. Stattdessen ist Bedingung2 allein nicht äquivalent, da dies Datensätze auslässt, die nur Bedingung1 erfüllen.

JS Version von nur (condition1 || condition2) ist äquivalent zu dem zitierten Ausdruck oben, wie wenn u sind passende condition1 Sie sind bereits passende sowohl condition2 und !condition2 so Sie sind bereits einschließlich condition2 für beide condition1 und !condition1 Fälle. Wenn dies nicht mit dem übereinstimmt, was Sie mit den Abfragen beabsichtigt haben, dann ist klarer, dass es sich nicht um ein Problem mit dem Optimierer, sondern mit den ursprünglichen Ausdrücken handelt.

Sie würden die vollständigen Ausdrücke nur benötigen, wenn Sie 2 Ergebnisse mit einem Concat anstelle von Union verbinden würden, da dies bedeuten würde, dass Sie am Ende Ergebnisse haben, die in beiden Ausdrücken übereinstimmen ... und dann würden Sie wiederholte Ergebnisse haben. Im Gegensatz dazu wird die Where-Anweisung pro Zeile ausgewertet, so dass Sie diese Probleme nicht haben.


Zweitens: Aus dem Code-Beispiel, ich denke, was Sie konfrontiert sind, ist weniger direkt von dem, was Sie nach in Ihrer Frage gehen. Sie erwähnten, dass Sie den ersten Tag erhalten, aber was Sie wirklich tun, kann in dieser umgeschriebenen Version gesehen werden:

public static IQueryable<BookTag> UniqueByTags(this IQueryable<BookTag> bookTags, User user)
{
    return bookTags.GroupBy(BT => BT.TagId)
        .Select(g => new BookTag() { 
             User = g.Any(bt => bt.UserId == user.Id) ? user : g.First().User,
             Tag = g.First().Tag, Book = bookTags.First().Book 
        });
}

Was in dem Kommentar erwähnt wird, scheint eher so zu sein:

public static IQueryable<BookTag> UniqueByTags(this IQueryable<BookTag> bookTags, User user)
{
    return bookTags.GroupBy(BT => BT.TagId)
        .Select(g => g.Any(bt => bt.UserId == user.Id) ? 
            g.First(bt=>bt.UserId == user.Id) : 
            g.First()
        );
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X