Skip to content

Commit

Permalink
Allow HasDefaultValue where value is assignable to property (#25539)
Browse files Browse the repository at this point in the history
* Allow HasDefaultValue where value is assignable to property

Fixes #17780

* Updated baseline after merge conflict
  • Loading branch information
ajcvickers committed Aug 17, 2021
1 parent b603d37 commit c4bfe3b
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
Expand Down Expand Up @@ -799,7 +802,7 @@ public static void SetDefaultValue(this IMutableProperty property, object? value
}

var valueType = value.GetType();
if (property.ClrType.UnwrapNullableType() != valueType)
if (!property.ClrType.UnwrapNullableType().IsAssignableFrom(valueType))
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.TestUtilities;
Expand Down Expand Up @@ -56,6 +57,20 @@ protected ValueConvertersEndToEndTestBase(TFixture fixture)
{ typeof(PhysicalAddress), new object?[] { _physicalAddress1, _physicalAddress2, _physicalAddress1, _physicalAddress2 } },
{ typeof(TimeSpan), new object?[] { _timeSpan1, _timeSpan2, _timeSpan1, _timeSpan2 } },
{ typeof(Uri), new object?[] { _uri1, _uri2, _uri1, _uri2 } },
{ typeof(List<int>), new object?[]
{
new List<int> { 47, 48, 47, 46 },
new List<int> { 57, 58, 57, 56 },
new List<int> { 67, 68, 67, 66 },
new List<int> { 77, 78, 77, 76 },
} },
{ typeof(IEnumerable<int>), new object?[]
{
new List<int> { 47, 48, 47, 46 },
new List<int> { 57, 58, 57, 56 },
new List<int> { 67, 68, 67, 66 },
new List<int> { 77, 78, 77, 76 },
} },
};

protected static Dictionary<Type, object?[]> StringTestValues = new()
Expand Down Expand Up @@ -161,7 +176,8 @@ private static void SetPropertyValues(DbContext context, ConvertingEntity entity

var propertyEntry = entry.Property(property.Name);

if (previousValueIndex >= 0)
if (previousValueIndex >= 0
&& property.FindAnnotation("Relational:DefaultValue") == null)
{
Assert.Equal(testValues[previousValueIndex], propertyEntry.CurrentValue);
}
Expand Down Expand Up @@ -593,6 +609,12 @@ protected class ConvertingEntity
public int NonNullIntToNonNullString { get; set; }
public int? NullIntToNullString { get; set; }
public int? NullIntToNonNullString { get; set; }

public List<int>? NullableListOfInt { get; set; }
public List<int> ListOfInt { get; set; } = null!;

public IEnumerable<int>? NullableEnumerableOfInt { get; set; }
public IEnumerable<int> EnumerableOfInt { get; set; } = null!;
}

protected DbContext CreateContext()
Expand Down Expand Up @@ -788,6 +810,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.Property(e => e.NullStringToNonNullString).HasConversion(new NullStringToNonNullStringConverter()).IsRequired();
b.Property(e => e.NonNullStringToNullString).HasConversion(new NonNullStringToNullStringConverter())
.IsRequired(false);
b.Property(e => e.NullableListOfInt).HasConversion(
(ValueConverter?)new ListOfIntToJsonConverter(), new ListOfIntComparer());
b.Property(e => e.ListOfInt).HasConversion(
new ListOfIntToJsonConverter(), new ListOfIntComparer());
b.Property(e => e.NullableEnumerableOfInt).HasConversion(
(ValueConverter?)new EnumerableOfIntToJsonConverter(), new EnumerableOfIntComparer());
b.Property(e => e.EnumerableOfInt).HasConversion(
new EnumerableOfIntToJsonConverter(), new EnumerableOfIntComparer());
});
}
}
Expand Down Expand Up @@ -846,6 +880,48 @@ protected enum TheExperience : ushort
Noel,
Mitch
}

protected class ListOfIntToJsonConverter : ValueConverter<List<int>, string>
{
public ListOfIntToJsonConverter()
: base(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions?)null)!)
{
}
}

protected class ListOfIntComparer : ValueComparer<List<int>?>
{
public ListOfIntComparer()
: base(
(c1, c2) => (c1 == null && c2 == null) || (c1 != null && c2 != null && c1.SequenceEqual(c2)),
c => c == null ? 0 : c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c == null ? null : c.ToList())
{
}
}

protected class EnumerableOfIntToJsonConverter : ValueConverter<IEnumerable<int>, string>
{
public EnumerableOfIntToJsonConverter()
: base(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions?)null)!)
{
}
}

protected class EnumerableOfIntComparer : ValueComparer<IEnumerable<int>?>
{
public EnumerableOfIntComparer()
: base(
(c1, c2) => (c1 == null && c2 == null) || (c1 != null && c2 != null && c1.SequenceEqual(c2)),
c => c == null ? 0 : c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c == null ? null : (IEnumerable<int>)c.ToList())
{
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
Expand Down Expand Up @@ -148,6 +150,8 @@ public ValueConvertersEndToEndSqlServerTest(ValueConvertersEndToEndSqlServerFixt
[InlineData(nameof(ConvertingEntity.NullableUriToNullableString), "nvarchar(max)", true)]
[InlineData(nameof(ConvertingEntity.NullStringToNonNullString), "nvarchar(max)", false)]
[InlineData(nameof(ConvertingEntity.NonNullStringToNullString), "nvarchar(max)", true)]
[InlineData(nameof(ConvertingEntity.NullableListOfInt), "nvarchar(max)", true)]
[InlineData(nameof(ConvertingEntity.ListOfInt), "nvarchar(max)", false)]
public virtual void Properties_with_conversions_map_to_appropriately_null_columns(
string propertyName,
string databaseType,
Expand All @@ -171,7 +175,7 @@ public virtual void Can_use_custom_converters_without_property()
Assert.Empty(context.Set<ConvertingEntity>().Where(e => EF.Functions.DataLength((string)(object)new WrappedString { Value = "" }) == 1).ToList());
}

Assert.Equal(@"SELECT [c].[Id], [c].[BoolAsChar], [c].[BoolAsInt], [c].[BoolAsNullableChar], [c].[BoolAsNullableInt], [c].[BoolAsNullableString], [c].[BoolAsString], [c].[BytesAsNullableString], [c].[BytesAsString], [c].[CharAsNullableString], [c].[CharAsString], [c].[DateTimeOffsetToBinary], [c].[DateTimeOffsetToNullableBinary], [c].[DateTimeOffsetToNullableString], [c].[DateTimeOffsetToString], [c].[DateTimeToBinary], [c].[DateTimeToNullableBinary], [c].[DateTimeToNullableString], [c].[DateTimeToString], [c].[EnumToNullableNumber], [c].[EnumToNullableString], [c].[EnumToNumber], [c].[EnumToString], [c].[GuidToBytes], [c].[GuidToNullableBytes], [c].[GuidToNullableString], [c].[GuidToString], [c].[IPAddressToBytes], [c].[IPAddressToNullableBytes], [c].[IPAddressToNullableString], [c].[IPAddressToString], [c].[IntAsLong], [c].[IntAsNullableLong], [c].[NonNullIntToNonNullString], [c].[NonNullIntToNullString], [c].[NonNullStringToNullString], [c].[NullIntToNonNullString], [c].[NullIntToNullString], [c].[NullStringToNonNullString], [c].[NullableBoolAsChar], [c].[NullableBoolAsInt], [c].[NullableBoolAsNullableChar], [c].[NullableBoolAsNullableInt], [c].[NullableBoolAsNullableString], [c].[NullableBoolAsString], [c].[NullableBytesAsNullableString], [c].[NullableBytesAsString], [c].[NullableCharAsNullableString], [c].[NullableCharAsString], [c].[NullableDateTimeOffsetToBinary], [c].[NullableDateTimeOffsetToNullableBinary], [c].[NullableDateTimeOffsetToNullableString], [c].[NullableDateTimeOffsetToString], [c].[NullableDateTimeToBinary], [c].[NullableDateTimeToNullableBinary], [c].[NullableDateTimeToNullableString], [c].[NullableDateTimeToString], [c].[NullableEnumToNullableNumber], [c].[NullableEnumToNullableString], [c].[NullableEnumToNumber], [c].[NullableEnumToString], [c].[NullableGuidToBytes], [c].[NullableGuidToNullableBytes], [c].[NullableGuidToNullableString], [c].[NullableGuidToString], [c].[NullableIPAddressToBytes], [c].[NullableIPAddressToNullableBytes], [c].[NullableIPAddressToNullableString], [c].[NullableIPAddressToString], [c].[NullableIntAsLong], [c].[NullableIntAsNullableLong], [c].[NullableNumberToBytes], [c].[NullableNumberToNullableBytes], [c].[NullableNumberToNullableString], [c].[NullableNumberToString], [c].[NullablePhysicalAddressToBytes], [c].[NullablePhysicalAddressToNullableBytes], [c].[NullablePhysicalAddressToNullableString], [c].[NullablePhysicalAddressToString], [c].[NullableStringToBool], [c].[NullableStringToBytes], [c].[NullableStringToChar], [c].[NullableStringToDateTime], [c].[NullableStringToDateTimeOffset], [c].[NullableStringToEnum], [c].[NullableStringToGuid], [c].[NullableStringToNullableBool], [c].[NullableStringToNullableBytes], [c].[NullableStringToNullableChar], [c].[NullableStringToNullableDateTime], [c].[NullableStringToNullableDateTimeOffset], [c].[NullableStringToNullableEnum], [c].[NullableStringToNullableGuid], [c].[NullableStringToNullableNumber], [c].[NullableStringToNullableTimeSpan], [c].[NullableStringToNumber], [c].[NullableStringToTimeSpan], [c].[NullableTimeSpanToNullableString], [c].[NullableTimeSpanToNullableTicks], [c].[NullableTimeSpanToString], [c].[NullableTimeSpanToTicks], [c].[NullableUriToNullableString], [c].[NullableUriToString], [c].[NumberToBytes], [c].[NumberToNullableBytes], [c].[NumberToNullableString], [c].[NumberToString], [c].[PhysicalAddressToBytes], [c].[PhysicalAddressToNullableBytes], [c].[PhysicalAddressToNullableString], [c].[PhysicalAddressToString], [c].[StringToBool], [c].[StringToBytes], [c].[StringToChar], [c].[StringToDateTime], [c].[StringToDateTimeOffset], [c].[StringToEnum], [c].[StringToGuid], [c].[StringToNullableBool], [c].[StringToNullableBytes], [c].[StringToNullableChar], [c].[StringToNullableDateTime], [c].[StringToNullableDateTimeOffset], [c].[StringToNullableEnum], [c].[StringToNullableGuid], [c].[StringToNullableNumber], [c].[StringToNullableTimeSpan], [c].[StringToNumber], [c].[StringToTimeSpan], [c].[TimeSpanToNullableString], [c].[TimeSpanToNullableTicks], [c].[TimeSpanToString], [c].[TimeSpanToTicks], [c].[UriToNullableString], [c].[UriToString]
Assert.Equal(@"SELECT [c].[Id], [c].[BoolAsChar], [c].[BoolAsInt], [c].[BoolAsNullableChar], [c].[BoolAsNullableInt], [c].[BoolAsNullableString], [c].[BoolAsString], [c].[BytesAsNullableString], [c].[BytesAsString], [c].[CharAsNullableString], [c].[CharAsString], [c].[DateTimeOffsetToBinary], [c].[DateTimeOffsetToNullableBinary], [c].[DateTimeOffsetToNullableString], [c].[DateTimeOffsetToString], [c].[DateTimeToBinary], [c].[DateTimeToNullableBinary], [c].[DateTimeToNullableString], [c].[DateTimeToString], [c].[EnumToNullableNumber], [c].[EnumToNullableString], [c].[EnumToNumber], [c].[EnumToString], [c].[EnumerableOfInt], [c].[GuidToBytes], [c].[GuidToNullableBytes], [c].[GuidToNullableString], [c].[GuidToString], [c].[IPAddressToBytes], [c].[IPAddressToNullableBytes], [c].[IPAddressToNullableString], [c].[IPAddressToString], [c].[IntAsLong], [c].[IntAsNullableLong], [c].[ListOfInt], [c].[NonNullIntToNonNullString], [c].[NonNullIntToNullString], [c].[NonNullStringToNullString], [c].[NullIntToNonNullString], [c].[NullIntToNullString], [c].[NullStringToNonNullString], [c].[NullableBoolAsChar], [c].[NullableBoolAsInt], [c].[NullableBoolAsNullableChar], [c].[NullableBoolAsNullableInt], [c].[NullableBoolAsNullableString], [c].[NullableBoolAsString], [c].[NullableBytesAsNullableString], [c].[NullableBytesAsString], [c].[NullableCharAsNullableString], [c].[NullableCharAsString], [c].[NullableDateTimeOffsetToBinary], [c].[NullableDateTimeOffsetToNullableBinary], [c].[NullableDateTimeOffsetToNullableString], [c].[NullableDateTimeOffsetToString], [c].[NullableDateTimeToBinary], [c].[NullableDateTimeToNullableBinary], [c].[NullableDateTimeToNullableString], [c].[NullableDateTimeToString], [c].[NullableEnumToNullableNumber], [c].[NullableEnumToNullableString], [c].[NullableEnumToNumber], [c].[NullableEnumToString], [c].[NullableEnumerableOfInt], [c].[NullableGuidToBytes], [c].[NullableGuidToNullableBytes], [c].[NullableGuidToNullableString], [c].[NullableGuidToString], [c].[NullableIPAddressToBytes], [c].[NullableIPAddressToNullableBytes], [c].[NullableIPAddressToNullableString], [c].[NullableIPAddressToString], [c].[NullableIntAsLong], [c].[NullableIntAsNullableLong], [c].[NullableListOfInt], [c].[NullableNumberToBytes], [c].[NullableNumberToNullableBytes], [c].[NullableNumberToNullableString], [c].[NullableNumberToString], [c].[NullablePhysicalAddressToBytes], [c].[NullablePhysicalAddressToNullableBytes], [c].[NullablePhysicalAddressToNullableString], [c].[NullablePhysicalAddressToString], [c].[NullableStringToBool], [c].[NullableStringToBytes], [c].[NullableStringToChar], [c].[NullableStringToDateTime], [c].[NullableStringToDateTimeOffset], [c].[NullableStringToEnum], [c].[NullableStringToGuid], [c].[NullableStringToNullableBool], [c].[NullableStringToNullableBytes], [c].[NullableStringToNullableChar], [c].[NullableStringToNullableDateTime], [c].[NullableStringToNullableDateTimeOffset], [c].[NullableStringToNullableEnum], [c].[NullableStringToNullableGuid], [c].[NullableStringToNullableNumber], [c].[NullableStringToNullableTimeSpan], [c].[NullableStringToNumber], [c].[NullableStringToTimeSpan], [c].[NullableTimeSpanToNullableString], [c].[NullableTimeSpanToNullableTicks], [c].[NullableTimeSpanToString], [c].[NullableTimeSpanToTicks], [c].[NullableUriToNullableString], [c].[NullableUriToString], [c].[NumberToBytes], [c].[NumberToNullableBytes], [c].[NumberToNullableString], [c].[NumberToString], [c].[PhysicalAddressToBytes], [c].[PhysicalAddressToNullableBytes], [c].[PhysicalAddressToNullableString], [c].[PhysicalAddressToString], [c].[StringToBool], [c].[StringToBytes], [c].[StringToChar], [c].[StringToDateTime], [c].[StringToDateTimeOffset], [c].[StringToEnum], [c].[StringToGuid], [c].[StringToNullableBool], [c].[StringToNullableBytes], [c].[StringToNullableChar], [c].[StringToNullableDateTime], [c].[StringToNullableDateTimeOffset], [c].[StringToNullableEnum], [c].[StringToNullableGuid], [c].[StringToNullableNumber], [c].[StringToNullableTimeSpan], [c].[StringToNumber], [c].[StringToTimeSpan], [c].[TimeSpanToNullableString], [c].[TimeSpanToNullableTicks], [c].[TimeSpanToString], [c].[TimeSpanToTicks], [c].[UriToNullableString], [c].[UriToString]
FROM [ConvertingEntity] AS [c]
WHERE CAST(DATALENGTH(CAST(N'' AS nvarchar(max))) AS int) = 1", Fixture.TestSqlLoggerFactory.SqlStatements[0]);
}
Expand Down Expand Up @@ -203,6 +207,20 @@ protected override ITestStoreFactory TestStoreFactory

public TestSqlLoggerFactory TestSqlLoggerFactory
=> (TestSqlLoggerFactory)ListLoggerFactory;

protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
{
base.OnModelCreating(modelBuilder, context);

modelBuilder.Entity<ConvertingEntity>(
b =>
{
b.Property(e => e.NullableListOfInt).HasDefaultValue(new List<int>());
b.Property(e => e.ListOfInt).HasDefaultValue(new List<int>());
b.Property(e => e.NullableEnumerableOfInt).HasDefaultValue(Enumerable.Empty<int>());
b.Property(e => e.EnumerableOfInt).HasDefaultValue(Enumerable.Empty<int>());
});
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;

Expand Down Expand Up @@ -164,6 +166,20 @@ public class ValueConvertersEndToEndSqliteFixture : ValueConvertersEndToEndFixtu
{
protected override ITestStoreFactory TestStoreFactory
=> SqliteTestStoreFactory.Instance;

protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
{
base.OnModelCreating(modelBuilder, context);

modelBuilder.Entity<ConvertingEntity>(
b =>
{
b.Property(e => e.NullableListOfInt).HasDefaultValue(new List<int>());
b.Property(e => e.ListOfInt).HasDefaultValue(new List<int>());
b.Property(e => e.NullableEnumerableOfInt).HasDefaultValue(Enumerable.Empty<int>());
b.Property(e => e.EnumerableOfInt).HasDefaultValue(Enumerable.Empty<int>());
});
}
}
}
}
Expand Down

0 comments on commit c4bfe3b

Please sign in to comment.