Skip to content

Commit

Permalink
assert that type handlers don't need object? (#1960)
Browse files Browse the repository at this point in the history
* assert that type handlers don't need object? - include test to validate (plus: NRT check was happy)

fix #1959

* release notes
  • Loading branch information
mgravell committed Sep 13, 2023
1 parent 61ff85a commit a4a55f5
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 17 deletions.
8 changes: 4 additions & 4 deletions Dapper/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#nullable enable
abstract Dapper.SqlMapper.StringTypeHandler<T>.Format(T xml) -> string!
abstract Dapper.SqlMapper.StringTypeHandler<T>.Parse(string! xml) -> T
abstract Dapper.SqlMapper.TypeHandler<T>.Parse(object? value) -> T?
abstract Dapper.SqlMapper.TypeHandler<T>.Parse(object! value) -> T?
abstract Dapper.SqlMapper.TypeHandler<T>.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void
const Dapper.DbString.DefaultLength = 4000 -> int
Dapper.CommandDefinition
Expand Down Expand Up @@ -130,8 +130,8 @@ Dapper.SqlMapper.IParameterCallbacks.OnCompleted() -> void
Dapper.SqlMapper.IParameterLookup
Dapper.SqlMapper.IParameterLookup.this[string! name].get -> object?
Dapper.SqlMapper.ITypeHandler
Dapper.SqlMapper.ITypeHandler.Parse(System.Type! destinationType, object? value) -> object?
Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, object? value) -> void
Dapper.SqlMapper.ITypeHandler.Parse(System.Type! destinationType, object! value) -> object?
Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void
Dapper.SqlMapper.ITypeMap
Dapper.SqlMapper.ITypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo?
Dapper.SqlMapper.ITypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo?
Expand All @@ -149,7 +149,7 @@ override Dapper.DbString.ToString() -> string!
override Dapper.SqlMapper.Identity.Equals(object? obj) -> bool
override Dapper.SqlMapper.Identity.GetHashCode() -> int
override Dapper.SqlMapper.Identity.ToString() -> string!
override Dapper.SqlMapper.StringTypeHandler<T>.Parse(object? value) -> T
override Dapper.SqlMapper.StringTypeHandler<T>.Parse(object! value) -> T
override Dapper.SqlMapper.StringTypeHandler<T>.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void
readonly Dapper.SqlMapper.Identity.commandType -> System.Data.CommandType?
readonly Dapper.SqlMapper.Identity.connectionString -> string!
Expand Down
4 changes: 2 additions & 2 deletions Dapper/SqlDataRecordHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ namespace Dapper
internal sealed class SqlDataRecordHandler<T> : SqlMapper.ITypeHandler
where T : IDataRecord
{
public object Parse(Type destinationType, object? value)
public object Parse(Type destinationType, object value)
{
throw new NotSupportedException();
}

public void SetValue(IDbDataParameter parameter, object? value)
public void SetValue(IDbDataParameter parameter, object value)
{
SqlDataRecordListTVPParameter<T>.Set(parameter, value as IEnumerable<T>, null);
}
Expand Down
4 changes: 2 additions & 2 deletions Dapper/SqlMapper.ITypeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ public interface ITypeHandler
/// </summary>
/// <param name="parameter">The parameter to configure</param>
/// <param name="value">Parameter value</param>
void SetValue(IDbDataParameter parameter, object? value);
void SetValue(IDbDataParameter parameter, object value);

/// <summary>
/// Parse a database value back to a typed value
/// </summary>
/// <param name="value">The value from the database</param>
/// <param name="destinationType">The type to parse to</param>
/// <returns>The typed value</returns>
object? Parse(Type destinationType, object? value);
object? Parse(Type destinationType, object value);
}
}
}
8 changes: 4 additions & 4 deletions Dapper/SqlMapper.TypeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public abstract class TypeHandler<T> : ITypeHandler
/// </summary>
/// <param name="value">The value from the database</param>
/// <returns>The typed value</returns>
public abstract T? Parse(object? value);
public abstract T? Parse(object value);

void ITypeHandler.SetValue(IDbDataParameter parameter, object? value)
void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
{
if (value is DBNull)
{
Expand All @@ -37,7 +37,7 @@ void ITypeHandler.SetValue(IDbDataParameter parameter, object? value)
}
}

object? ITypeHandler.Parse(Type destinationType, object? value)
object? ITypeHandler.Parse(Type destinationType, object value)
{
return Parse(value);
}
Expand Down Expand Up @@ -76,7 +76,7 @@ public override void SetValue(IDbDataParameter parameter, T? value)
/// </summary>
/// <param name="value">The value from the database</param>
/// <returns>The typed value</returns>
public override T Parse(object? value)
public override T Parse(object value)
{
if (value is null || value is DBNull) return default!;
return Parse((string)value);
Expand Down
4 changes: 2 additions & 2 deletions Dapper/UdtTypeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ public UdtTypeHandler(string udtTypeName)
this.udtTypeName = udtTypeName;
}

object? ITypeHandler.Parse(Type destinationType, object? value)
object? ITypeHandler.Parse(Type destinationType, object value)
{
return value is DBNull ? null : value;
}

void ITypeHandler.SetValue(IDbDataParameter parameter, object? value)
void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
{
#pragma warning disable 0618
parameter.Value = SanitizeParameterValue(value);
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command
(note: new PRs will not be merged until they add release note wording here)

- add untyped `GridReader.ReadUnbufferedAsync` API (#1958 via @mgravell)
- tweak NRT annotations on type-handler API (#1960 via @mgravell, fixes #1959)

### 2.1.1

Expand Down
2 changes: 1 addition & 1 deletion tests/Dapper.Tests/DecimalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public void Issue261_Decimals()
parameters.Add("c", dbType: DbType.Decimal, direction: ParameterDirection.Output, precision: 10, scale: 5);
connection.Execute("create proc #Issue261 @c decimal(10,5) OUTPUT as begin set @c=11.884 end");
connection.Execute("#Issue261", parameters, commandType: CommandType.StoredProcedure);
var c = parameters.Get<Decimal>("c");
var c = parameters.Get<decimal>("c");
Assert.Equal(11.884M, c);
}

Expand Down
129 changes: 127 additions & 2 deletions tests/Dapper.Tests/TypeHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ private RatingValueHandler()
{
}

public static readonly RatingValueHandler Default = new RatingValueHandler();
public static readonly RatingValueHandler Default = new();

public override RatingValue Parse(object? value)
{
Expand Down Expand Up @@ -431,7 +431,7 @@ private StringListTypeHandler()
{
}

public static readonly StringListTypeHandler Default = new StringListTypeHandler();
public static readonly StringListTypeHandler Default = new();
//Just a simple List<string> type handler implementation
public override void SetValue(IDbDataParameter parameter, List<string>? value)
{
Expand Down Expand Up @@ -739,5 +739,130 @@ public Issue461_ParameterisedTypeConstructor(int id, string someValue, Blarg som
public string SomeValue { get; }
public Blarg SomeBlargValue { get; }
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void Issue1959_TypeHandlerNullability_Subclass(bool isNull)
{
Issue1959_Subclass_Handler.Register();
Issue1959_Subclass? when = isNull ? null : new(DateTime.Today);
var whenNotNull = when ?? new(new DateTime(1753, 1, 1));

var args = new HazIssue1959_Subclass { Id = 42, Nullable = when, NonNullable = whenNotNull };
var row = connection.QuerySingle<HazIssue1959_Subclass>(
"select @Id as [Id], @NonNullable as [NonNullable], @Nullable as [Nullable]",
args);

Assert.NotNull(row);
Assert.Equal(42, row.Id);
Assert.Equal(when, row.Nullable);
Assert.Equal(whenNotNull, row.NonNullable);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void Issue1959_TypeHandlerNullability_Raw(bool isNull)
{
Issue1959_Raw_Handler.Register();
Issue1959_Raw? when = isNull ? null : new(DateTime.Today);
var whenNotNull = when ?? new(new DateTime(1753, 1, 1));

var args = new HazIssue1959_Raw { Id = 42, Nullable = when, NonNullable = whenNotNull };
var row = connection.QuerySingle<HazIssue1959_Raw>(
"select @Id as [Id], @NonNullable as [NonNullable], @Nullable as [Nullable]",
args);

Assert.NotNull(row);
Assert.Equal(42, row.Id);
Assert.Equal(when, row.Nullable);
Assert.Equal(whenNotNull, row.NonNullable);
}

public class HazIssue1959_Subclass
{
public int Id { get; set; }
public Issue1959_Subclass NonNullable { get; set; }
public Issue1959_Subclass? Nullable { get; set; }
}

public class HazIssue1959_Raw
{
public int Id { get; set; }
public Issue1959_Raw NonNullable { get; set; }
public Issue1959_Raw? Nullable { get; set; }
}

public class Issue1959_Subclass_Handler : SqlMapper.TypeHandler<Issue1959_Subclass>
{
public static void Register() => SqlMapper.AddTypeHandler<Issue1959_Subclass>(Instance);
private Issue1959_Subclass_Handler() { }
private static readonly Issue1959_Subclass_Handler Instance = new();

public override Issue1959_Subclass Parse(object value)
{
Assert.NotNull(value);
Assert.IsType<DateTime>(value); // checking not DbNull etc
return new Issue1959_Subclass((DateTime)value);
}
public override void SetValue(IDbDataParameter parameter, TypeHandlerTests<TProvider>.Issue1959_Subclass value)
=> parameter.Value = value.Value;
}

public class Issue1959_Raw_Handler : SqlMapper.ITypeHandler
{
public static void Register() => SqlMapper.AddTypeHandler(typeof(Issue1959_Raw), Instance);
private Issue1959_Raw_Handler() { }
private static readonly Issue1959_Raw_Handler Instance = new();

void SqlMapper.ITypeHandler.SetValue(IDbDataParameter parameter, object value)
{
Assert.NotNull(value);
if (value is DBNull)
{
parameter.Value = value;
}
else
{
Assert.IsType<Issue1959_Raw>(value); // checking not DbNull etc
parameter.Value = ((Issue1959_Raw)value).Value;
}
}
object? SqlMapper.ITypeHandler.Parse(Type destinationType, object value)
{
Assert.NotNull(value);
Assert.IsType<DateTime>(value); // checking not DbNull etc
return new Issue1959_Raw((DateTime)value);
}
}

#pragma warning disable CA2231 // Overload operator equals on overriding value type Equals
public readonly struct Issue1959_Subclass : IEquatable<Issue1959_Subclass>
#pragma warning restore CA2231 // Overload operator equals on overriding value type Equals
{
public Issue1959_Subclass(DateTime value) => Value = value;
public readonly DateTime Value;
public override int GetHashCode() => Value.GetHashCode();
public override bool Equals(object? obj)
=> obj is Issue1959_Subclass other && Equals(other);
public bool Equals(Issue1959_Subclass other)
=> other.Value == Value;
public override string ToString() => Value.ToString();
}

#pragma warning disable CA2231 // Overload operator equals on overriding value type Equals
public readonly struct Issue1959_Raw : IEquatable<Issue1959_Raw>
#pragma warning restore CA2231 // Overload operator equals on overriding value type Equals
{
public Issue1959_Raw(DateTime value) => Value = value;
public readonly DateTime Value;
public override int GetHashCode() => Value.GetHashCode();
public override bool Equals(object? obj)
=> obj is Issue1959_Raw other && Equals(other);
public bool Equals(Issue1959_Raw other)
=> other.Value == Value;
public override string ToString() => Value.ToString();
}
}
}

0 comments on commit a4a55f5

Please sign in to comment.