Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1388,8 +1388,6 @@ protected virtual void ValidateCompatible(
storeObject.DisplayName()));
}

var typeMapping = property.GetRelationalTypeMapping();
var duplicateTypeMapping = duplicateProperty.GetRelationalTypeMapping();
var currentTypeString = property.GetColumnType(storeObject);
var previousTypeString = duplicateProperty.GetColumnType(storeObject);
if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase))
Expand All @@ -1406,8 +1404,12 @@ protected virtual void ValidateCompatible(
currentTypeString));
}

var currentProviderType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType;
var previousProviderType = duplicateTypeMapping.Converter?.ProviderClrType ?? duplicateTypeMapping.ClrType;
var typeMapping = property.GetRelationalTypeMapping();
var duplicateTypeMapping = duplicateProperty.GetRelationalTypeMapping();
var currentProviderType = typeMapping.Converter?.ProviderClrType.UnwrapNullableType()
?? typeMapping.ClrType;
var previousProviderType = duplicateTypeMapping.Converter?.ProviderClrType.UnwrapNullableType()
?? duplicateTypeMapping.ClrType;
if (currentProviderType != previousProviderType)
{
throw new InvalidOperationException(
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/Internal/ColumnBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public virtual Type ProviderClrType
}

var typeMapping = StoreTypeMapping;
var providerType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType;
var providerType = typeMapping.Converter?.ProviderClrType.UnwrapNullableType() ?? typeMapping.ClrType;

return _providerClrType = providerType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ private static ColumnAccessors CreateGeneric<TColumn>(IColumn column)
}

var providerValue = entry.GetCurrentProviderValue(property);
if (providerValue == null
&& !typeof(TColumn).IsNullableType())
if (providerValue == null)
{
return (value!, valueFound);
}
Expand Down Expand Up @@ -93,8 +92,7 @@ private static ColumnAccessors CreateGeneric<TColumn>(IColumn column)
}

var providerValue = entry.GetOriginalProviderValue(property);
if (providerValue == null
&& !typeof(TColumn).IsNullableType())
if (providerValue == null)
{
return (value!, valueFound);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,29 +53,29 @@ private static IRowForeignKeyValueFactory CreateSimple<TKey, TForeignKey>(
IValueConverterSelector valueConverterSelector)
where TKey : notnull
{
var dependentColumn = foreignKey.Columns.Single();
var dependentType = dependentColumn.ProviderClrType;
var principalType = foreignKey.PrincipalColumns.Single().ProviderClrType;
var dependentColumn = foreignKey.Columns.First();
var principalColumn = foreignKey.PrincipalColumns.First();
var columnAccessors = ((Column)dependentColumn).Accessors;

if (dependentType.IsNullableType()
&& principalType.IsNullableType())
if (principalColumn.ProviderClrType.IsNullableType()
|| (dependentColumn.IsNullable
&& principalColumn.IsNullable))
{
return new SimpleFullyNullableRowForeignKeyValueFactory<TKey, TForeignKey>(
foreignKey, dependentColumn, columnAccessors, valueConverterSelector);
}

if (dependentType.IsNullableType())
if (dependentColumn.IsNullable)
{
return (IRowForeignKeyValueFactory<TKey>)Activator.CreateInstance(
typeof(SimpleNullableRowForeignKeyValueFactory<,>).MakeGenericType(
typeof(TKey), typeof(TForeignKey)), foreignKey, dependentColumn, columnAccessors, valueConverterSelector)!;
}

return principalType.IsNullableType()
return principalColumn.IsNullable
? (IRowForeignKeyValueFactory<TKey>)Activator.CreateInstance(
typeof(SimpleNullablePrincipalRowForeignKeyValueFactory<,,>).MakeGenericType(
typeof(TKey), typeof(TKey).UnwrapNullableType(), typeof(TForeignKey)), foreignKey, dependentColumn, columnAccessors)!
typeof(SimpleNullablePrincipalRowForeignKeyValueFactory<,>).MakeGenericType(
typeof(TKey), typeof(TKey), typeof(TForeignKey)), foreignKey, dependentColumn, columnAccessors)!
: new SimpleNonNullableRowForeignKeyValueFactory<TKey, TForeignKey>(
foreignKey, dependentColumn, columnAccessors, valueConverterSelector);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SimpleNullablePrincipalRowForeignKeyValueFactory<TKey, TNonNullableKey, TForeignKey>
public class SimpleNullablePrincipalRowForeignKeyValueFactory<TKey, TForeignKey>
: RowForeignKeyValueFactory<TKey, TForeignKey>
where TNonNullableKey : struct
where TKey : notnull
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,29 @@ private sealed class NoNullsCustomEqualityComparer : IEqualityComparer<TKey>

public NoNullsCustomEqualityComparer(ValueComparer comparer)
{
_equals = (Func<TKey?, TKey?, bool>)comparer.EqualsExpression.Compile();
_hashCode = (Func<TKey, int>)comparer.HashCodeExpression.Compile();
var equals = comparer.EqualsExpression;
var getHashCode = comparer.HashCodeExpression;
var type = typeof(TKey);
if (type != comparer.Type)
{
var newEqualsParam1 = Expression.Parameter(type, "v1");
var newEqualsParam2 = Expression.Parameter(type, "v2");
equals = Expression.Lambda(
comparer.ExtractEqualsBody(
Expression.Convert(newEqualsParam1, comparer.Type),
Expression.Convert(newEqualsParam2, comparer.Type)),
newEqualsParam1, newEqualsParam2);


var newHashCodeParam = Expression.Parameter(type, "v");
getHashCode = Expression.Lambda(
comparer.ExtractHashCodeBody(
Expression.Convert(newHashCodeParam, comparer.Type)),
newHashCodeParam);
}

_equals = (Func<TKey?, TKey?, bool>)equals.Compile();
_hashCode = (Func<TKey, int>)getHashCode.Compile();
}

public bool Equals(TKey? x, TKey? y)
Expand Down
9 changes: 8 additions & 1 deletion src/EFCore.Relational/Update/ModificationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,14 @@ public bool TryPropagate(IColumnMappingBase mapping, IUpdateEntry entry)
if (property.GetAfterSaveBehavior() == PropertySaveBehavior.Save
|| entry.EntityState == EntityState.Added)
{
entry.SetStoreGeneratedValue(property, _currentValue);
var value = _currentValue;
var converter = property.GetTypeMapping().Converter;
if (converter != null)
{
value = converter.ConvertFromProvider(value);
}

entry.SetStoreGeneratedValue(property, value);
}

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ public virtual IComparer<IUpdateEntry> Create(IPropertyBase propertyBase)
if (IsGenericComparable(providerType, nonNullableProviderType))
{
var comparerType = modelType.IsClass
? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelType, converter.ProviderClrType)
? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelType, providerType)
: modelType == converter.ModelClrType
? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelType, converter.ProviderClrType)
? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelType, providerType)
: typeof(NullableStructCurrentProviderValueComparer<,>).MakeGenericType(
nonNullableModelType, converter.ProviderClrType);
nonNullableModelType, providerType);

return (IComparer<IUpdateEntry>)Activator.CreateInstance(comparerType, propertyBase, converter)!;
}
Expand Down
3 changes: 1 addition & 2 deletions src/EFCore/ChangeTracking/ValueComparer`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ public ValueComparer(
|| unwrappedType == typeof(Guid)
|| unwrappedType == typeof(bool)
|| unwrappedType == typeof(decimal)
|| unwrappedType == typeof(object)
)
|| unwrappedType == typeof(object))
{
return Expression.Lambda<Func<T?, T?, bool>>(
Expression.Equal(param1, param2),
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore/Metadata/Internal/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -787,8 +787,8 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior()
=> FindAnnotation(CoreAnnotationNames.ProviderClrType)?.GetConfigurationSource();

private Type GetEffectiveProviderClrType()
=> TypeMapping?.Converter?.ProviderClrType
?? ClrType.UnwrapNullableType();
=> (TypeMapping?.Converter?.ProviderClrType
?? ClrType).UnwrapNullableType();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -997,7 +997,7 @@ public virtual CoreTypeMapping? TypeMapping
/// </summary>
public virtual ValueComparer? GetProviderValueComparer()
=> GetProviderValueComparer(null)
?? (GetEffectiveProviderClrType() == ClrType
?? (GetEffectiveProviderClrType() == ClrType.UnwrapNullableType()
? GetKeyValueComparer()
: TypeMapping?.ProviderValueComparer);

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

using Microsoft.EntityFrameworkCore.TestModels.UpdatesModel;
Expand Down Expand Up @@ -108,6 +108,39 @@ public virtual void Save_with_shared_foreign_key()
});
}

[ConditionalFact]
public virtual void Can_use_shared_columns_with_conversion()
=> ExecuteWithStrategyInTransaction(
context =>
{
var person = new Person("1", null)
{
Address = new Address { Country = Country.Eswatini, City = "Bulembu" },
Country = "Eswatini"
};

context.Add(person);

context.SaveChanges();
},
context =>
{
var person = context.Set<Person>().Single();
person.Address = new Address { Country = Country.Türkiye, City = "Konya", ZipCode = 42100 };

context.SaveChanges();
},
context =>
{
var person = context.Set<Person>().Single();

Assert.Equal(Country.Türkiye, person.Address!.Country);
Assert.Equal("Konya", person.Address.City);
Assert.Equal(42100, person.Address.ZipCode);
Assert.Equal("Türkiye", person.Country);
Assert.Equal("42100", person.ZipCode);
});

[ConditionalFact]
public virtual void Swap_filtered_unique_index_values()
{
Expand Down Expand Up @@ -174,14 +207,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con

modelBuilder.Entity<Product>().HasIndex(p => new { p.Name, p.Price }).IsUnique().HasFilter("Name IS NOT NULL");

modelBuilder.Entity<Person>()
.Property(p => p.Country)
.HasColumnName("Country");

modelBuilder.Entity<Person>()
.OwnsOne(p => p.Address)
.Property(p => p.Country)
.HasColumnName("Country");
modelBuilder.Entity<Person>(pb =>
{
pb.Property(p => p.Country)
.HasColumnName("Country");
pb.Property(p => p.ZipCode)
.HasColumnName("ZipCode");
pb.OwnsOne(p => p.Address)
.Property(p => p.Country)
.HasColumnName("Country");
pb.OwnsOne(p => p.Address)
.Property(p => p.ZipCode)
.HasColumnName("ZipCode");
});

modelBuilder
.Entity<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,22 +421,26 @@ public virtual void Detects_incompatible_primary_key_columns_with_shared_table()
}

[ConditionalFact]
public virtual void Passes_on_not_configured_shared_columns_with_shared_table()
public virtual void Passes_on_shared_columns_with_shared_table()
{
var modelBuilder = CreateConventionModelBuilder();

modelBuilder.Entity<A>().HasOne<B>().WithOne(b => b.A).HasForeignKey<A>(a => a.Id).HasPrincipalKey<B>(b => b.Id).IsRequired();
modelBuilder.Entity<A>().Property(a => a.P0).HasColumnName(nameof(A.P0));
modelBuilder.Entity<A>().Property(a => a.P3).HasColumnName(nameof(A.P3))
.HasConversion(e => (long?)e, e => (int?)e);
modelBuilder.Entity<A>().Property(a => a.P1).IsRequired();
modelBuilder.Entity<A>().ToTable("Table");
modelBuilder.Entity<B>().Property(b => b.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt");
modelBuilder.Entity<B>().Property(b => b.P3).HasColumnName(nameof(A.P3))
.HasConversion(e => (long)e, e => (int?)e);
modelBuilder.Entity<B>().ToTable("Table");

Validate(modelBuilder);
}

[ConditionalFact]
public virtual void Throws_on_not_configured_shared_columns_with_shared_table_with_dependents()
public virtual void Throws_on_nullable_shared_columns_with_shared_table_with_dependents()
{
var modelBuilder = CreateConventionModelBuilder();

Expand All @@ -450,7 +454,7 @@ public virtual void Throws_on_not_configured_shared_columns_with_shared_table_wi
}

[ConditionalFact]
public virtual void Warns_on_not_configured_shared_columns_with_shared_table()
public virtual void Warns_on_no_required_columns_with_shared_table()
{
var modelBuilder = CreateConventionModelBuilder();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public class Address
{
public string City { get; set; } = null!;
public Country Country { get; set; }
public int? ZipCode { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public Person(string name, Person? parent)
public string Name { get; set; }
public int? ParentId { get; set; }
public string? Country { get; set; }
public string? ZipCode { get; set; }
public Person? Parent { get; set; }
public Address? Address { get; set; }
}
31 changes: 20 additions & 11 deletions test/EFCore.Specification.Tests/UpdatesTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,11 @@ public virtual void Can_change_enums_with_conversion()
=> ExecuteWithStrategyInTransaction(
context =>
{
var person = new Person("1", null) { Address = new Address { Country = Country.Eswatini, City = "Bulembu" }, Country = "Eswatini" };
var person = new Person("1", null)
{
Address = new Address { Country = Country.Eswatini, City = "Bulembu" },
Country = "Eswatini"
};

context.Add(person);

Expand All @@ -385,8 +389,9 @@ public virtual void Can_change_enums_with_conversion()
context =>
{
var person = context.Set<Person>().Single();
person.Address = new Address { Country = Country.Türkiye, City = "Konya" };
person.Address = new Address { Country = Country.Türkiye, City = "Konya", ZipCode = 42100 };
person.Country = "Türkiye";
person.ZipCode = "42100";

context.SaveChanges();
},
Expand All @@ -396,7 +401,9 @@ public virtual void Can_change_enums_with_conversion()

Assert.Equal(Country.Türkiye, person.Address!.Country);
Assert.Equal("Konya", person.Address.City);
Assert.Equal(42100, person.Address.ZipCode);
Assert.Equal("Türkiye", person.Country);
Assert.Equal("42100", person.ZipCode);
});

[ConditionalFact]
Expand Down Expand Up @@ -644,15 +651,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
.HasForeignKey(e => e.DependentId)
.HasPrincipalKey(e => e.PrincipalId);

modelBuilder.Entity<Person>()
.HasOne(p => p.Parent)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);

modelBuilder.Entity<Person>()
.OwnsOne(p => p.Address)
.Property(p => p.Country)
.HasConversion<string>();
modelBuilder.Entity<Person>(pb =>
{
pb.HasOne(p => p.Parent)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
pb.OwnsOne(p => p.Address)
.Property(p => p.Country)
.HasConversion<string>();
pb.Property(p => p.ZipCode)
.HasConversion<int?>(v => v == null ? null : int.Parse(v), v => v == null ? null : v.ToString()!);
});

modelBuilder.Entity<Category>().HasMany(e => e.ProductCategories).WithOne(e => e.Category)
.HasForeignKey(e => e.CategoryId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public virtual void Queries_work_but_SaveChanges_fails_when_keys_incompatible_in
Assert.Equal(
RelationalStrings.StoredKeyTypesNotConvertable(
nameof(OptionalSingleBad.PrincipalId), "uniqueidentifier", "bigint", nameof(PrincipalBad.Id)),
Assert.Throws<TargetInvocationException>(() => context.SaveChanges()).InnerException!.Message);
Assert.Throws<TargetInvocationException>(() => context.SaveChanges()).InnerException!.InnerException!.Message);
}

protected class MismatchedKeyTypesContextNoFks : MismatchedKeyTypesContext
Expand Down
Loading