2016-08-19 5 views
13

Ich muss überprüfen, ob alle Definitionen bestimmte Daten enthält. Es funktioniert gut außer dem Fall, wenn GroupBy leere Sammlung zurückgibt.Linq Alle auf leere Sammlung

Wie umschreiben Sie das so, dass alle auf leere Sammlung false zurückgeben würden?

UPDATE: Es ist ein LINQ zu SQL und ich wollte dies in Einzelanruf ausführen.

UPDATE2: Ich denke, das funktioniert:

var exist = dbContext.Definitions 
        .Where(x => propertyTypeIds.Contains(x.PropertyTypeId) && x.CountryId == countryId) 
        .GroupBy(x => x.PropertyTypeId) 
        .Count(x => x 
         .All(...some condition...)) == propertyTypeIds.Count; 
+0

Ist dies LINQ zu Objekten oder etwas anderes? Die Antworten könnten radikal anders sein. –

+0

Versuchen Sie mit AllOrDefault – elloco999

+1

'All' wird nicht false für eine leere Sammlung zurückgeben. Siehe hier: http://stackoverflow.com/questions/7884888/why-does-enumerable-all-return-true-for-an-empty-sequence – sr28

Antwort

0

Was ist Ihre eigene Erweiterungsmethode zu schreiben? (Ich bin mir ziemlich sicher, dass Sie es besser nennen wird)

public static bool NotEmptyAll<T>(
    this IEnumerable<T> collection, 
    Func<T, bool> predicate) 
{ 
    return collection != null 
     && collection.Any() 
     && collection.All(predicate); 
} 

es dann statt All

var exist = definitions.Where(
     x => propertyTypeIds.Contains(x.PropertyTypeId) && x.CountryId == countryId) 
     .GroupBy(x => x.PropertyTypeId) 
     .NotEmptyAll(
      ...some condition...)); 
+5

Beachten Sie, dass dies die Abfrage zweimal ausführt, was möglicherweise eine schlechte Idee ist. –

+0

Danke für die Warnung @JonSkeet. Aber es ist die einzige Lösung, die mir in den Sinn kommt. Da es nicht mit "EntityFramework" gekennzeichnet ist, denke ich, dass es generell nicht viel kosten sollte. –

+0

Nun, es wird doppelt so viel kosten wie einmal ... es ist auch ziemlich unLINQ-ähnlich ... es gibt keinen regulären LINQ-Operator, der seine Eingabe mehr als einmal auswertet, IIRC. –

1

Nun nennen Sie es in zwei Schritten tun:

var definitions = definitions.Where(
        x => propertyTypeIds.Contains(x.PropertyTypeId) && x.CountryId == countryId) 
        .GroupBy(x => x.PropertyTypeId); 

var exist = definitions.Any() && definitions.All(...some condition...); 
+5

Es gibt keine Notwendigkeit für die Nullitätsprüfung - aber Sie müssen sich bewusst sein, dass dies die Abfrage zweimal ausführen wird. –

+2

Wenn dies jedoch etwas wie Entity Framework ist, werden zwei Abfragen für die Datenbank ausgeführt. – DavidG

+0

Wahr, es wird die Abfrage zweimal ausführen. Danke, dass du es gezeigt hast. – Fabjan

5

können Sie in der Lage sein, dies unter Verwendung eines Aggregate zu tun, in der Art von:

.Aggregate(new {exists = 0, matches = 0}, (a, g) => 
     new {exists = a.exists + 1, matches = a.matches + g > 10 ? 1 : 0}) 

(Hier g > 10 ist mein Test)

Und dann einfache Logik, dass exists größer als Null ist und dass exists und matches den gleichen Wert haben.

Dadurch wird vermieden, die gesamte Abfrage zweimal auszuführen.

3

Sie könnten die Verwendung von DefaultIfEmpty Erweiterungsmethode machen, und passen Sie Ihre some condition so dass es wertet null zu false.

var exist = definitions 
    .Where(x => propertyTypeIds.Contains(x.PropertyTypeId) && x.CountryId == countryId) 
    .GroupBy(x => x.PropertyTypeId) 
    .DefaultIfEmpty() 
    .All(...some condition...)); 
+1

Sehr clever! Mit anderen Worten, '.All (g => g! = Null && (... irgendeine Bedingung ...))'. Zusätzliche Klammern sind möglicherweise nicht erforderlich, abhängig davon, wie "... einige Bedingung ..." geschrieben wird. – devgeezer

1

Bearbeiten: erste Antwort hätte nicht funktioniert.

Wenn Sie Ihre Abfrage etwas neu anordnen, können Sie DefaultIfEmpty, ohne verwenden, um Ihre Bedingung zu ändern:

var exist = dbContext.Definitions 
        .Where(x => propertyTypeIds.Contains(x.PropertyTypeId) 
            && x.CountryId == countryId) 
        .GroupBy(x => x.PropertyTypeId); 

      // apply the condition to all entries, 
      // resulting in sequence of bools (or empty), 
      // to permit the next step 
        .Select(...some condition...) 

      //if seq is empty, add `false` 
        .DefaultIfEmpty(false) 

      //All with identity function to apply the query and calculate result 
        .All(b => b) 
     ); 
+1

Um einen Kommentar aus einer gelöschten Antwort zu stehlen (die Sie nicht sehen können): "Aber dann müssen Sie das Ergebnis von Any negieren, um die gleichen Ergebnisse wie von All zu erhalten (true, wenn es für alle wahr ist, sonst false) und du bist gleich wieder in der gleichen Situation " – DavidG

+0

@DavidG verdammt, das ist richtig. – RoadieRich

+0

@DavidG ist die neue Version besser? – RoadieRich

12

Wenn Sie LINQ to Objects verwenden, würde ich nur meine eigene Erweiterungsmethode schreiben. Mein Edulinq project hat Beispielcode für All, und dass die Anpassung ist ziemlich einfach:

public static bool AnyAndAll<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate) 
{ 
    if (source == null) 
    { 
     throw new ArgumentNullException(nameof(source)); 
    } 
    if (predicate == null) 
    { 
     throw new ArgumentNullException(nameof(predicate)); 
    } 

    bool any = false; 
    foreach (TSource item in source) 
    { 
     any = true; 
     if (!predicate(item)) 
     { 
      return false; 
     } 
    } 
    return any; 
} 

Dies vermeidet die Eingabe mehr als einmal auswertet.

0

ist hier ein anderer Trick:

var exist = dbContext.Definitions 
    .Where(x => propertyTypeIds.Contains(x.PropertyTypeId) && x.CountryId == countryId) 
    .GroupBy(x => x.PropertyTypeId) 
    .Min(some_condition ? (int?)1 : 0) == 1; 

Es nutzt die Tatsache, dass die oben Min<int?> Methode zurückgibt:

(A) null wenn die eingestellte
(B) 0 wenn die leer ist Bedingung ist für einige Elemente nicht erfüllt
(C) 1 Wenn die Bedingung für alle Elemente

erfüllt ist

so überprüfen wir einfach das Ergebnis für (C) mit den Nullwertvergleichsregeln.

Verwandte Themen