diff --git a/src/EFCore.Design/Metadata/Internal/ScaffoldingAnnotationNames.cs b/src/EFCore.Design/Metadata/Internal/ScaffoldingAnnotationNames.cs
index 8fc9d640c92..c16bc3b8ad3 100644
--- a/src/EFCore.Design/Metadata/Internal/ScaffoldingAnnotationNames.cs
+++ b/src/EFCore.Design/Metadata/Internal/ScaffoldingAnnotationNames.cs
@@ -43,6 +43,14 @@ public static class ScaffoldingAnnotationNames
///
public const string ConcurrencyToken = "ConcurrencyToken";
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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.
+ ///
+ public const string ClrType = "ClrType";
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore.Design/Scaffolding/Internal/IScaffoldingTypeMapper.cs b/src/EFCore.Design/Scaffolding/Internal/IScaffoldingTypeMapper.cs
index 69f25691872..6d97aad9f0c 100644
--- a/src/EFCore.Design/Scaffolding/Internal/IScaffoldingTypeMapper.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/IScaffoldingTypeMapper.cs
@@ -17,5 +17,5 @@ public interface IScaffoldingTypeMapper
/// 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.
///
- TypeScaffoldingInfo? FindMapping(string storeType, bool keyOrIndex, bool rowVersion);
+ TypeScaffoldingInfo? FindMapping(string storeType, bool keyOrIndex, bool rowVersion, Type? clrType = null);
}
diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs
index b1fdaf6da25..ae03e4f4143 100644
--- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs
@@ -487,7 +487,8 @@ protected virtual EntityTypeBuilder VisitColumns(EntityTypeBuilder builder, ICol
property.Metadata.AddAnnotations(
column.GetAnnotations().Where(
- a => a.Name != ScaffoldingAnnotationNames.ConcurrencyToken));
+ a => a.Name != ScaffoldingAnnotationNames.ConcurrencyToken
+ && a.Name != ScaffoldingAnnotationNames.ClrType));
return property;
}
@@ -967,7 +968,8 @@ protected virtual List ExistingIdentifiers(IReadOnlyEntityType entityTyp
return _scaffoldingTypeMapper.FindMapping(
column.StoreType,
column.IsKeyOrIndex(),
- column.IsRowVersion());
+ column.IsRowVersion(),
+ (Type?)column[ScaffoldingAnnotationNames.ClrType]);
}
private static void AssignOnDeleteAction(
diff --git a/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs b/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs
index 1668b1dd18a..7f62b00e9c5 100644
--- a/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs
@@ -33,9 +33,12 @@ public ScaffoldingTypeMapper(IRelationalTypeMappingSource typeMappingSource)
public virtual TypeScaffoldingInfo? FindMapping(
string storeType,
bool keyOrIndex,
- bool rowVersion)
+ bool rowVersion,
+ Type? clrType = null)
{
- var mapping = _typeMappingSource.FindMapping(storeType);
+ var mapping = clrType is null
+ ? _typeMappingSource.FindMapping(storeType)
+ : _typeMappingSource.FindMapping(clrType, storeType);
if (mapping == null)
{
return null;
diff --git a/src/EFCore.Sqlite.Core/Diagnostics/Internal/SqliteLoggingDefinitions.cs b/src/EFCore.Sqlite.Core/Diagnostics/Internal/SqliteLoggingDefinitions.cs
index 791ab4de185..493c1b531de 100644
--- a/src/EFCore.Sqlite.Core/Diagnostics/Internal/SqliteLoggingDefinitions.cs
+++ b/src/EFCore.Sqlite.Core/Diagnostics/Internal/SqliteLoggingDefinitions.cs
@@ -130,4 +130,28 @@ public class SqliteLoggingDefinitions : RelationalLoggingDefinitions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public EventDefinitionBase? LogCompositeKeyWithValueGeneration;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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.
+ ///
+ public EventDefinitionBase? LogInferringTypes;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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.
+ ///
+ public EventDefinitionBase? LogOutOfRangeWarning;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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.
+ ///
+ public EventDefinitionBase? LogFormatWarning;
}
diff --git a/src/EFCore.Sqlite.Core/Diagnostics/SqliteEventId.cs b/src/EFCore.Sqlite.Core/Diagnostics/SqliteEventId.cs
index e90221f6e4c..a5c85d3b1ed 100644
--- a/src/EFCore.Sqlite.Core/Diagnostics/SqliteEventId.cs
+++ b/src/EFCore.Sqlite.Core/Diagnostics/SqliteEventId.cs
@@ -47,7 +47,10 @@ private enum Id
PrimaryKeyFound,
SchemasNotSupportedWarning,
TableFound,
- UniqueConstraintFound
+ UniqueConstraintFound,
+ InferringTypes,
+ OutOfRangeWarning,
+ FormatWarning
}
private static readonly string ValidationPrefix = DbLoggerCategory.Model.Validation.Name + ".";
@@ -213,4 +216,28 @@ private static EventId MakeScaffoldingId(Id id)
/// This event is in the category.
///
public static readonly EventId UniqueConstraintFound = MakeScaffoldingId(Id.UniqueConstraintFound);
+
+ ///
+ /// Inferring CLR types.
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ public static readonly EventId InferringTypes = MakeScaffoldingId(Id.InferringTypes);
+
+ ///
+ /// Values are out of range for the type.
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ public static readonly EventId OutOfRangeWarning = MakeScaffoldingId(Id.OutOfRangeWarning);
+
+ ///
+ /// Values are in an invalid format for the type.
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ public static readonly EventId FormatWarning = MakeScaffoldingId(Id.FormatWarning);
}
diff --git a/src/EFCore.Sqlite.Core/Extensions/Internal/SqliteLoggerExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/Internal/SqliteLoggerExtensions.cs
index 2c6676ef4cc..99e3ad2f16a 100644
--- a/src/EFCore.Sqlite.Core/Extensions/Internal/SqliteLoggerExtensions.cs
+++ b/src/EFCore.Sqlite.Core/Extensions/Internal/SqliteLoggerExtensions.cs
@@ -413,4 +413,68 @@ private static string CompositeKeyWithValueGeneration(EventDefinitionBase defini
p.Key.DeclaringEntityType.DisplayName(),
p.Key.Properties.Format());
}
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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.
+ ///
+ public static void InferringTypes(
+ this IDiagnosticsLogger diagnostics,
+ string? tableName)
+ {
+ var definition = SqliteResources.LogInferringTypes(diagnostics);
+
+ if (diagnostics.ShouldLog(definition))
+ {
+ definition.Log(diagnostics, tableName);
+ }
+
+ // No DiagnosticsSource events because these are purely design-time messages
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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.
+ ///
+ public static void OutOfRangeWarning(
+ this IDiagnosticsLogger diagnostics,
+ string? columnName,
+ string? tableName,
+ string? type)
+ {
+ var definition = SqliteResources.LogOutOfRangeWarning(diagnostics);
+
+ if (diagnostics.ShouldLog(definition))
+ {
+ definition.Log(diagnostics, columnName, tableName, type);
+ }
+
+ // No DiagnosticsSource events because these are purely design-time messages
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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.
+ ///
+ public static void FormatWarning(
+ this IDiagnosticsLogger diagnostics,
+ string? columnName,
+ string? tableName,
+ string? type)
+ {
+ var definition = SqliteResources.LogFormatWarning(diagnostics);
+
+ if (diagnostics.ShouldLog(definition))
+ {
+ definition.Log(diagnostics, columnName, tableName, type);
+ }
+
+ // No DiagnosticsSource events because these are purely design-time messages
+ }
}
diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs
index 347b66fae15..8794b7b9b1b 100644
--- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs
+++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs
@@ -171,6 +171,31 @@ private static readonly ResourceManager _resourceManager
return (EventDefinition)definition;
}
+ ///
+ /// The column '{columnName}' on table '{tableName}' should map to a property of type '{type}', but its values are in an incompatible format. Using a different type.
+ ///
+ public static EventDefinition LogFormatWarning(IDiagnosticsLogger logger)
+ {
+ var definition = ((Diagnostics.Internal.SqliteLoggingDefinitions)logger.Definitions).LogFormatWarning;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((Diagnostics.Internal.SqliteLoggingDefinitions)logger.Definitions).LogFormatWarning,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ SqliteEventId.FormatWarning,
+ LogLevel.Warning,
+ "SqliteEventId.FormatWarning",
+ level => LoggerMessage.Define(
+ level,
+ SqliteEventId.FormatWarning,
+ _resourceManager.GetString("LogFormatWarning")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
///
/// Found column on table '{tableName}' with name: '{columnName}', data type: {dataType}, not nullable: {notNullable}, default value: {defaultValue}.
///
@@ -321,6 +346,31 @@ private static readonly ResourceManager _resourceManager
return (EventDefinition)definition;
}
+ ///
+ /// Querying table '{tableName}' to determine an appropriate CLR type for each column.
+ ///
+ public static EventDefinition LogInferringTypes(IDiagnosticsLogger logger)
+ {
+ var definition = ((Diagnostics.Internal.SqliteLoggingDefinitions)logger.Definitions).LogInferringTypes;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((Diagnostics.Internal.SqliteLoggingDefinitions)logger.Definitions).LogInferringTypes,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ SqliteEventId.InferringTypes,
+ LogLevel.Debug,
+ "SqliteEventId.InferringTypes",
+ level => LoggerMessage.Define(
+ level,
+ SqliteEventId.InferringTypes,
+ _resourceManager.GetString("LogInferringTypes")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
///
/// Unable to find a table in the database matching the selected table '{table}'.
///
@@ -346,6 +396,31 @@ private static readonly ResourceManager _resourceManager
return (EventDefinition)definition;
}
+ ///
+ /// The column '{columnName}' on table '{tableName}' should map to a property of type '{type}', but its values are out of range. Using a different type.
+ ///
+ public static EventDefinition LogOutOfRangeWarning(IDiagnosticsLogger logger)
+ {
+ var definition = ((Diagnostics.Internal.SqliteLoggingDefinitions)logger.Definitions).LogOutOfRangeWarning;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((Diagnostics.Internal.SqliteLoggingDefinitions)logger.Definitions).LogOutOfRangeWarning,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ SqliteEventId.OutOfRangeWarning,
+ LogLevel.Warning,
+ "SqliteEventId.OutOfRangeWarning",
+ level => LoggerMessage.Define(
+ level,
+ SqliteEventId.OutOfRangeWarning,
+ _resourceManager.GetString("LogOutOfRangeWarning")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
///
/// Skipping foreign key with identity '{id}' on table '{tableName}', since the principal column '{principalColumnName}' on the foreign key's principal table, '{principalTableName}', was not found in the model.
///
diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx
index 681c42f2ce3..aa3444f088b 100644
--- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx
+++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx
@@ -143,6 +143,10 @@
Skipping foreign key with identity '{id}' on table '{tableName}' since principal table '{principalTableName}' was not found in the model. This usually happens when the principal table was not included in the selection set.
Warning SqliteEventId.ForeignKeyReferencesMissingTableWarning string? string? string?
+
+ The column '{columnName}' on table '{tableName}' should map to a property of type '{type}', but its values are in an incompatible format. Using a different type.
+ Warning SqliteEventId.FormatWarning string? string? string?
+
Found column on table '{tableName}' with name: '{columnName}', data type: {dataType}, not nullable: {notNullable}, default value: {defaultValue}.
Debug SqliteEventId.ColumnFound string? string? string? bool string?
@@ -167,10 +171,18 @@
Found unique constraint on table '{tableName}' with name: {uniqueConstraintName}.
Debug SqliteEventId.UniqueConstraintFound string? string?
+
+ Querying table '{tableName}' to determine an appropriate CLR type for each column.
+ Debug SqliteEventId.InferringTypes string?
+
Unable to find a table in the database matching the selected table '{table}'.
Warning SqliteEventId.MissingTableWarning string?
+
+ The column '{columnName}' on table '{tableName}' should map to a property of type '{type}', but its values are out of range. Using a different type.
+ Warning SqliteEventId.OutOfRangeWarning string? string? string?
+
Skipping foreign key with identity '{id}' on table '{tableName}', since the principal column '{principalColumnName}' on the foreign key's principal table, '{principalTableName}', was not found in the model.
Warning SqliteEventId.ForeignKeyPrincipalColumnMissingWarning string? string? string? string?
diff --git a/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs b/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs
index fc7b2dee089..ededa3ceec6 100644
--- a/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs
+++ b/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteDatabaseModelFactory.cs
@@ -3,6 +3,7 @@
using System.Data;
using System.Text;
+using System.Text.RegularExpressions;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
using Microsoft.EntityFrameworkCore.Sqlite.Internal;
@@ -16,8 +17,116 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.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.
///
-public class SqliteDatabaseModelFactory : DatabaseModelFactory
+public partial class SqliteDatabaseModelFactory : DatabaseModelFactory
{
+ private static readonly HashSet _defaultClrTypes = new()
+ {
+ typeof(long),
+ typeof(string),
+ typeof(byte[]),
+ typeof(double)
+ };
+ private static readonly HashSet _boolTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "BIT",
+ "BOOL",
+ "BOOLEAN",
+ "LOGICAL",
+ "YESNO"
+ };
+ private static readonly HashSet _uintTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "MEDIUMUINT",
+ "UINT",
+ "UINT32",
+ "UNSIGNEDINTEGER32"
+ };
+ private static readonly HashSet _ulongTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "BIGUINT",
+ "UINT64",
+ "ULONG",
+ "UNSIGNEDINTEGER",
+ "UNSIGNEDINTEGER64"
+ };
+ private static readonly HashSet _byteTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "BYTE",
+ "TINYINT",
+ "UINT8",
+ "UNSIGNEDINTEGER8"
+ };
+ private static readonly HashSet _shortTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "INT16",
+ "INTEGER16",
+ "SHORT",
+ "SMALLINT"
+ };
+ private static readonly HashSet _longTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "BIGINT",
+ "INT64",
+ "INTEGER64",
+ "LONG"
+ };
+ private static readonly HashSet _sbyteTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "INT8",
+ "INTEGER8",
+ "SBYTE",
+ "TINYSINT"
+ };
+ private static readonly HashSet _floatTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "SINGLE"
+ };
+ private static readonly HashSet _ushortTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "SMALLUINT",
+ "UINT16",
+ "UNSIGNEDINTEGER16",
+ "USHORT"
+ };
+ private static readonly HashSet _timeOnlyTypes = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "TIMEONLY"
+ };
+ private static readonly Dictionary _typesByName = new Dictionary
+ {
+ { "CURRENCY", typeof(decimal) },
+ { "DATE", typeof(DateTime) },
+ { "DATEONLY", typeof(DateOnly) },
+ { "DATETIME", typeof(DateTime) },
+ { "DATETIME2", typeof(DateTime) },
+ { "DATETIMEOFFSET", typeof(DateTimeOffset) },
+ { "DECIMAL", typeof(decimal) },
+ { "GUID", typeof(Guid) },
+ { "JSON", typeof(string) },
+ { "MONEY", typeof(decimal) },
+ { "NUMBER", typeof(decimal) },
+ { "NUMERIC", typeof(decimal) },
+ { "SMALLDATE", typeof(DateTime) },
+ { "SMALLMONEY", typeof(decimal) },
+ { "STRING", typeof(string) },
+ { "TIME", typeof(TimeSpan) },
+ { "TIMESPAN", typeof(TimeSpan) },
+ { "TIMESTAMP", typeof(DateTime) },
+ { "UNIQUEIDENTIFIER", typeof(Guid) },
+ { "UUID", typeof(Guid) },
+ { "XML", typeof(string) }
+ }
+ .Concat(_boolTypes.Select(t => KeyValuePair.Create(t, typeof(bool))))
+ .Concat(_byteTypes.Select(t => KeyValuePair.Create(t, typeof(byte))))
+ .Concat(_shortTypes.Select(t => KeyValuePair.Create(t, typeof(short))))
+ .Concat(_sbyteTypes.Select(t => KeyValuePair.Create(t, typeof(sbyte))))
+ .Concat(_floatTypes.Select(t => KeyValuePair.Create(t, typeof(float))))
+ .Concat(_timeOnlyTypes.Select(t => KeyValuePair.Create(t, typeof(TimeOnly))))
+ .Concat(_ushortTypes.Select(t => KeyValuePair.Create(t, typeof(ushort))))
+ .Concat(_uintTypes.Select(t => KeyValuePair.Create(t, typeof(uint))))
+ .Concat(_ulongTypes.Select(t => KeyValuePair.Create(t, typeof(ulong))))
+ .ToDictionary(i => i.Key, i => i.Value, StringComparer.OrdinalIgnoreCase);
+
private readonly IDiagnosticsLogger _logger;
private readonly IRelationalTypeMappingSource _typeMappingSource;
@@ -271,6 +380,8 @@ ORDER BY "cid"
: collation
});
}
+
+ InferClrTypes(connection, table);
}
private string? FilterClrDefaults(string dataType, bool notNull, string defaultValue)
@@ -290,6 +401,344 @@ ORDER BY "cid"
return defaultValue;
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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.
+ ///
+ protected virtual void InferClrTypes(DbConnection connection, DatabaseTable table)
+ {
+ var command = connection.CreateCommand();
+ var commandText = new StringBuilder();
+ commandText.Append("SELECT");
+
+ var i = 0;
+ var dictionary = new Dictionary();
+ foreach (var column in table.Columns)
+ {
+ if (string.Equals(column.StoreType, "BLOB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(column.StoreType, "REAL", StringComparison.OrdinalIgnoreCase))
+ {
+ // Trust the column type (for perf)
+ continue;
+ }
+
+ var defaultClrType = _typeMappingSource.FindMapping(column.StoreType!)?.ClrType;
+ if (!_defaultClrTypes.Contains(defaultClrType))
+ {
+ // Handled by a plugin
+ continue;
+ }
+
+ if (i != 0)
+ {
+ commandText.Append(",");
+ }
+
+ var columnIdentifier = DelimitIdentifier(column.Name);
+ commandText
+ .Append(" typeof(max(")
+ .Append(columnIdentifier)
+ .Append(")), min(")
+ .Append(columnIdentifier)
+ .Append("), max(")
+ .Append(columnIdentifier)
+ .Append(")");
+
+ dictionary.Add(column, (i, defaultClrType));
+ i += 3;
+ }
+
+ if (dictionary.Count == 0)
+ {
+ return;
+ }
+
+ commandText
+ .Append(" FROM (SELECT * FROM ")
+ .Append(DelimitIdentifier(table.Name))
+ .Append(" LIMIT 131072)");
+
+ command.CommandText = commandText.ToString();
+
+ _logger.InferringTypes(table.Name);
+
+ using var reader = command.ExecuteReader();
+ var read = reader.Read();
+ Check.DebugAssert(read, "No results");
+
+ foreach (var (column, (offset, defaultClrTpe)) in dictionary)
+ {
+ var valueType = reader.GetString(offset + 0);
+
+ var index = column.StoreType!.IndexOf("(", StringComparison.OrdinalIgnoreCase);
+ var baseColumnType = index == -1
+ ? column.StoreType
+ : column.StoreType.Substring(0, index);
+
+ if (string.Equals(valueType, "INTEGER", StringComparison.OrdinalIgnoreCase))
+ {
+ var min = reader.GetInt64(offset + 1);
+ var max = reader.GetInt64(offset + 2);
+
+ if (_boolTypes.Contains(baseColumnType))
+ {
+ if (min >= 0L
+ && max <= 1L)
+ {
+ column["ClrType"] = typeof(bool);
+
+ continue;
+ }
+
+ _logger.OutOfRangeWarning(column.Name, table.Name, "bool");
+ }
+ if (_byteTypes.Contains(baseColumnType))
+ {
+ if (min >= byte.MinValue
+ && max <= byte.MaxValue)
+ {
+ column["ClrType"] = typeof(byte);
+
+ continue;
+ }
+
+ _logger.OutOfRangeWarning(column.Name, table.Name, "byte");
+ }
+ if (_shortTypes.Contains(baseColumnType))
+ {
+ if (min >= short.MinValue
+ && max <= short.MaxValue)
+ {
+ column["ClrType"] = typeof(short);
+
+ continue;
+ }
+
+ _logger.OutOfRangeWarning(column.Name, table.Name, "short");
+ }
+ if (_longTypes.Contains(baseColumnType))
+ {
+ if (defaultClrTpe != typeof(long))
+ {
+ column["ClrType"] = typeof(long);
+ }
+
+ continue;
+ }
+ if (_sbyteTypes.Contains(baseColumnType))
+ {
+ if (min >= sbyte.MinValue
+ && max <= sbyte.MaxValue)
+ {
+ column["ClrType"] = typeof(sbyte);
+
+ continue;
+ }
+
+ _logger.OutOfRangeWarning(column.Name, table.Name, "sbyte");
+ }
+ if (_ushortTypes.Contains(baseColumnType))
+ {
+ if (min >= ushort.MinValue
+ && max <= ushort.MaxValue)
+ {
+ column["ClrType"] = typeof(ushort);
+
+ continue;
+ }
+
+ _logger.OutOfRangeWarning(column.Name, table.Name, "ushort");
+ }
+ if (_uintTypes.Contains(baseColumnType))
+ {
+ if (min >= uint.MinValue
+ && max <= uint.MaxValue)
+ {
+ column["ClrType"] = typeof(uint);
+
+ continue;
+ }
+
+ _logger.OutOfRangeWarning(column.Name, table.Name, "uint");
+ }
+ if (_ulongTypes.Contains(baseColumnType))
+ {
+ column["ClrType"] = typeof(ulong);
+
+ continue;
+ }
+
+ if (min < int.MinValue
+ || max > int.MaxValue)
+ {
+ if (defaultClrTpe != typeof(long))
+ {
+ column["ClrType"] = typeof(long);
+ }
+
+ continue;
+ }
+
+ column["ClrType"] = typeof(int);
+
+ continue;
+ }
+ if (string.Equals(valueType, "TEXT", StringComparison.OrdinalIgnoreCase))
+ {
+ var min = reader.GetString(offset + 1);
+ var max = reader.GetString(offset + 2);
+
+ if (Regex.IsMatch(max, @"^\d{4}-\d{2}-\d{2}$", default, TimeSpan.FromMilliseconds(1000.0)))
+ {
+ column["ClrType"] = typeof(DateOnly);
+
+ continue;
+ }
+ if (Regex.IsMatch(max, @"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d{1,7})?$", default, TimeSpan.FromMilliseconds(1000.0)))
+ {
+ column["ClrType"] = typeof(DateTime);
+
+ continue;
+ }
+ if (Regex.IsMatch(max, @"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d{1,7})?[-+]\d{2}:\d{2}$", default, TimeSpan.FromMilliseconds(1000.0)))
+ {
+ column["ClrType"] = typeof(DateTimeOffset);
+
+ continue;
+ }
+ if (Regex.IsMatch(max, @"^-?\d+\.\d{1,28}$", default, TimeSpan.FromMilliseconds(1000.0)))
+ {
+ column["ClrType"] = typeof(decimal);
+
+ continue;
+ }
+ if (Regex.IsMatch(max, @"^(\d|[A-F]){8}-(\d|[A-F]){4}-(\d|[A-F]){4}-(\d|[A-F]){4}-(\d|[A-F]){12}$", default, TimeSpan.FromMilliseconds(1000.0)))
+ {
+ column["ClrType"] = typeof(Guid);
+
+ continue;
+ }
+ if (Regex.IsMatch(max, @"^-?(\d+\.)?\d{2}:\d{2}:\d{2}(\.\d{1,7})?$", default, TimeSpan.FromMilliseconds(1000.0)))
+ {
+ if (_timeOnlyTypes.Contains(baseColumnType))
+ {
+ if (TimeSpan.TryParse(min, out var minTimeSpan)
+ && TimeSpan.TryParse(max, out var maxTimeSpan)
+ && minTimeSpan >= TimeOnly.MinValue.ToTimeSpan()
+ && maxTimeSpan <= TimeOnly.MaxValue.ToTimeSpan())
+ {
+ column["ClrType"] = typeof(TimeOnly);
+
+ continue;
+ }
+
+ _logger.OutOfRangeWarning(column.Name, table.Name, "TimeOnly");
+ }
+
+ column["ClrType"] = typeof(TimeSpan);
+
+ continue;
+ }
+
+ if (DateOnly.TryParse(max, out _))
+ {
+ _logger.FormatWarning(column.Name, table.Name, "DateOnly");
+ }
+ else if (DateTime.TryParse(max, out _))
+ {
+ _logger.FormatWarning(column.Name, table.Name, "DateTime");
+ }
+ else if (DateTimeOffset.TryParse(max, out _))
+ {
+ _logger.FormatWarning(column.Name, table.Name, "DateTimeOffset");
+ }
+ else if (decimal.TryParse(max, out _))
+ {
+ _logger.FormatWarning(column.Name, table.Name, "decimal");
+ }
+ else if (Guid.TryParse(max, out _))
+ {
+ _logger.FormatWarning(column.Name, table.Name, "Guid");
+ }
+ else if (TimeSpan.TryParse(max, out _))
+ {
+ _logger.FormatWarning(
+ column.Name,
+ table.Name,
+ _timeOnlyTypes.Contains(baseColumnType)
+ ? "TimeOnly"
+ : "TimeSpan");
+ }
+
+ if (defaultClrTpe != typeof(string))
+ {
+ column["ClrType"] = typeof(string);
+ }
+
+ continue;
+ }
+ if (string.Equals(valueType, "BLOB", StringComparison.OrdinalIgnoreCase))
+ {
+ if (defaultClrTpe != typeof(byte[]))
+ {
+ column["ClrType"] = typeof(byte[]);
+ }
+
+ continue;
+ }
+ if (string.Equals(valueType, "REAL", StringComparison.OrdinalIgnoreCase))
+ {
+ var min = reader.GetDouble(offset + 1);
+ var max = reader.GetDouble(offset + 2);
+
+ if (_floatTypes.Contains(baseColumnType))
+ {
+ if (min >= float.MinValue
+ && max <= float.MaxValue)
+ {
+ column["ClrType"] = typeof(float);
+
+ continue;
+ }
+
+ _logger.OutOfRangeWarning(column.Name, table.Name, "float");
+ }
+
+ if (defaultClrTpe != typeof(double))
+ {
+ column["ClrType"] = typeof(double);
+ }
+
+ continue;
+ }
+
+ Check.DebugAssert(
+ string.Equals(valueType, "NULL", StringComparison.OrdinalIgnoreCase),
+ "Unexpected type: " + valueType);
+
+ if (_typesByName.TryGetValue(baseColumnType, out var type))
+ {
+ Check.DebugAssert(defaultClrTpe != type, "Unnecessary mapping for " + baseColumnType);
+
+ column["ClrType"] = type;
+
+ continue;
+ }
+ if (baseColumnType.Contains("INT", StringComparison.OrdinalIgnoreCase)
+ && !_longTypes.Contains(baseColumnType))
+ {
+ column["ClrType"] = typeof(int);
+
+ continue;
+ }
+ }
+
+ static string DelimitIdentifier(string name)
+ => @$"""{name.Replace(@"""", @"""""")}""";
+ }
+
private void GetPrimaryKey(DbConnection connection, DatabaseTable table)
{
using var command = connection.CreateCommand();
@@ -315,7 +764,8 @@ ORDER BY "seq"
var primaryKey = new DatabasePrimaryKey
{
- Table = table, Name = name.StartsWith("sqlite_", StringComparison.Ordinal) ? string.Empty : name
+ Table = table,
+ Name = name.StartsWith("sqlite_", StringComparison.Ordinal) ? string.Empty : name
};
_logger.PrimaryKeyFound(name, table.Name);
@@ -404,7 +854,8 @@ ORDER BY "seq"
var constraintName = reader1.GetString(0);
var uniqueConstraint = new DatabaseUniqueConstraint
{
- Table = table, Name = constraintName.StartsWith("sqlite_", StringComparison.Ordinal) ? string.Empty : constraintName
+ Table = table,
+ Name = constraintName.StartsWith("sqlite_", StringComparison.Ordinal) ? string.Empty : constraintName
};
_logger.UniqueConstraintFound(constraintName, table.Name);
diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
index 08b5cdc4b89..e3cebd332b1 100644
--- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
+++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
@@ -251,7 +251,6 @@ public static bool IsSpatialiteType(string columnType)
? Text
: null,
name => Contains(name, "BLOB")
- || Contains(name, "BIN")
? Blob
: null,
name => Contains(name, "REAL")
diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs
index b9ba7bce8cd..59b32fd3604 100644
--- a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs
+++ b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs
@@ -1978,6 +1978,13 @@ public void Correct_arguments_to_scaffolding_typemapper()
ValueGenerated = ValueGenerated.OnAddOrUpdate,
[ScaffoldingAnnotationNames.ConcurrencyToken] = true
};
+ var clrTypeColumn = new DatabaseColumn
+ {
+ Table = Table,
+ Name = "ClrType",
+ StoreType = "char(36)",
+ [ScaffoldingAnnotationNames.ClrType] = typeof(Guid)
+ };
var principalTable = new DatabaseTable
{
@@ -1988,7 +1995,8 @@ public void Correct_arguments_to_scaffolding_typemapper()
principalPkColumn,
principalAkColumn,
principalIndexColumn,
- rowversionColumn
+ rowversionColumn,
+ clrTypeColumn
},
PrimaryKey = new DatabasePrimaryKey
{
@@ -2070,6 +2078,7 @@ public void Correct_arguments_to_scaffolding_typemapper()
Assert.Null(model.FindEntityType("Principal").FindProperty("AlternateKey").GetConfiguredColumnType());
Assert.Null(model.FindEntityType("Principal").FindProperty("Index").GetConfiguredColumnType());
Assert.Null(model.FindEntityType("Principal").FindProperty("Rowversion").GetConfiguredColumnType());
+ Assert.Equal(typeof(Guid), model.FindEntityType("Principal").FindProperty("ClrType").ClrType);
Assert.Null(model.FindEntityType("Dependent").FindProperty("BlogAlternateKey").GetConfiguredColumnType());
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/SqliteDatabaseModelFactoryTest.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/SqliteDatabaseModelFactoryTest.cs
index c9c96b02a8a..39524df0b26 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/SqliteDatabaseModelFactoryTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/SqliteDatabaseModelFactoryTest.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
using Microsoft.EntityFrameworkCore.Sqlite.Design.Internal;
using Microsoft.EntityFrameworkCore.Sqlite.Diagnostics.Internal;
@@ -45,6 +46,7 @@ private void Test(
.AddSingleton();
new SqliteDesignTimeServices().ConfigureDesignTimeServices(services);
+ new SqliteNetTopologySuiteDesignTimeServices().ConfigureDesignTimeServices(services);
var databaseModelFactory = services
.BuildServiceProvider() // No scope validation; design services only resolved once
@@ -357,6 +359,221 @@ RandomProperty randomType
},
"DROP TABLE StoreType;");
+ [ConditionalTheory]
+ [InlineData("BIT", typeof(bool))]
+ [InlineData("BIT(1)", typeof(bool))]
+ [InlineData("BOOL", typeof(bool))]
+ [InlineData("BOOLEAN", typeof(bool))]
+ [InlineData("LOGICAL", typeof(bool))]
+ [InlineData("YESNO", typeof(bool))]
+ [InlineData("TINYINT", typeof(byte))]
+ [InlineData("UINT8", typeof(byte))]
+ [InlineData("UNSIGNEDINTEGER8", typeof(byte))]
+ [InlineData("BYTE", typeof(byte))]
+ [InlineData("SMALLINT", typeof(short))]
+ [InlineData("INT16", typeof(short))]
+ [InlineData("INTEGER16", typeof(short))]
+ [InlineData("SHORT", typeof(short))]
+ [InlineData("MEDIUMINT", typeof(int))]
+ [InlineData("INT", typeof(int))]
+ [InlineData("INT32", typeof(int))]
+ [InlineData("INTEGER", typeof(int))]
+ [InlineData("INTEGER32", typeof(int))]
+ [InlineData("BIGINT", null)]
+ [InlineData("INT64", null)]
+ [InlineData("INTEGER64", null)]
+ [InlineData("LONG", null)]
+ [InlineData("TINYSINT", typeof(sbyte))]
+ [InlineData("INT8", typeof(sbyte))]
+ [InlineData("INTEGER8", typeof(sbyte))]
+ [InlineData("SBYTE", typeof(sbyte))]
+ [InlineData("SMALLUINT", typeof(ushort))]
+ [InlineData("UINT16", typeof(ushort))]
+ [InlineData("UNSIGNEDINTEGER16", typeof(ushort))]
+ [InlineData("USHORT", typeof(ushort))]
+ [InlineData("MEDIUMUINT", typeof(uint))]
+ [InlineData("UINT", typeof(uint))]
+ [InlineData("UINT32", typeof(uint))]
+ [InlineData("UNSIGNEDINTEGER32", typeof(uint))]
+ [InlineData("BIGUINT", typeof(ulong))]
+ [InlineData("UINT64", typeof(ulong))]
+ [InlineData("UNSIGNEDINTEGER", typeof(ulong))]
+ [InlineData("UNSIGNEDINTEGER64", typeof(ulong))]
+ [InlineData("ULONG", typeof(ulong))]
+ [InlineData("REAL", null)]
+ [InlineData("DOUBLE", null)]
+ [InlineData("FLOAT", null)]
+ [InlineData("SINGLE", typeof(float))]
+ [InlineData("TEXT", null)]
+ [InlineData("NTEXT", null)]
+ [InlineData("CHAR(1)", null)]
+ [InlineData("NCHAR(1)", null)]
+ [InlineData("VARCHAR(1)", null)]
+ [InlineData("VARCHAR2(1)", null)]
+ [InlineData("NVARCHAR(1)", null)]
+ [InlineData("CLOB", null)]
+ [InlineData("STRING", typeof(string))]
+ [InlineData("JSON", typeof(string))]
+ [InlineData("XML", typeof(string))]
+ [InlineData("DATEONLY", typeof(DateOnly))]
+ [InlineData("DATE", typeof(DateTime))]
+ [InlineData("DATETIME", typeof(DateTime))]
+ [InlineData("DATETIME2", typeof(DateTime))]
+ [InlineData("SMALLDATE", typeof(DateTime))]
+ [InlineData("TIMESTAMP(7)", typeof(DateTime))]
+ [InlineData("DATETIMEOFFSET", typeof(DateTimeOffset))]
+ [InlineData("CURRENCY", typeof(decimal))]
+ [InlineData("DECIMAL(18, 0)", typeof(decimal))]
+ [InlineData("MONEY", typeof(decimal))]
+ [InlineData("SMALLMONEY", typeof(decimal))]
+ [InlineData("NUMBER(18, 0)", typeof(decimal))]
+ [InlineData("NUMERIC(18, 0)", typeof(decimal))]
+ [InlineData("GUID", typeof(Guid))]
+ [InlineData("UNIQUEIDENTIFIER", typeof(Guid))]
+ [InlineData("UUID", typeof(Guid))]
+ [InlineData("TIMEONLY", typeof(TimeOnly))]
+ [InlineData("TIME(7)", typeof(TimeSpan))]
+ [InlineData("TIMESPAN", typeof(TimeSpan))]
+ [InlineData("BLOB", null)]
+ [InlineData("BINARY(10)", null)]
+ [InlineData("VARBINARY(10)", null)]
+ [InlineData("IMAGE", null)]
+ [InlineData("RAW(10)", null)]
+ [InlineData("GEOMETRY", null)]
+ [InlineData("GEOMETRYZ", null)]
+ [InlineData("GEOMETRYM", null)]
+ [InlineData("GEOMETRYZM", null)]
+ [InlineData("GEOMETRYCOLLECTION", null)]
+ [InlineData("GEOMETRYCOLLECTIONZ", null)]
+ [InlineData("GEOMETRYCOLLECTIONM", null)]
+ [InlineData("GEOMETRYCOLLECTIONZM", null)]
+ [InlineData("LINESTRING", null)]
+ [InlineData("LINESTRINGZ", null)]
+ [InlineData("LINESTRINGM", null)]
+ [InlineData("LINESTRINGZM", null)]
+ [InlineData("MULTILINESTRING", null)]
+ [InlineData("MULTILINESTRINGZ", null)]
+ [InlineData("MULTILINESTRINGM", null)]
+ [InlineData("MULTILINESTRINGZM", null)]
+ [InlineData("MULTIPOINT", null)]
+ [InlineData("MULTIPOINTZ", null)]
+ [InlineData("MULTIPOINTM", null)]
+ [InlineData("MULTIPOINTZM", null)]
+ [InlineData("MULTIPOLYGON", null)]
+ [InlineData("MULTIPOLYGONZ", null)]
+ [InlineData("MULTIPOLYGONM", null)]
+ [InlineData("MULTIPOLYGONZM", null)]
+ [InlineData("POINT", null)]
+ [InlineData("POINTZ", null)]
+ [InlineData("POINTM", null)]
+ [InlineData("POINTZM", null)]
+ [InlineData("POLYGON", null)]
+ [InlineData("POLYGONZ", null)]
+ [InlineData("POLYGONM", null)]
+ [InlineData("POLYGONZM", null)]
+ public void Column_ClrType_is_set_when_no_data(string storeType, Type expected)
+ => Test(
+ $@"
+CREATE TABLE ClrType (
+ EmptyColumn {storeType}
+);",
+ Enumerable.Empty(),
+ Enumerable.Empty(),
+ model =>
+ {
+ var table = Assert.Single(model.Tables);
+ var column = Assert.Single(table.Columns);
+ Assert.Equal(expected, (Type)column[ScaffoldingAnnotationNames.ClrType]);
+ },
+ "DROP TABLE ClrType");
+
+ [ConditionalTheory]
+ [InlineData("INTEGER", "1", typeof(int))]
+ [InlineData("INTEGER", "2147483648", null)]
+ [InlineData("BIT", "1", typeof(bool))]
+ [InlineData("TINYINT", "1", typeof(byte))]
+ [InlineData("SMALLINT", "1", typeof(short))]
+ [InlineData("BIGINT", "1", null)]
+ [InlineData("INT8", "1", typeof(sbyte))]
+ [InlineData("UINT16", "1", typeof(ushort))]
+ [InlineData("UINT", "1", typeof(uint))]
+ [InlineData("UINT64", "1", typeof(ulong))]
+ [InlineData("UINT64", "-1", typeof(ulong))]
+ [InlineData("REAL", "0.1", null)]
+ [InlineData("SINGLE", "0.1", typeof(float))]
+ [InlineData("TEXT", "'A'", null)]
+ [InlineData("TEXT", "'2023-01-20'", typeof(DateOnly))]
+ [InlineData("TEXT", "'2023-01-20 13:37:00'", typeof(DateTime))]
+ [InlineData("TEXT", "'2023-01-20 13:42:00-08:00'", typeof(DateTimeOffset))]
+ [InlineData("TEXT", "'0.1'", typeof(decimal))]
+ [InlineData("TEXT", "'00000000-0000-0000-0000-000000000000'", typeof(Guid))]
+ [InlineData("TEXT", "'13:44:00'", typeof(TimeSpan))]
+ [InlineData("TIMEONLY", "'14:34:00'", typeof(TimeOnly))]
+ [InlineData("BLOB", "x'01'", null)]
+ [InlineData("GEOMETRY", "x'00010000000000000000000000000000000000000000000000000000000000000000000000007C0100000000000000000000000000000000000000FE'", null)]
+ [InlineData("POINT", "x'00010000000000000000000000000000000000000000000000000000000000000000000000007C0100000000000000000000000000000000000000FE'", null)]
+ public void Column_ClrType_is_set_when_data(string storeType, string value, Type expected)
+ => Test(
+ $@"
+CREATE TABLE IF NOT EXISTS ClrTypeWithData (
+ ColumnWithData {storeType}
+);
+
+INSERT INTO ClrTypeWithData VALUES ({value});",
+ Enumerable.Empty(),
+ Enumerable.Empty(),
+ model =>
+ {
+ var table = Assert.Single(model.Tables);
+ var column = Assert.Single(table.Columns);
+ Assert.Equal(expected, (Type)column[ScaffoldingAnnotationNames.ClrType]);
+ },
+ "DROP TABLE ClrTypeWithData");
+
+ [ConditionalTheory]
+ [InlineData("INTEGER", "0.1", typeof(double))]
+ [InlineData("BIT", "2", typeof(int))]
+ [InlineData("TINYINT", "-1", typeof(int))]
+ [InlineData("TINYINT", "256", typeof(int))]
+ [InlineData("SMALLINT", "32768", typeof(int))]
+ [InlineData("MEDIUMINT", "2147483648", null)]
+ [InlineData("INT8", "128", typeof(int))]
+ [InlineData("UINT16", "-1", typeof(int))]
+ [InlineData("UINT16", "65536", typeof(int))]
+ [InlineData("UINT", "4294967296", null)]
+ [InlineData("REAL", "'A'", null)]
+ [InlineData("SINGLE", "3.402824E+38", typeof(double))]
+ [InlineData("TEXT", "x'00'", typeof(byte[]))]
+ [InlineData("DATE", "'A'", typeof(string))]
+ [InlineData("DATEONLY", "'A'", typeof(string))]
+ [InlineData("DATETIME", "'A'", typeof(string))]
+ [InlineData("DATETIMEOFFSET", "'A'", typeof(string))]
+ [InlineData("DECIMAL", "'A'", typeof(string))]
+ [InlineData("GUID", "'A'", typeof(string))]
+ [InlineData("TIME", "'A'", typeof(string))]
+ [InlineData("TIMEONLY", "'A'", typeof(string))]
+ [InlineData("TIMEONLY", "'24:00:00'", typeof(TimeSpan))]
+ [InlineData("BLOB", "1", null)]
+ [InlineData("GEOMETRY", "1", null)]
+ [InlineData("POINT", "1", null)]
+ public void Column_ClrType_is_set_when_insane(string storeType, string value, Type expected)
+ => Test(
+ $@"
+CREATE TABLE IF NOT EXISTS ClrTypeWithData (
+ ColumnWithData {storeType}
+);
+
+INSERT INTO ClrTypeWithData VALUES ({value});",
+ Enumerable.Empty(),
+ Enumerable.Empty(),
+ model =>
+ {
+ var table = Assert.Single(model.Tables);
+ var column = Assert.Single(table.Columns);
+ Assert.Equal(expected, (Type)column[ScaffoldingAnnotationNames.ClrType]);
+ },
+ "DROP TABLE ClrTypeWithData");
+
[ConditionalFact]
public void Column_nullability_is_set()
=> Test(