diff --git a/src/EFCore.SqlServer/Diagnostics/Internal/SqlServerLoggingDefinitions.cs b/src/EFCore.SqlServer/Diagnostics/Internal/SqlServerLoggingDefinitions.cs index a81a6df2ebd..b4d8f1d594a 100644 --- a/src/EFCore.SqlServer/Diagnostics/Internal/SqlServerLoggingDefinitions.cs +++ b/src/EFCore.SqlServer/Diagnostics/Internal/SqlServerLoggingDefinitions.cs @@ -155,6 +155,14 @@ public class SqlServerLoggingDefinitions : RelationalLoggingDefinitions /// public EventDefinitionBase? LogReflexiveConstraintIgnored; + /// + /// 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? LogDataverseForeignKeyInvalid; + /// /// 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.SqlServer/Diagnostics/SqlServerEventId.cs b/src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs index 5940679dcf3..599c93a60a4 100644 --- a/src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs +++ b/src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs @@ -69,6 +69,7 @@ private enum Id ColumnWithoutTypeWarning, ForeignKeyReferencesUnknownPrincipalTableWarning, MissingViewDefinitionRightsWarning, + DataverseForeignKeyInvalidWarning, } private static readonly string ValidationPrefix = DbLoggerCategory.Model.Validation.Name + "."; @@ -286,6 +287,14 @@ private static EventId MakeScaffoldingId(Id id) /// public static readonly EventId ReflexiveConstraintIgnored = MakeScaffoldingId(Id.ReflexiveConstraintIgnored); + /// + /// An invalid foreign key constraint was skipped. + /// + /// + /// This event is in the category. + /// + public static readonly EventId DataverseForeignKeyInvalidWarning = MakeScaffoldingId(Id.DataverseForeignKeyInvalidWarning); + /// /// A duplicate foreign key constraint was skipped. /// diff --git a/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs b/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs index bfa21d25d66..54e7f79c7aa 100644 --- a/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs @@ -577,6 +577,27 @@ public static void ReflexiveConstraintIgnored( // 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 DataverseForeignKeyInvalidWarning( + this IDiagnosticsLogger diagnostics, + string foreignKeyName, + string tableName) + { + var definition = SqlServerResources.LogDataverseForeignKeyInvalidWarning(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log(diagnostics, foreignKeyName, 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 diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs index 00b27a4de1a..03681e04002 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs @@ -1018,6 +1018,31 @@ public static EventDefinition LogReflexiveConstraintIgnored(IDia return (EventDefinition)definition; } + /// + /// Skipping foreign key '{foreignKeyName}' on table '{tableName}' since it is not supported by the Dataverse TDS Endpoint + /// + public static EventDefinition LogDataverseForeignKeyInvalidWarning(IDiagnosticsLogger logger) + { + var definition = ((Diagnostics.Internal.SqlServerLoggingDefinitions)logger.Definitions).LogDataverseForeignKeyInvalid; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((Diagnostics.Internal.SqlServerLoggingDefinitions)logger.Definitions).LogDataverseForeignKeyInvalid, + logger, + static logger => new EventDefinition( + logger.Options, + SqlServerEventId.DataverseForeignKeyInvalidWarning, + LogLevel.Debug, + "SqlServerEventId.LogDataverseForeignKeyInvalidWarning", + level => LoggerMessage.Define( + level, + SqlServerEventId.DataverseForeignKeyInvalidWarning, + _resourceManager.GetString("LogDataverseForeignKeyInvalidWarning")!))); + } + + return (EventDefinition)definition; + } + /// /// Savepoints are disabled because Multiple Active Result Sets (MARS) is enabled. If 'SaveChanges' fails, then the transaction cannot be automatically rolled back to a known clean state. Instead, the transaction should be rolled back by the application before retrying 'SaveChanges'. See https://go.microsoft.com/fwlink/?linkid=2149338 for more information and examples. To identify the code which triggers this warning, call 'ConfigureWarnings(w => w.Throw(SqlServerEventId.SavepointsDisabledBecauseOfMARS))'. /// diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx index 2f8726af548..4e1bc2ebcb9 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx @@ -296,6 +296,10 @@ Skipping foreign key '{foreignKeyName}' on table '{tableName}' since all of its columns reference themselves. Debug SqlServerEventId.ReflexiveConstraintIgnored string string + + Skipping foreign key '{foreignKeyName}' on table '{tableName}' since it is not supported by the Dataverse TDS Endpoint + Debug SqlServerEventId.DataverseForeignKeyInvalidWarning string string + Savepoints are disabled because Multiple Active Result Sets (MARS) is enabled. If 'SaveChanges' fails, then the transaction cannot be automatically rolled back to a known clean state. Instead, the transaction should be rolled back by the application before retrying 'SaveChanges'. See https://go.microsoft.com/fwlink/?linkid=2149338 for more information and examples. To identify the code which triggers this warning, call 'ConfigureWarnings(w => w.Throw(SqlServerEventId.SavepointsDisabledBecauseOfMARS))'. Warning SqlServerEventId.SavepointsDisabledBecauseOfMARS diff --git a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs index 2cd011ba677..c60c517c7b2 100644 --- a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs +++ b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs @@ -682,10 +682,7 @@ FROM [sys].[views] AS [v] // This is done separately due to MARS property may be turned off GetColumns(connection, tables, tableFilterSql, viewFilter, typeAliases, databaseCollation); - if (SupportsIndexes()) - { - GetIndexes(connection, tables, tableFilterSql); - } + GetIndexes(connection, tables, tableFilterSql); GetForeignKeys(connection, tables, tableFilterSql); @@ -1028,7 +1025,7 @@ private void GetIndexes(DbConnection connection, IReadOnlyList ta [i].[has_filter], [i].[filter_definition], [i].[fill_factor], - COL_NAME([ic].[object_id], [ic].[column_id]) AS [column_name], + [c].[name] AS [column_name], [ic].[is_descending_key], [ic].[is_included_column] FROM [sys].[indexes] AS [i] @@ -1089,7 +1086,7 @@ FROM [sys].[indexes] i if (primaryKeyGroups.Length == 1) { - if (TryGetPrimaryKey(primaryKeyGroups[0], out var primaryKey)) + if (TryGetPrimaryKey(primaryKeyGroups[0], out var primaryKey) && IsValidPrimaryKey(primaryKey)) { _logger.PrimaryKeyFound(primaryKey.Name!, DisplayName(tableSchema, tableName)); table.PrimaryKey = primaryKey; @@ -1137,6 +1134,16 @@ FROM [sys].[indexes] i } } + bool IsValidPrimaryKey(DatabasePrimaryKey primaryKey) + { + if (_engineEdition != EngineEdition.DynamicsTdsEndpoint) + { + return true; + } + + return primaryKey.Columns.Count == 1 && primaryKey.Columns[0].StoreType == "uniqueidentifier"; + } + bool TryGetPrimaryKey( IGrouping<(string Name, string? TypeDesc, byte FillFactor), DbDataRecord> primaryKeyGroup, [NotNullWhen(true)] out DatabasePrimaryKey? primaryKey) @@ -1377,6 +1384,14 @@ FROM [sys].[foreign_keys] AS [f] foreignKey.PrincipalColumns.Add(principalColumn); } + if (!invalid && _engineEdition == EngineEdition.DynamicsTdsEndpoint && !IsValidDataverseForeignKey(foreignKey)) + { + invalid = true; + _logger.DataverseForeignKeyInvalidWarning( + fkName!, + DisplayName(table.Schema, table.Name)); + } + if (!invalid) { if (foreignKey.Columns.SequenceEqual(foreignKey.PrincipalColumns)) @@ -1406,6 +1421,36 @@ FROM [sys].[foreign_keys] AS [f] } } } + + bool IsValidDataverseForeignKey(DatabaseForeignKey foreignKey) + { + if (foreignKey.Columns.Count != 1) + { + return false; + } + + if (foreignKey.Columns[0].StoreType != "uniqueidentifier") + { + return false; + } + + if (foreignKey.PrincipalTable.PrimaryKey == null) + { + return false; + } + + if (foreignKey.PrincipalTable.PrimaryKey.Columns.Count != 1) + { + return false; + } + + if (foreignKey.PrincipalTable.PrimaryKey.Columns[0].Name != foreignKey.PrincipalColumns[0].Name) + { + return false; + } + + return true; + } } private void GetTriggers(DbConnection connection, IReadOnlyList tables, string tableFilter) @@ -1456,9 +1501,6 @@ private bool SupportsMemoryOptimizedTable() private bool SupportsSequences() => _compatibilityLevel >= 110 && IsFullFeaturedEngineEdition(); - private bool SupportsIndexes() - => _engineEdition != EngineEdition.DynamicsTdsEndpoint; - private bool SupportsViews() => _engineEdition != EngineEdition.DynamicsTdsEndpoint;