Voyager.DBSafeCast
3.0.0
dotnet add package Voyager.DBSafeCast --version 3.0.0
NuGet\Install-Package Voyager.DBSafeCast -Version 3.0.0
<PackageReference Include="Voyager.DBSafeCast" Version="3.0.0" />
<PackageVersion Include="Voyager.DBSafeCast" Version="3.0.0" />
<PackageReference Include="Voyager.DBSafeCast" />
paket add Voyager.DBSafeCast --version 3.0.0
#r "nuget: Voyager.DBSafeCast, 3.0.0"
#:package Voyager.DBSafeCast@3.0.0
#addin nuget:?package=Voyager.DBSafeCast&version=3.0.0
#tool nuget:?package=Voyager.DBSafeCast&version=3.0.0
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 useCultureInfo.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. Onlydecimalwas correct before. CastTimeSpan(string)exception type. Bad inputs now throwFormatException(wasIndexOutOfRangeException/OverflowExceptiondepending on the malformation). The new parser isTimeSpan.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 fromstring.Compare(.., ignoreCase: true)(current culture) toStringComparison.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?,String→string, …). Source-compat. DBNullIF(this int? value, int def)now actually hasthis— previously it was a regular static method. ABI-safe; the modifier doesn't change the binary.- Nullable annotations added:
object?for inputs that acceptnull/DBNull,byte[]?/string?for outputs that returnnull. Consumers withNullable=enablemay see new flow-analysis warnings.
Infrastructure (no consumer-visible behaviour change)
- Strong-name key, namespace
Systemplacement, 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
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 | Versions 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. |
-
.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 |