Skip to content

Commit d9b2021

Browse files
committed
Allow shared columns with nullable value converters
Make IColumn.ProviderClrType always non-nullable for value types Fixes #29531
1 parent 99a642b commit d9b2021

File tree

17 files changed

+185
-125
lines changed

17 files changed

+185
-125
lines changed

src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,8 +1388,6 @@ protected virtual void ValidateCompatible(
13881388
storeObject.DisplayName()));
13891389
}
13901390

1391-
var typeMapping = property.GetRelationalTypeMapping();
1392-
var duplicateTypeMapping = duplicateProperty.GetRelationalTypeMapping();
13931391
var currentTypeString = property.GetColumnType(storeObject);
13941392
var previousTypeString = duplicateProperty.GetColumnType(storeObject);
13951393
if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase))
@@ -1406,8 +1404,12 @@ protected virtual void ValidateCompatible(
14061404
currentTypeString));
14071405
}
14081406

1409-
var currentProviderType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType;
1410-
var previousProviderType = duplicateTypeMapping.Converter?.ProviderClrType ?? duplicateTypeMapping.ClrType;
1407+
var typeMapping = property.GetRelationalTypeMapping();
1408+
var duplicateTypeMapping = duplicateProperty.GetRelationalTypeMapping();
1409+
var currentProviderType = typeMapping.Converter?.ProviderClrType.UnwrapNullableType()
1410+
?? typeMapping.ClrType;
1411+
var previousProviderType = duplicateTypeMapping.Converter?.ProviderClrType.UnwrapNullableType()
1412+
?? duplicateTypeMapping.ClrType;
14111413
if (currentProviderType != previousProviderType)
14121414
{
14131415
throw new InvalidOperationException(

src/EFCore.Relational/Metadata/Internal/ColumnBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public virtual Type ProviderClrType
8484
}
8585

8686
var typeMapping = StoreTypeMapping;
87-
var providerType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType;
87+
var providerType = typeMapping.Converter?.ProviderClrType.UnwrapNullableType() ?? typeMapping.ClrType;
8888

8989
return _providerClrType = providerType;
9090
}

src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ private static ColumnAccessors CreateGeneric<TColumn>(IColumn column)
5050
}
5151

5252
var providerValue = entry.GetCurrentProviderValue(property);
53-
if (providerValue == null
54-
&& !typeof(TColumn).IsNullableType())
53+
if (providerValue == null)
5554
{
5655
return (value!, valueFound);
5756
}
@@ -93,8 +92,7 @@ private static ColumnAccessors CreateGeneric<TColumn>(IColumn column)
9392
}
9493

9594
var providerValue = entry.GetOriginalProviderValue(property);
96-
if (providerValue == null
97-
&& !typeof(TColumn).IsNullableType())
95+
if (providerValue == null)
9896
{
9997
return (value!, valueFound);
10098
}

src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactoryFactory.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,29 +53,29 @@ private static IRowForeignKeyValueFactory CreateSimple<TKey, TForeignKey>(
5353
IValueConverterSelector valueConverterSelector)
5454
where TKey : notnull
5555
{
56-
var dependentColumn = foreignKey.Columns.Single();
57-
var dependentType = dependentColumn.ProviderClrType;
58-
var principalType = foreignKey.PrincipalColumns.Single().ProviderClrType;
56+
var dependentColumn = foreignKey.Columns.First();
57+
var principalColumn = foreignKey.PrincipalColumns.First();
5958
var columnAccessors = ((Column)dependentColumn).Accessors;
6059

61-
if (dependentType.IsNullableType()
62-
&& principalType.IsNullableType())
60+
if (principalColumn.ProviderClrType.IsNullableType()
61+
|| (dependentColumn.IsNullable
62+
&& principalColumn.IsNullable))
6363
{
6464
return new SimpleFullyNullableRowForeignKeyValueFactory<TKey, TForeignKey>(
6565
foreignKey, dependentColumn, columnAccessors, valueConverterSelector);
6666
}
6767

68-
if (dependentType.IsNullableType())
68+
if (dependentColumn.IsNullable)
6969
{
7070
return (IRowForeignKeyValueFactory<TKey>)Activator.CreateInstance(
7171
typeof(SimpleNullableRowForeignKeyValueFactory<,>).MakeGenericType(
7272
typeof(TKey), typeof(TForeignKey)), foreignKey, dependentColumn, columnAccessors, valueConverterSelector)!;
7373
}
7474

75-
return principalType.IsNullableType()
75+
return principalColumn.IsNullable
7676
? (IRowForeignKeyValueFactory<TKey>)Activator.CreateInstance(
77-
typeof(SimpleNullablePrincipalRowForeignKeyValueFactory<,,>).MakeGenericType(
78-
typeof(TKey), typeof(TKey).UnwrapNullableType(), typeof(TForeignKey)), foreignKey, dependentColumn, columnAccessors)!
77+
typeof(SimpleNullablePrincipalRowForeignKeyValueFactory<,>).MakeGenericType(
78+
typeof(TKey), typeof(TKey), typeof(TForeignKey)), foreignKey, dependentColumn, columnAccessors)!
7979
: new SimpleNonNullableRowForeignKeyValueFactory<TKey, TForeignKey>(
8080
foreignKey, dependentColumn, columnAccessors, valueConverterSelector);
8181
}

src/EFCore.Relational/Update/Internal/SimpleNullablePrincipalRowForeignKeyValueFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal;
1111
/// any release. You should only use it directly in your code with extreme caution and knowing that
1212
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1313
/// </summary>
14-
public class SimpleNullablePrincipalRowForeignKeyValueFactory<TKey, TNonNullableKey, TForeignKey>
14+
public class SimpleNullablePrincipalRowForeignKeyValueFactory<TKey, TForeignKey>
1515
: RowForeignKeyValueFactory<TKey, TForeignKey>
16-
where TNonNullableKey : struct
16+
where TKey : notnull
1717
{
1818
/// <summary>
1919
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to

src/EFCore.Relational/Update/Internal/SimpleRowKeyValueFactory.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,29 @@ private sealed class NoNullsCustomEqualityComparer : IEqualityComparer<TKey>
138138

139139
public NoNullsCustomEqualityComparer(ValueComparer comparer)
140140
{
141-
_equals = (Func<TKey?, TKey?, bool>)comparer.EqualsExpression.Compile();
142-
_hashCode = (Func<TKey, int>)comparer.HashCodeExpression.Compile();
141+
var equals = comparer.EqualsExpression;
142+
var getHashCode = comparer.HashCodeExpression;
143+
var type = typeof(TKey);
144+
if (type != comparer.Type)
145+
{
146+
var newEqualsParam1 = Expression.Parameter(type, "v1");
147+
var newEqualsParam2 = Expression.Parameter(type, "v2");
148+
equals = Expression.Lambda(
149+
comparer.ExtractEqualsBody(
150+
Expression.Convert(newEqualsParam1, comparer.Type),
151+
Expression.Convert(newEqualsParam2, comparer.Type)),
152+
newEqualsParam1, newEqualsParam2);
153+
154+
155+
var newHashCodeParam = Expression.Parameter(type, "v");
156+
getHashCode = Expression.Lambda(
157+
comparer.ExtractHashCodeBody(
158+
Expression.Convert(newHashCodeParam, comparer.Type)),
159+
newHashCodeParam);
160+
}
161+
162+
_equals = (Func<TKey?, TKey?, bool>)equals.Compile();
163+
_hashCode = (Func<TKey, int>)getHashCode.Compile();
143164
}
144165

145166
public bool Equals(TKey? x, TKey? y)

src/EFCore.Relational/Update/ModificationCommand.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,14 @@ public bool TryPropagate(IColumnMappingBase mapping, IUpdateEntry entry)
981981
if (property.GetAfterSaveBehavior() == PropertySaveBehavior.Save
982982
|| entry.EntityState == EntityState.Added)
983983
{
984-
entry.SetStoreGeneratedValue(property, _currentValue);
984+
var value = _currentValue;
985+
var converter = property.GetTypeMapping().Converter;
986+
if (converter != null)
987+
{
988+
value = converter.ConvertFromProvider(value);
989+
}
990+
991+
entry.SetStoreGeneratedValue(property, value);
985992
}
986993

987994
return false;

src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ public virtual IComparer<IUpdateEntry> Create(IPropertyBase propertyBase)
5050
if (IsGenericComparable(providerType, nonNullableProviderType))
5151
{
5252
var comparerType = modelType.IsClass
53-
? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelType, converter.ProviderClrType)
53+
? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelType, providerType)
5454
: modelType == converter.ModelClrType
55-
? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelType, converter.ProviderClrType)
55+
? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelType, providerType)
5656
: typeof(NullableStructCurrentProviderValueComparer<,>).MakeGenericType(
57-
nonNullableModelType, converter.ProviderClrType);
57+
nonNullableModelType, providerType);
5858

5959
return (IComparer<IUpdateEntry>)Activator.CreateInstance(comparerType, propertyBase, converter)!;
6060
}

src/EFCore/ChangeTracking/ValueComparer`.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,7 @@ public ValueComparer(
117117
|| unwrappedType == typeof(Guid)
118118
|| unwrappedType == typeof(bool)
119119
|| unwrappedType == typeof(decimal)
120-
|| unwrappedType == typeof(object)
121-
)
120+
|| unwrappedType == typeof(object))
122121
{
123122
return Expression.Lambda<Func<T?, T?, bool>>(
124123
Expression.Equal(param1, param2),

src/EFCore/Metadata/Internal/Property.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,8 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior()
787787
=> FindAnnotation(CoreAnnotationNames.ProviderClrType)?.GetConfigurationSource();
788788

789789
private Type GetEffectiveProviderClrType()
790-
=> TypeMapping?.Converter?.ProviderClrType
791-
?? ClrType.UnwrapNullableType();
790+
=> (TypeMapping?.Converter?.ProviderClrType
791+
?? ClrType).UnwrapNullableType();
792792

793793
/// <summary>
794794
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -997,7 +997,7 @@ public virtual CoreTypeMapping? TypeMapping
997997
/// </summary>
998998
public virtual ValueComparer? GetProviderValueComparer()
999999
=> GetProviderValueComparer(null)
1000-
?? (GetEffectiveProviderClrType() == ClrType
1000+
?? (GetEffectiveProviderClrType() == ClrType.UnwrapNullableType()
10011001
? GetKeyValueComparer()
10021002
: TypeMapping?.ProviderValueComparer);
10031003

0 commit comments

Comments
 (0)