Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6.0.11 to 7.0.0: [...] are both mapped to column [..] but are configured to use differing provider types ('double' and 'double?').' #29531

Closed
Dunge opened this issue Nov 10, 2022 · 3 comments · Fixed by #29746
Labels
area-model-building closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported Servicing-approved type-bug
Milestone

Comments

@Dunge
Copy link

Dunge commented Nov 10, 2022

Include your code

When updating Microsoft.EntityFrameworkCore.SqlServer from 6.0.11 to 7.0.0 I receive a new error. I checked the breaking changes and full release notes and I can't see anything that should impact this.

I have four entities types mapped to the same database column. Column is named "LowVoltageThreshold" and is float nullable in the database.

Three of these entities map directly to a double? property:

        public double? Threshold { get; set; }

        public void Configure(EntityTypeBuilder<BearingChangedAlarmSetting> entity)
        {
            entity.HasBaseType<AlarmSetting>();
            entity.Property(e => e.Threshold).HasColumnName("LowVoltageThreshold");
        }

The other map to utility class containing a float? property, so I use a converter to convert from double? to float?:

    public class DistanceUnit
    {
        public float? Metres { get; set; }
        [...]
    }

    public DistanceUnit Threshold { get; set; }

    public void Configure(EntityTypeBuilder<RouteLengthChangedAlarmSetting> entity)
    {
        entity.HasBaseType<AlarmSetting>();
        entity.Property(e => e.Threshold)
            .HasColumnName("LowVoltageThreshold")
            .HasConversion(e => (double?)e.Metres, e => new DistanceUnit { Metres = (float?)e });
    }

When looking at the signature returned by HasConversion it indeed have the nullable "?" in the type:
image

So where does that "double" without the nullable comes from in the compatibility validation? And why did this behavior change in v7?

Include stack traces

Include the full exception message and stack trace for any exception you encounter.

System.InvalidOperationException: 'BearingChangedAlarmSetting.Threshold' and 'RouteLengthChangedAlarmSetting.Threshold' are both mapped to column 'LowVoltageThreshold' in 'AlarmSettings', but are configured to use differing provider types ('double' and 'double?').
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ValidateCompatible(IProperty property, IProperty duplicateProperty, String columnName, StoreObjectIdentifier& storeObject, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal.SqlServerModelValidator.ValidateCompatible(IProperty property, IProperty duplicateProperty, String columnName, StoreObjectIdentifier& storeObject, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ValidateSharedColumnsCompatibility(IReadOnlyList`1 mappedTypes, StoreObjectIdentifier& storeObject, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal.SqlServerModelValidator.ValidateSharedColumnsCompatibility(IReadOnlyList`1 mappedTypes, StoreObjectIdentifier& storeObject, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ValidateSharedTableCompatibility(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal.SqlServerModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()

Include provider and version information

EF Core version: 7.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0

@ajcvickers
Copy link
Member

Note for triage: full repro:

public static class Your
{
    public static string ConnectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow";
}

public class AlarmSetting
{
    public int Id { get; set; }
}

public class AlarmSetting1 : AlarmSetting
{
    public double? Threshold { get; set; }
}

public class AlarmSetting2 : AlarmSetting
{
    public double? Threshold { get; set; }
}

public class AlarmSetting3 : AlarmSetting
{
    public double? Threshold { get; set; }
}

public class AlarmSetting4 : AlarmSetting
{
    public DistanceUnit Threshold { get; set; }
}

public class DistanceUnit
{
    public float? Metres { get; set; }
}

public class SomeDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(Your.ConnectionString)
            //.LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AlarmSetting>();
        modelBuilder.Entity<AlarmSetting1>().Property(e => e.Threshold).HasColumnName("LowVoltageThreshold");
        modelBuilder.Entity<AlarmSetting2>().Property(e => e.Threshold).HasColumnName("LowVoltageThreshold");
        modelBuilder.Entity<AlarmSetting3>().Property(e => e.Threshold).HasColumnName("LowVoltageThreshold");
        modelBuilder.Entity<AlarmSetting4>().Property(e => e.Threshold)
            .HasColumnName("LowVoltageThreshold")
            .HasConversion(e => (double?)e.Metres, e => new DistanceUnit { Metres = (float?)e });
    }
}

public class Program
{
    public static void Main()
    {
        using (var context = new SomeDbContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
        }
    }
}

@colleonimattia
Copy link

colleonimattia commented Nov 16, 2022

Hi, I've noticed they changed the compatible validation:
This is EF Core 7.0.0

        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))
        {
            throw new InvalidOperationException(
                RelationalStrings.DuplicateColumnNameDataTypeMismatch(
                    duplicateProperty.DeclaringEntityType.DisplayName(),
                    duplicateProperty.Name,
                    property.DeclaringEntityType.DisplayName(),
                    property.Name,
                    columnName,
                    storeObject.DisplayName(),
                    previousTypeString,
                    currentTypeString));
        }

        var currentProviderType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType;
        var previousProviderType = duplicateTypeMapping.Converter?.ProviderClrType ?? duplicateTypeMapping.ClrType;
        if (currentProviderType != previousProviderType)
        {
            throw new InvalidOperationException(
                RelationalStrings.DuplicateColumnNameProviderTypeMismatch(
                    duplicateProperty.DeclaringEntityType.DisplayName(),
                    duplicateProperty.Name,
                    property.DeclaringEntityType.DisplayName(),
                    property.Name,
                    columnName,
                    storeObject.DisplayName(),
                    previousProviderType.ShortDisplayName(),
                    currentProviderType.ShortDisplayName()));
        }

and this is EF Core 6.0.11:

            var currentTypeString = property.GetColumnType(storeObject)
                ?? property.GetRelationalTypeMapping().StoreType;
            var previousTypeString = duplicateProperty.GetColumnType(storeObject)
                ?? duplicateProperty.GetRelationalTypeMapping().StoreType;
            if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException(
                    RelationalStrings.DuplicateColumnNameDataTypeMismatch(
                        duplicateProperty.DeclaringEntityType.DisplayName(),
                        duplicateProperty.Name,
                        property.DeclaringEntityType.DisplayName(),
                        property.Name,
                        columnName,
                        storeObject.DisplayName(),
                        previousTypeString,
                        currentTypeString));
            }

The first part, the confrontation by GetColumnType, is the same, but the second part is different: one uses ProviderClrType and the other StoreType.
I think this is a bug introduced and is conceptually wrong to use the ClrType: I have several entities that share a table using a discriminator. All of these entities have a column, payload, that is saved as jsonb and all are mapped to a different class. Until 7.0.0 all was working fine.

@ajcvickers
Copy link
Member

Note for triage: specifying the store type explicitly doesn't work as a workaround.

@ajcvickers ajcvickers added this to the 7.0.x milestone Nov 18, 2022
AndriySvyryd added a commit that referenced this issue Dec 3, 2022
Make IColumn.ProviderClrType always non-nullable for value types

Fixes #29531
@AndriySvyryd AndriySvyryd added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Dec 3, 2022
AndriySvyryd added a commit that referenced this issue Dec 5, 2022
Make IColumn.ProviderClrType always non-nullable for value types

Fixes #29531
AndriySvyryd added a commit that referenced this issue Dec 5, 2022
Make IColumn.ProviderClrType always non-nullable for value types

Fixes #29531
AndriySvyryd added a commit that referenced this issue Dec 5, 2022
Make IColumn.ProviderClrType always non-nullable for value types

Fixes #29531
AndriySvyryd added a commit that referenced this issue Dec 5, 2022
Make IColumn.ProviderClrType always non-nullable for value types

Fixes #29531
@AndriySvyryd AndriySvyryd removed their assignment Dec 5, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.x, 7.0.3 Dec 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-model-building closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported Servicing-approved type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants