Skip to content

Commit a4a55f5

Browse files
authored
assert that type handlers don't need object? (#1960)
* assert that type handlers don't need object? - include test to validate (plus: NRT check was happy) fix #1959 * release notes
1 parent 61ff85a commit a4a55f5

File tree

8 files changed

+143
-17
lines changed

8 files changed

+143
-17
lines changed

Dapper/PublicAPI.Shipped.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#nullable enable
22
abstract Dapper.SqlMapper.StringTypeHandler<T>.Format(T xml) -> string!
33
abstract Dapper.SqlMapper.StringTypeHandler<T>.Parse(string! xml) -> T
4-
abstract Dapper.SqlMapper.TypeHandler<T>.Parse(object? value) -> T?
4+
abstract Dapper.SqlMapper.TypeHandler<T>.Parse(object! value) -> T?
55
abstract Dapper.SqlMapper.TypeHandler<T>.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void
66
const Dapper.DbString.DefaultLength = 4000 -> int
77
Dapper.CommandDefinition
@@ -130,8 +130,8 @@ Dapper.SqlMapper.IParameterCallbacks.OnCompleted() -> void
130130
Dapper.SqlMapper.IParameterLookup
131131
Dapper.SqlMapper.IParameterLookup.this[string! name].get -> object?
132132
Dapper.SqlMapper.ITypeHandler
133-
Dapper.SqlMapper.ITypeHandler.Parse(System.Type! destinationType, object? value) -> object?
134-
Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, object? value) -> void
133+
Dapper.SqlMapper.ITypeHandler.Parse(System.Type! destinationType, object! value) -> object?
134+
Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void
135135
Dapper.SqlMapper.ITypeMap
136136
Dapper.SqlMapper.ITypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo?
137137
Dapper.SqlMapper.ITypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo?
@@ -149,7 +149,7 @@ override Dapper.DbString.ToString() -> string!
149149
override Dapper.SqlMapper.Identity.Equals(object? obj) -> bool
150150
override Dapper.SqlMapper.Identity.GetHashCode() -> int
151151
override Dapper.SqlMapper.Identity.ToString() -> string!
152-
override Dapper.SqlMapper.StringTypeHandler<T>.Parse(object? value) -> T
152+
override Dapper.SqlMapper.StringTypeHandler<T>.Parse(object! value) -> T
153153
override Dapper.SqlMapper.StringTypeHandler<T>.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void
154154
readonly Dapper.SqlMapper.Identity.commandType -> System.Data.CommandType?
155155
readonly Dapper.SqlMapper.Identity.connectionString -> string!

Dapper/SqlDataRecordHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ namespace Dapper
77
internal sealed class SqlDataRecordHandler<T> : SqlMapper.ITypeHandler
88
where T : IDataRecord
99
{
10-
public object Parse(Type destinationType, object? value)
10+
public object Parse(Type destinationType, object value)
1111
{
1212
throw new NotSupportedException();
1313
}
1414

15-
public void SetValue(IDbDataParameter parameter, object? value)
15+
public void SetValue(IDbDataParameter parameter, object value)
1616
{
1717
SqlDataRecordListTVPParameter<T>.Set(parameter, value as IEnumerable<T>, null);
1818
}

Dapper/SqlMapper.ITypeHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ public interface ITypeHandler
1515
/// </summary>
1616
/// <param name="parameter">The parameter to configure</param>
1717
/// <param name="value">Parameter value</param>
18-
void SetValue(IDbDataParameter parameter, object? value);
18+
void SetValue(IDbDataParameter parameter, object value);
1919

2020
/// <summary>
2121
/// Parse a database value back to a typed value
2222
/// </summary>
2323
/// <param name="value">The value from the database</param>
2424
/// <param name="destinationType">The type to parse to</param>
2525
/// <returns>The typed value</returns>
26-
object? Parse(Type destinationType, object? value);
26+
object? Parse(Type destinationType, object value);
2727
}
2828
}
2929
}

Dapper/SqlMapper.TypeHandler.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ public abstract class TypeHandler<T> : ITypeHandler
2323
/// </summary>
2424
/// <param name="value">The value from the database</param>
2525
/// <returns>The typed value</returns>
26-
public abstract T? Parse(object? value);
26+
public abstract T? Parse(object value);
2727

28-
void ITypeHandler.SetValue(IDbDataParameter parameter, object? value)
28+
void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
2929
{
3030
if (value is DBNull)
3131
{
@@ -37,7 +37,7 @@ void ITypeHandler.SetValue(IDbDataParameter parameter, object? value)
3737
}
3838
}
3939

40-
object? ITypeHandler.Parse(Type destinationType, object? value)
40+
object? ITypeHandler.Parse(Type destinationType, object value)
4141
{
4242
return Parse(value);
4343
}
@@ -76,7 +76,7 @@ public override void SetValue(IDbDataParameter parameter, T? value)
7676
/// </summary>
7777
/// <param name="value">The value from the database</param>
7878
/// <returns>The typed value</returns>
79-
public override T Parse(object? value)
79+
public override T Parse(object value)
8080
{
8181
if (value is null || value is DBNull) return default!;
8282
return Parse((string)value);

Dapper/UdtTypeHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ public UdtTypeHandler(string udtTypeName)
2222
this.udtTypeName = udtTypeName;
2323
}
2424

25-
object? ITypeHandler.Parse(Type destinationType, object? value)
25+
object? ITypeHandler.Parse(Type destinationType, object value)
2626
{
2727
return value is DBNull ? null : value;
2828
}
2929

30-
void ITypeHandler.SetValue(IDbDataParameter parameter, object? value)
30+
void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
3131
{
3232
#pragma warning disable 0618
3333
parameter.Value = SanitizeParameterValue(value);

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command
2525
(note: new PRs will not be merged until they add release note wording here)
2626

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

2930
### 2.1.1
3031

tests/Dapper.Tests/DecimalTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void Issue261_Decimals()
2020
parameters.Add("c", dbType: DbType.Decimal, direction: ParameterDirection.Output, precision: 10, scale: 5);
2121
connection.Execute("create proc #Issue261 @c decimal(10,5) OUTPUT as begin set @c=11.884 end");
2222
connection.Execute("#Issue261", parameters, commandType: CommandType.StoredProcedure);
23-
var c = parameters.Get<Decimal>("c");
23+
var c = parameters.Get<decimal>("c");
2424
Assert.Equal(11.884M, c);
2525
}
2626

tests/Dapper.Tests/TypeHandlerTests.cs

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ private RatingValueHandler()
374374
{
375375
}
376376

377-
public static readonly RatingValueHandler Default = new RatingValueHandler();
377+
public static readonly RatingValueHandler Default = new();
378378

379379
public override RatingValue Parse(object? value)
380380
{
@@ -431,7 +431,7 @@ private StringListTypeHandler()
431431
{
432432
}
433433

434-
public static readonly StringListTypeHandler Default = new StringListTypeHandler();
434+
public static readonly StringListTypeHandler Default = new();
435435
//Just a simple List<string> type handler implementation
436436
public override void SetValue(IDbDataParameter parameter, List<string>? value)
437437
{
@@ -739,5 +739,130 @@ public Issue461_ParameterisedTypeConstructor(int id, string someValue, Blarg som
739739
public string SomeValue { get; }
740740
public Blarg SomeBlargValue { get; }
741741
}
742+
743+
[Theory]
744+
[InlineData(true)]
745+
[InlineData(false)]
746+
public void Issue1959_TypeHandlerNullability_Subclass(bool isNull)
747+
{
748+
Issue1959_Subclass_Handler.Register();
749+
Issue1959_Subclass? when = isNull ? null : new(DateTime.Today);
750+
var whenNotNull = when ?? new(new DateTime(1753, 1, 1));
751+
752+
var args = new HazIssue1959_Subclass { Id = 42, Nullable = when, NonNullable = whenNotNull };
753+
var row = connection.QuerySingle<HazIssue1959_Subclass>(
754+
"select @Id as [Id], @NonNullable as [NonNullable], @Nullable as [Nullable]",
755+
args);
756+
757+
Assert.NotNull(row);
758+
Assert.Equal(42, row.Id);
759+
Assert.Equal(when, row.Nullable);
760+
Assert.Equal(whenNotNull, row.NonNullable);
761+
}
762+
763+
[Theory]
764+
[InlineData(true)]
765+
[InlineData(false)]
766+
public void Issue1959_TypeHandlerNullability_Raw(bool isNull)
767+
{
768+
Issue1959_Raw_Handler.Register();
769+
Issue1959_Raw? when = isNull ? null : new(DateTime.Today);
770+
var whenNotNull = when ?? new(new DateTime(1753, 1, 1));
771+
772+
var args = new HazIssue1959_Raw { Id = 42, Nullable = when, NonNullable = whenNotNull };
773+
var row = connection.QuerySingle<HazIssue1959_Raw>(
774+
"select @Id as [Id], @NonNullable as [NonNullable], @Nullable as [Nullable]",
775+
args);
776+
777+
Assert.NotNull(row);
778+
Assert.Equal(42, row.Id);
779+
Assert.Equal(when, row.Nullable);
780+
Assert.Equal(whenNotNull, row.NonNullable);
781+
}
782+
783+
public class HazIssue1959_Subclass
784+
{
785+
public int Id { get; set; }
786+
public Issue1959_Subclass NonNullable { get; set; }
787+
public Issue1959_Subclass? Nullable { get; set; }
788+
}
789+
790+
public class HazIssue1959_Raw
791+
{
792+
public int Id { get; set; }
793+
public Issue1959_Raw NonNullable { get; set; }
794+
public Issue1959_Raw? Nullable { get; set; }
795+
}
796+
797+
public class Issue1959_Subclass_Handler : SqlMapper.TypeHandler<Issue1959_Subclass>
798+
{
799+
public static void Register() => SqlMapper.AddTypeHandler<Issue1959_Subclass>(Instance);
800+
private Issue1959_Subclass_Handler() { }
801+
private static readonly Issue1959_Subclass_Handler Instance = new();
802+
803+
public override Issue1959_Subclass Parse(object value)
804+
{
805+
Assert.NotNull(value);
806+
Assert.IsType<DateTime>(value); // checking not DbNull etc
807+
return new Issue1959_Subclass((DateTime)value);
808+
}
809+
public override void SetValue(IDbDataParameter parameter, TypeHandlerTests<TProvider>.Issue1959_Subclass value)
810+
=> parameter.Value = value.Value;
811+
}
812+
813+
public class Issue1959_Raw_Handler : SqlMapper.ITypeHandler
814+
{
815+
public static void Register() => SqlMapper.AddTypeHandler(typeof(Issue1959_Raw), Instance);
816+
private Issue1959_Raw_Handler() { }
817+
private static readonly Issue1959_Raw_Handler Instance = new();
818+
819+
void SqlMapper.ITypeHandler.SetValue(IDbDataParameter parameter, object value)
820+
{
821+
Assert.NotNull(value);
822+
if (value is DBNull)
823+
{
824+
parameter.Value = value;
825+
}
826+
else
827+
{
828+
Assert.IsType<Issue1959_Raw>(value); // checking not DbNull etc
829+
parameter.Value = ((Issue1959_Raw)value).Value;
830+
}
831+
}
832+
object? SqlMapper.ITypeHandler.Parse(Type destinationType, object value)
833+
{
834+
Assert.NotNull(value);
835+
Assert.IsType<DateTime>(value); // checking not DbNull etc
836+
return new Issue1959_Raw((DateTime)value);
837+
}
838+
}
839+
840+
#pragma warning disable CA2231 // Overload operator equals on overriding value type Equals
841+
public readonly struct Issue1959_Subclass : IEquatable<Issue1959_Subclass>
842+
#pragma warning restore CA2231 // Overload operator equals on overriding value type Equals
843+
{
844+
public Issue1959_Subclass(DateTime value) => Value = value;
845+
public readonly DateTime Value;
846+
public override int GetHashCode() => Value.GetHashCode();
847+
public override bool Equals(object? obj)
848+
=> obj is Issue1959_Subclass other && Equals(other);
849+
public bool Equals(Issue1959_Subclass other)
850+
=> other.Value == Value;
851+
public override string ToString() => Value.ToString();
852+
}
853+
854+
#pragma warning disable CA2231 // Overload operator equals on overriding value type Equals
855+
public readonly struct Issue1959_Raw : IEquatable<Issue1959_Raw>
856+
#pragma warning restore CA2231 // Overload operator equals on overriding value type Equals
857+
{
858+
public Issue1959_Raw(DateTime value) => Value = value;
859+
public readonly DateTime Value;
860+
public override int GetHashCode() => Value.GetHashCode();
861+
public override bool Equals(object? obj)
862+
=> obj is Issue1959_Raw other && Equals(other);
863+
public bool Equals(Issue1959_Raw other)
864+
=> other.Value == Value;
865+
public override string ToString() => Value.ToString();
866+
}
742867
}
743868
}

0 commit comments

Comments
 (0)