Skip to content

Commit

Permalink
1. give clearer error messages when an ICustomQueryParameter is null (#…
Browse files Browse the repository at this point in the history
…2003)

2. provide a convenience .ctor on DbString
  • Loading branch information
mgravell committed Nov 23, 2023
1 parent 0272d82 commit c46c69a
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 3 deletions.
18 changes: 15 additions & 3 deletions Dapper/DbString.cs
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
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
Expand Up @@ -2637,6 +2637,16 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql)
{
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
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

0 comments on commit c46c69a

Please sign in to comment.