Voyager.DBSafeCast 3.0.0

dotnet add package Voyager.DBSafeCast --version 3.0.0
                    
NuGet\Install-Package Voyager.DBSafeCast -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Voyager.DBSafeCast" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Voyager.DBSafeCast" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Voyager.DBSafeCast" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Voyager.DBSafeCast --version 3.0.0
                    
#r "nuget: Voyager.DBSafeCast, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Voyager.DBSafeCast@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Voyager.DBSafeCast&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Voyager.DBSafeCast&version=3.0.0
                    
Install as a Cake Tool

Voyager.DBSafeCast

DBNull-aware cast helpers for shuttling values between data readers / rows and CLR types, and back to DbParameter values via DBNull.Value sentinels.

Targets netstandard2.0 / net8.0 / net10.0. Strong-named.

Why

ADO.NET surfaces missing values as DBNull.Value, not null. Every read from IDataReader / DataRow has to disambiguate three states (null, DBNull, real value) before the value is usable as a CLR type. The reverse trip — turning a CLR Nullable<T> into a parameter — needs the same discipline. Writing if (reader.IsDBNull(i)) … else (int)reader[i] on every column gets old fast.

This library is two complementary families of one-liners that absorb the ceremony.

Install

dotnet add package Voyager.DBSafeCast

The public API lives in namespace System, so it's reachable without an explicit using — extension methods on object / Nullable<T> / string / Guid / byte[] resolve at call sites in DAL code without further setup.

Reading from a data source

using var reader = command.ExecuteReader();
while (reader.Read())
{
    int        id      = reader["Id"].Cast(0);                                 // extension
    Guid       trace   = reader["TraceId"].Cast(Guid.Empty);                   // extension
    string     name    = DBSafeCast.CastEmptyString(reader["Name"]);           // DBNull → ""
    DateTime?  closed  = DBSafeCast.CastNullableDateTime(reader["ClosedAt"]);  // DBNull → null
    byte[]?    payload = DBSafeCast.CastNullByteArray(reader["Payload"]);
}

Only Cast<T> and CastTimeSpan are extension methods on the read side; the typed CastNullable* / CastEmpty* / CastNull* helpers are regular static calls (DBSafeCast.X(value)). The write-side DBNullIF* family is all extension methods.

Cast<T> falls back to invariant-culture Parse when the source column is a string and T is decimal / int / long / byte — so a SQL column that comes back as text (legacy schema or varchar(MAX)) still yields a correctly-typed scalar without explicit handling.

Writing to a data source

command.Parameters.AddWithValue("@Id",       id.DBNullIF());                   // 0    → DBNull
command.Parameters.AddWithValue("@Name",     name.DBNullIFEmpty());            // ""   → DBNull
command.Parameters.AddWithValue("@ParentId", parentId.DBNullIF(0));            // 0/null → DBNull
command.Parameters.AddWithValue("@ClosedAt", closedAt.DBNullIFMin());          // MinValue/null → DBNull
command.Parameters.AddWithValue("@TraceId",  traceId.DBNullIF());              // Guid.Empty → DBNull
command.Parameters.AddWithValue("@Payload",  bytes.DBNullIF());                // null → DBNull

Every DBNullIF* returns object — either the underlying value or DBNull.Value — so it slots straight into AddWithValue / Value = / any other parameter slot that accepts object.

Runnable demo

dotnet run --project samples/BasicUsage

Walks through five typical patterns (scalar cast, nullable cast, sentinel collapse, IDataReader round-trip, DbParameter writes) against an in-memory DataTable — no SQL Server required.

Migration from 2.x to 3.x

Bump to 3.0 is breaking. Audit consumers for these specifically.

Target frameworks

net6.0 and net472 are gone. netstandard2.0 is in; it covers .NET Framework 4.6.2+ and any other runtime implementing netstandard2.0, so most .NET Framework consumers see no change. If you were pinned to net6, either upgrade the runtime or stay on Voyager.DBSafeCast 2.x.

Removed APIs

2.x 3.x
CastNullableString(object) Removed. Use CastEmptyString (or call site decides whether null or "" is desired).
CastString(object) [Obsolete] alias for CastEmptyString; will be removed in 4.0.

CastNullableString carried [Obsolete("Move to CastEmptyString")] since 2023; 3.0 finishes the job.

Changed behaviour

  • Culture-invariant string parsing. Cast<int>("1234", 0) / Cast<long> / Cast<byte> now use CultureInfo.InvariantCulture. Before: int.Parse(string) with the process culture. On a Polish process culture this could land on the wrong number for strings containing group separators. Only decimal was correct before.
  • CastTimeSpan(string) exception type. Bad inputs now throw FormatException (was IndexOutOfRangeException / OverflowException depending on the malformation). The new parser is TimeSpan.ParseExact(value, @"hh\:mm\:ss", InvariantCulture) — also stricter about the input shape: "12.34.56" was silently accepted before; it isn't now.
  • DBNullIF(this string?, string def) comparison moved from string.Compare(.., ignoreCase: true) (current culture) to StringComparison.OrdinalIgnoreCase. ASCII case-folding identical; non-ASCII sentinel comparisons (Turkish-I etc.) may now match differently.

Source-compatible cleanups

  • Predefined types normalised on the public surface (Byte[]byte[], Int32?int?, Stringstring, …). Source-compat.
  • DBNullIF(this int? value, int def) now actually has this — previously it was a regular static method. ABI-safe; the modifier doesn't change the binary.
  • Nullable annotations added: object? for inputs that accept null / DBNull, byte[]? / string? for outputs that return null. Consumers with Nullable=enable may see new flow-analysis warnings.

Infrastructure (no consumer-visible behaviour change)

  • Strong-name key, namespace System placement, and the names of every surviving public method are unchanged. Existing call sites continue to compile without code edits.
  • Embedded SourceLink: stepping into library source from a debugger now works out of the box.

License

MIT

Project roadmap

docs/adr/ADR-001-modernization-roadmap.md captures the eight-phase modernization plan that landed in 3.0 (build infra → editorconfig → code refactor → MinVer → CI/CD → tests → docs → optional Compass), with the rationale for each step and the alternatives that were rejected.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0 153 5/18/2026
3.0.0-preview.1 45 5/18/2026
2.0.1 1,513 11/21/2023
2.0.0 308 5/11/2023