Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve null handling on ICustomQueryParameter (and add new DbString .ctor) #2003

Merged
merged 1 commit into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions Dapper/DbString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ public DbString()
Length = -1;
IsAnsi = IsAnsiDefault;
}

/// <summary>
/// Create a new DbString
/// </summary>
public DbString(string? value, int length = -1)
{
Value = value;
Length = length;
IsAnsi = IsAnsiDefault;
}

/// <summary>
/// Ansi vs Unicode
/// </summary>
Expand All @@ -44,12 +55,13 @@ public DbString()
/// The value of the string
/// </summary>
public string? Value { get; set; }

/// <summary>
/// Gets a string representation of this DbString.
/// </summary>
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})";

/// <summary>
/// Add the parameter to the command... internal use only
Expand Down
2 changes: 2 additions & 0 deletions Dapper/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<T>.Parse(object! value) -> T?
static Dapper.SqlMapper.TypeHandlerCache<T>.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void
static Dapper.SqlMapper.TypeMapProvider -> System.Func<System.Type!, Dapper.SqlMapper.ITypeMap!>!
19 changes: 19 additions & 0 deletions Dapper/SqlMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -3859,6 +3869,14 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro
return null;
}

/// <summary>
/// For internal use only
/// </summary>
[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");

/// <summary>
/// Throws a data exception, only used internally
/// </summary>
Expand All @@ -3867,6 +3885,7 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro
/// <param name="reader">The reader the exception occurred in.</param>
/// <param name="value">The value that caused the exception.</param>
[Obsolete(ObsoleteInternalUsageOnly, false)]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public static void ThrowDataException(Exception ex, int index, IDataReader reader, object? value)
{
Exception toThrow;
Expand Down
23 changes: 23 additions & 0 deletions tests/Dapper.Tests/MiscTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<InvalidOperationException>(() => 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()
{
Expand All @@ -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());
Expand Down