Entwickler-Ecke

Datenbanken (inkl. ADO.NET) - null safe Zugriff auf string/int Spalte in DB


JohnDyr - Fr 04.01.19 18:04
Titel: null safe Zugriff auf string/int Spalte in DB
Moin,

meine Spalte kann entweder int oder null sein. Dafür habe ich folgenden Code geschrieben, welcher null-safe sein soll:

C#-Quelltext
1:
2:
3:
4:
5:
6:
internal static int GetLoaderFreightAmount1(int id)
{
  SqlCommand cmd = new SqlCommand("SELECT LoaderFreightAmount1 FROM [Order] WHERE ID = @id", Connection);
  cmd.Parameters.AddWithValue("@id", id);
  return cmd.ExecuteScalar() == DBNull.Value ? 0 : (int)cmd.ExecuteScalar();
}


Im Falle eines strings habe ich folgenden Code geschrieben:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
        
internal static string GetLoaderRef1(int id)
{
  SqlCommand cmd = new SqlCommand("SELECT LoaderRef1 FROM [Order] WHERE ID = @id", Connection);
  cmd.Parameters.AddWithValue("@id", id);
  return Convert.ToString(cmd.ExecuteScalar());
}


Würdet ihr das auch so machen? Geht das noch eleganter?


Th69 - Fr 04.01.19 18:50

Im ersten Fall solltest du cmd.ExecuteScalar() aber nur einmalig ausführen lassen (also Rückgabewert in einer Variablen speichern und vergleichen).


JohnDyr - Fr 04.01.19 19:16

Stimmt. Habe ich geändert.


C#-Quelltext
1:
2:
object res = cmd.ExecuteScalar(); 
return res == DBNull.Value? 0 : (double) res;


So oder?


Ralf Jansen - So 06.01.19 15:53

Wenn in der Datenbank nicht mal die passende Zeile gefunden wird liefert ExecuteScalar null zurück.
Z.B. also wenn in deiner GetLoaderFreightAmount1 Methode die id nicht existiert.

In dem Fall wird der cast knallen. Null lässt sich nicht nach int oder double casten.
Du mußt leider nicht nur auf DBNull.Value testen sondern auch auf null.


JohnDyr - So 06.01.19 16:33

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Wenn in der Datenbank nicht mal die passende Zeile gefunden wird liefert ExecuteScalar null zurück.
Z.B. also wenn in deiner GetLoaderFreightAmount1 Methode die id nicht existiert.

In dem Fall wird der cast knallen. Null lässt sich nicht nach int oder double casten.
Du mußt leider nicht nur auf DBNull.Value testen sondern auch auf null.


Das ist ja blöd... nun gut, habe es jetzt so gelöst:


C#-Quelltext
1:
2:
3:
4:
5:
6:
object res = cmd.ExecuteScalar();
  if (res == null || res == DBNull.Value)
  {
    return 0;
  }
  return (int) res;


Th69 - Mo 07.01.19 11:25

Das geht aber auch per ? : Operator:

C#-Quelltext
1:
2:
object res = cmd.ExecuteScalar();
return res == null || res == DBNull.Value? 0 : (int)res;

Oder man könnte daraus dann auch gleich eine generische Methode machen:

C#-Quelltext
1:
2:
3:
4:
5:
6:
T Exec<T>(...) where T : IConvertible
{
  // ...
  object res = cmd.ExecuteScalar(); 
  return res == null || res == DBNull.Value? default(T) : (T)res;
}

(jedoch gefällt mir die Rückgabe von 0 (bzw. default(T)) bei einem Fehler nicht so sehr)


Ralf Jansen - Mo 07.01.19 12:47

Zitat:
(jedoch gefällt mir die Rückgabe von 0 (bzw. default(T)) bei einem Fehler nicht so sehr)


Ob das ein Fehler ist ein wenig Definitionssache. "Nicht vorhanden" nicht von "es ist null" zu unterscheiden ist aber definitiv unschön.
Es konterkariert die Existenz von DBNull und es hat einen guten Grund warum es den Typ gibt auch wenn die Implementierung aufgrund des Alters von ADO.Net unschön ist.

Hier ist es jetzt sogar so das man 0 nicht von NULL nicht von nicht existent unterscheiden kann. Da es aber hier keine allgemeine Lösung ist sondern für ein bestimmtes Feld
einer bestimmten Tabelle könnte ich damit leben wenn das für dieses Feld ok ist. Man sollte das nur nicht für ein Standardmuster halten und immer so anwenden.


Th69 - Mo 07.01.19 14:37

Ja, "Fehler" ist Definitionssache.

Evtl. wäre aber dann Nullable<T> (kurz T?) hier besser (auch wenn man hier wieder explizit abfragen müßte, aber wenigstens wäre es typsicher - gegenüber nur object zurückzugeben):

C#-Quelltext
1:
2:
3:
4:
5:
6:
T? Exec<T>(...) where T : struct
{
  // ...
  object res = cmd.ExecuteScalar(); 
  return res == null || res == DBNull.Value? default(T?) : (T)res;
}

Nachträglich könnte man dann aber auch GetValueOrDefault() [https://docs.microsoft.com/de-de/dotnet/api/system.nullable-1.getvalueordefault?view=netframework-4.7.2] darauf aufrufen.