Thomas Bandt

Über mich | Kontakt | Archiv

Kleiner Helper für Linq to Sql: ToListOrDefault() (Update)

Um Abhängigkeiten und unerwünschte Seiteneffekte zu vermeiden setze ich bei meinen Repositories in aller Regel darauf diese nach außen hin abzugrenzen. Das bedeutet, dass ich beispielsweise auf IEnumerable<> verzichte und stattdessen immer List<> verwende.

Das hat zwar auch ein paar Nachteile (Kein Austausch der Implementierung im Vgl. zu IList<>, kein Lazy-Loading usw.) aber eben auch Vorteile - insbesondere den, dass alle Queries gegen die Datenbank ausgeführt werden, bevor die Daten die Repository-Methode verlassen. Somit kann ich den Datenzugriff hier exakt abgrenzen. Außerdem erwarte ich von jeder Methode null als Rückgabewert für den Fall, dass keine Daten in der Datenbank gefunden wurden.

Nun ist mir aber beim Testen meiner Repositories (später mehr ...) aufgefallen, dass Folgendes niemals null zurückliefert:

   1:  public List<Model.User> GetUsersByClientID(Guid clientID)
   2:  {
   3:      return Database.Users.Where(u => u.FKClientID == clientID)
   4:          .Select(u => Map(u)).ToList();
   5:  }

Im Zweifel ist das Ergebnis einfach eine leere Liste. Nicht besonders toll. Ich habe kurz recherchiert, aber keine wirklich elegante Lösung gefunden, da es für ToList() kein Gegenstück wie beispielsweise für Single() gibt - hier kann man mit SingleOrDefault() schlicht null zurückgeben lassen, wenn es keinen Treffer gibt.

Nun kann man in jeder Methode entsprechend auf einen Count von 0 prüfen und dann null zurückgeben - oder man packt es in eine Extension Method, die man dann genauso wie die vorhanden verwenden kann. Das dürfte zwar zum Lesen für Leute, die den Code nicht kennen, auf den ersten Blick schwieriger sein, aber hier überwiegen für mich die Vorteile ganz eindeutig.

   1:  public static class ToListOrDefaultExtension
   2:  {
   3:      public static List<TSource> ToListOrDefault(
   4:          this IQueryable<TSource> source)
   5:      {
   6:          return source.Count() > 0 ? source.ToList() : null;
   7:      }
   8:  }

Das Ergebnis:

   1:  public List<Model.User> GetUsersByClientID(Guid clientID)
   2:  {
   3:      return Database.Users.Where(u => u.FKClientID == clientID).Select(u => Map(u))
   4:          .ToListOrDefault();
   5:  }

That's it.

Update: Kommentare lesen ;-).

Kommentare

  1. Albert Weinert schrieb am Samstag, 1. Mai 2010 11:55:00 Uhr:

    Dass ist ja gerade das gute es eben nicht NULL zurückgeliefert wird, sondern eine leere Liste ;)

    Das zurückgeben von NULL ist ein zusätzlicher Aufwand, da dies dann beim weiteren Ablauf auch geprüft werden muss und nicht die, wenn auch leere, Liste einfach weiter durchgereicht werden kann. Deshalb IMMER bei Collection/Listen/Enumerables etc. bei keinen Daten eine leere Auflistung zurückgeben und nicht NULL.

  2. Thomas schrieb am Samstag, 1. Mai 2010 12:01:00 Uhr:

    Das IMMER mit der impliziten absoluten Wahrheit streichen wir mal.

    Aber du hast natürlich recht. Hätte ich mir mal angeschaut, wo ich die Repository-Methode verwende (die Tests haue ich gerade erst nachträglich drüber, anderes Thema), wär's mir auch aufgefallen.

    Also haue ich es mal schön wieder raus. Und gehe erst mal frühstücken, vielleicht denkt es sich dann klarer ;-).
  3. Daniel Marbach schrieb am Samstag, 1. Mai 2010 14:35:00 Uhr:

    Ausserdem könnte man noch Performance rausholen in dem man das Query zu return source.Any() ? source.ToList() : null; umschreibt. Aber ich pflichte Albert absolut zu.

    Geniess den Tag!

    Gruss Dani
  4. Ilker Cetinkaya schrieb am Samstag, 1. Mai 2010 15:42:00 Uhr:

    Ich unterstütze die allgemeine Empfehlung, bei leeren Ergebnissen nicht null zurückzuliefern. Es kann jedoch in einzelnen Fällen durchaus sinnvoll sein, null bei Leerergebnissen zurückzuliefern - das sollte aber selten der Fall sein.

    Also, jaa, Albert hat schon recht - und, jaa, Thomas hat auch recht, denn "IMMER" ist hier definitiv nicht angebracht.

    Null ist ein schwieriges Thema. Ich kenne ganz wenige Entwickler, die sich mit Null wirklich ernsthaft auseinandergesetzt haben. Zugegeben, es ist auch nicht einfach.

    Es wäre jetzt zuviel, wenn ich da vollends ausholen würde. Aber ich kann einen Rat geben, der in vielen Fällen weiterhelfen kann (mir hat es geholfen :-)).

    Diejenigen, die nicht genau wissen, ob oder ob nicht null als Rückgabe oder Zuweisung geeignet ist, sollten als "Entwscheidunghilfe" sich null wie eine Exception vorstellen. Null ist und soll eine Ausnahme darstellen. Null ist und soll auch signalisieren, das etwas nicht richtig sein kann. Das hilft in vielen Fällen weiter.

    Gruß,
    Ilker
  5. RalfW schrieb am Samstag, 1. Mai 2010 17:04:00 Uhr:

    Ausführlicher Kommentar hier: http://ralfw.blogspot.com/2010/05/null-oder-nicht-null-das-ist-hier-die.html
  6. Thomas schrieb am Samstag, 1. Mai 2010 17:48:00 Uhr:

    Ei ei ei ... nächstes Mal sollte ich zw. Problem, Lösung und Blogpost vielleicht mal 1 bis 2 Tage vertreichen lassen, in dem Fall hätte sich die Sache nämlich von selbst erledigt gehabt, denn ich denke es gibt hier einen klaren Konsens.

    Danke für eure Kommentare.
  7. Thomas goes .NET schrieb am Samstag, 1. Mai 2010 19:41:00 Uhr:

    Na da habe ich ja was ausgelöst
  8. Christoph Schmid schrieb am Sonntag, 2. Mai 2010 10:48:00 Uhr:

    Da würde ich gerne mal nach Eurer Meinung fragen, für eine Funktion in der Art
    GetAddressIDByAddressNumber(AddressNumber as string) as integer

    Wenn die Adressnummer nicht vorhanden ist,ist es dann ok, wenn eine 0 zurückgeliefert wird oder sollte das anders behandelt werden?

    Gruss Christoph
  9. Thomas schrieb am Sonntag, 2. Mai 2010 10:50:00 Uhr:

    Häng' dich mal hier mit ran:

    http://blog.thomasbandt.de/39/2333/de/blog/null-verstaendnis.html

    Imho wäre das nach der bisherigen Argumentation ein Fall für eine Exception.
  10. RalfW schrieb am Sonntag, 2. Mai 2010 11:57:00 Uhr:

    @Christoph: 0 ist offensichtlich keine gültige Id. Du könntest also sagen, dass 0 dem Ergebnis -1 entspricht, dass ein string.IndexOf(...) zurückliefert, wenn eine Zeichenkette nicht enthalten ist.

    Hm... so ganz bin ich nicht gegen die 0 in deinem Fall, weil 0 erstmal zur selben Kategorie wie eine gültige Id gehört, das die Kategorie "ganze Zahl". 0 entspräche also einem User.NonExistent.

    Aber ich würde noch weiter nachdenken wollen vor einer endgültigen Antwort. Wie sicher ist es denn, dass zu einer AddressNumber (was immer das sein mag) eine Id existiert? Ist das sehr sicher und die Nichtexistenz eine Ausnahme? Dann würde ich eher eine Exception werfen als 0 zurückgeben.

    Oder ist die Nichtexistenz eher der erwartbare Normalfall mit vielleicht >=20%? Dann wäre vielleicht ein bool TryGetUserIdByAddressNumber(...) angemessener.

    Bottom line: Die Entscheidung treffen aufgrund näherer Information. Noch ist deine Frage zu allgemein.

    -Ralf
  11. Daniel Marbach schrieb am Sonntag, 2. Mai 2010 16:34:00 Uhr:

    wenn man den kontext noch etwas weiter aufzieht und etwas provokativ gelaunt ist wie ich heute, kann man auch die frage in den raum werfen warum deine applikation die userid überhaupt kennen muss ;)


« Zurück  |  Weiter »