diff --git a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs index b8c70fc1195..69897e0742a 100644 --- a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs +++ b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs @@ -1356,7 +1356,8 @@ protected virtual void ColumnDefinition( return; } - var columnType = operation.ColumnType ?? GetColumnType(schema, table, name, operation, model)!; + var columnType = operation.ColumnType ?? GetColumnType(schema, table, name, operation, model); + builder .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(name)) .Append(" ") @@ -1401,7 +1402,7 @@ protected virtual void ComputedColumnDefinition( /// The column metadata. /// The target model which may be if the operations exist without a model. /// The database/store type for the column. - protected virtual string? GetColumnType( + protected virtual string GetColumnType( string? schema, string tableName, string name, @@ -1429,7 +1430,7 @@ protected virtual void ComputedColumnDefinition( || table.Indexes.Any(u => u.Columns.Contains(column)); } - return Dependencies.TypeMappingSource.FindMapping( + var storeType = Dependencies.TypeMappingSource.FindMapping( operation.ClrType, null, keyOrIndex, @@ -1440,6 +1441,15 @@ protected virtual void ComputedColumnDefinition( operation.Precision, operation.Scale) ?.StoreType; + + if (storeType != null) + { + return storeType; + } + + var fullTableName = schema != null ? $"{schema}.{tableName}" : tableName; + throw new InvalidOperationException( + RelationalStrings.UnsupportedTypeForColumn(fullTableName, name, operation.ClrType?.Name ?? "unknown")); } /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 1248246cde2..c482e874ef2 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -2241,6 +2241,14 @@ public static string UnsupportedType(object? clrType) GetString("UnsupportedType", nameof(clrType)), clrType); + /// + /// Unable to find a store type mapping for column '{table}.{column}' with CLR type '{clrType}'. + /// + public static string UnsupportedTypeForColumn(object? table, object? column, object? clrType) + => string.Format( + GetString("UnsupportedTypeForColumn", nameof(table), nameof(column), nameof(clrType)), + table, column, clrType); + /// /// The database operation was expected to affect {expectedRows} row(s), but actually affected {actualRows} row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index c600ef49f15..6c91cc2ebeb 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -1306,6 +1306,9 @@ The current provider doesn't have a store type mapping for properties of type '{clrType}'. + + Unable to find a store type mapping for column '{table}.{column}' with CLR type '{clrType}'. + The database operation was expected to affect {expectedRows} row(s), but actually affected {actualRows} row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs index d57bfa6b187..a59c7413f9d 100644 --- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs +++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs @@ -383,9 +383,7 @@ protected override void Generate( { Check.DebugAssert(operation.DefaultValue is not null); - var typeMapping = (columnType != null - ? Dependencies.TypeMappingSource.FindMapping(operation.DefaultValue.GetType(), columnType) - : null) + var typeMapping = Dependencies.TypeMappingSource.FindMapping(operation.DefaultValue.GetType(), columnType) ?? Dependencies.TypeMappingSource.GetMappingForValue(operation.DefaultValue); defaultValueSql = typeMapping.GenerateSqlLiteral(operation.DefaultValue); @@ -1640,7 +1638,7 @@ protected override void ColumnDefinition( return; } - var columnType = operation.ColumnType ?? GetColumnType(schema, table, name, operation, model)!; + var columnType = operation.ColumnType ?? GetColumnType(schema, table, name, operation, model); builder .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(name)) .Append(" ") diff --git a/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs b/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs index d1d6e7e23ff..e43c0a88572 100644 --- a/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs +++ b/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs @@ -58,7 +58,7 @@ private bool IsSpatialiteColumn(AddColumnOperation operation, IModel? model) operation.Table, operation.Name, operation, - model)!); + model)); private IReadOnlyList RewriteOperations( IReadOnlyList migrationOperations, diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs index f8d9cb99007..22ddb64c6bf 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs @@ -1,6 +1,7 @@ // 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.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; @@ -1267,6 +1268,25 @@ FROM [sys].[default_constraints] [d] """); } + + + [ConditionalFact] + public void Invalid_column_type_for_unmappable_clr_type_throws_meaningful_exception() + { + var ex = Assert.Throws(() => + Generate( + new AddColumnOperation + { + Name = "TestColumn", + Table = "TestTable", + ClrType = typeof(System.IO.FileStream), // Unmappable CLR type + ColumnType = null, + IsNullable = false + })); + + Assert.Equal(RelationalStrings.UnsupportedTypeForColumn("TestTable", "TestColumn", "FileStream"), ex.Message); + } + private static void CreateGotModel(ModelBuilder b) => b.HasDefaultSchema("dbo").Entity( "Person", pb =>