diff --git a/Dapper/DbString.cs b/Dapper/DbString.cs
index 14dc4612b..15b97d6bc 100644
--- a/Dapper/DbString.cs
+++ b/Dapper/DbString.cs
@@ -28,6 +28,17 @@ public DbString()
Length = -1;
IsAnsi = IsAnsiDefault;
}
+
+ ///
+ /// Create a new DbString
+ ///
+ public DbString(string? value, int length = -1)
+ {
+ Value = value;
+ Length = length;
+ IsAnsi = IsAnsiDefault;
+ }
+
///
/// Ansi vs Unicode
///
@@ -44,12 +55,13 @@ public DbString()
/// The value of the string
///
public string? Value { get; set; }
-
+
///
/// Gets a string representation of this DbString.
///
- public override string ToString() =>
- $"Dapper.DbString (Value: '{Value}', Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})";
+ public override string ToString() => Value is null
+ ? $"Dapper.DbString (Value: null, Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})"
+ : $"Dapper.DbString (Value: '{Value}', Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})";
///
/// Add the parameter to the command... internal use only
diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt
index 4c5417a6c..c1b08ea99 100644
--- a/Dapper/PublicAPI.Shipped.txt
+++ b/Dapper/PublicAPI.Shipped.txt
@@ -30,6 +30,7 @@ Dapper.CustomPropertyTypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.I
Dapper.DbString
Dapper.DbString.AddParameter(System.Data.IDbCommand! command, string! name) -> void
Dapper.DbString.DbString() -> void
+Dapper.DbString.DbString(string? value, int length = -1) -> void
Dapper.DbString.IsAnsi.get -> bool
Dapper.DbString.IsAnsi.set -> void
Dapper.DbString.IsFixedLength.get -> bool
@@ -323,6 +324,7 @@ static Dapper.SqlMapper.Settings.UseSingleRowOptimization.set -> void
static Dapper.SqlMapper.SetTypeMap(System.Type! type, Dapper.SqlMapper.ITypeMap? map) -> void
static Dapper.SqlMapper.SetTypeName(this System.Data.DataTable! table, string! typeName) -> void
static Dapper.SqlMapper.ThrowDataException(System.Exception! ex, int index, System.Data.IDataReader! reader, object? value) -> void
+static Dapper.SqlMapper.ThrowNullCustomQueryParameter(string! name) -> void
static Dapper.SqlMapper.TypeHandlerCache.Parse(object! value) -> T?
static Dapper.SqlMapper.TypeHandlerCache.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void
static Dapper.SqlMapper.TypeMapProvider -> System.Func!
\ No newline at end of file
diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs
index fb6ac88e6..fb151758e 100644
--- a/Dapper/SqlMapper.cs
+++ b/Dapper/SqlMapper.cs
@@ -2637,6 +2637,16 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true
{
il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [typed-param]
il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [custom]
+ if (!prop.PropertyType.IsValueType)
+ {
+ // throw if null
+ var notNull = il.DefineLabel();
+ il.Emit(OpCodes.Dup); // stack is [parameters] [custom] [custom]
+ il.Emit(OpCodes.Brtrue_S, notNull); // stack is [parameters] [custom]
+ il.Emit(OpCodes.Ldstr, prop.Name); // stack is [parameters] [custom] [name]
+ il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(ThrowNullCustomQueryParameter))!, null); // stack is [parameters] [custom]
+ il.MarkLabel(notNull);
+ }
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command]
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name]
il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter))!, null); // stack is now [parameters]
@@ -3859,6 +3869,14 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro
return null;
}
+ ///
+ /// For internal use only
+ ///
+ [Obsolete(ObsoleteInternalUsageOnly, false)]
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static void ThrowNullCustomQueryParameter(string name)
+ => throw new InvalidOperationException($"Member '{name}' is an {nameof(ICustomQueryParameter)} and cannot be null");
+
///
/// Throws a data exception, only used internally
///
@@ -3867,6 +3885,7 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro
/// The reader the exception occurred in.
/// The value that caused the exception.
[Obsolete(ObsoleteInternalUsageOnly, false)]
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public static void ThrowDataException(Exception ex, int index, IDataReader reader, object? value)
{
Exception toThrow;
diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs
index 24f7bf2bd..aaaf8e978 100644
--- a/tests/Dapper.Tests/MiscTests.cs
+++ b/tests/Dapper.Tests/MiscTests.cs
@@ -657,6 +657,27 @@ public void TestDbString()
Assert.Equal(10, (int)obj.f);
}
+ [Fact]
+ public void DbStringNullHandling()
+ {
+ // without lengths
+ var obj = new { x = new DbString("abc"), y = (DbString?)new DbString(null) };
+ var row = connection.QuerySingle<(string? x,string? y)>("select @x as x, @y as y", obj);
+ Assert.Equal("abc", row.x);
+ Assert.Null(row.y);
+
+ // with lengths
+ obj = new { x = new DbString("abc", 200), y = (DbString?)new DbString(null, 200) };
+ row = connection.QuerySingle<(string? x, string? y)>("select @x as x, @y as y", obj);
+ Assert.Equal("abc", row.x);
+ Assert.Null(row.y);
+
+ // null raw value - give clear message, at least
+ obj = obj with { y = null };
+ var ex = Assert.Throws(() => connection.QuerySingle<(string? x, string? y)>("select @x as x, @y as y", obj));
+ Assert.Equal("Member 'y' is an ICustomQueryParameter and cannot be null", ex.Message);
+ }
+
[Fact]
public void TestDbStringToString()
{
@@ -668,6 +689,8 @@ public void TestDbStringToString()
new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = true }.ToString());
Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: False, IsFixedLength: False)",
new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = false }.ToString());
+ Assert.Equal("Dapper.DbString (Value: null, Length: -1, IsAnsi: False, IsFixedLength: False)",
+ new DbString { Value = null }.ToString());
Assert.Equal("Dapper.DbString (Value: 'abcde', Length: -1, IsAnsi: True, IsFixedLength: False)",
new DbString { Value = "abcde", IsAnsi = true }.ToString());