diff --git a/src/EFCore.Jet/Diagnostics/Internal/JetLoggingDefinitions.cs b/src/EFCore.Jet/Diagnostics/Internal/JetLoggingDefinitions.cs
index 6af230d..e0ac7fe 100644
--- a/src/EFCore.Jet/Diagnostics/Internal/JetLoggingDefinitions.cs
+++ b/src/EFCore.Jet/Diagnostics/Internal/JetLoggingDefinitions.cs
@@ -116,6 +116,14 @@ namespace EntityFrameworkCore.Jet.Diagnostics.Internal
///
public EventDefinitionBase LogFoundIndex;
+ ///
+ /// 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 LogSkippedIndex;
+
///
/// 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.Jet/Diagnostics/JetEventId.cs b/src/EFCore.Jet/Diagnostics/JetEventId.cs
index 4d74275..10c939e 100644
--- a/src/EFCore.Jet/Diagnostics/JetEventId.cs
+++ b/src/EFCore.Jet/Diagnostics/JetEventId.cs
@@ -62,7 +62,8 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics
IndexFound,
ForeignKeyFound,
ForeignKeyPrincipalColumnMissingWarning,
- ReflexiveConstraintIgnored
+ ReflexiveConstraintIgnored,
+ IndexSkipped,
}
private static readonly string _validationPrefix = DbLoggerCategory.Model.Validation.Name + ".";
@@ -212,6 +213,12 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics
///
public static readonly EventId IndexFound = MakeScaffoldingId(Id.IndexFound);
+ ///
+ /// An index was skipped.
+ /// This event is in the category.
+ ///
+ public static readonly EventId IndexSkipped = MakeScaffoldingId(Id.IndexSkipped);
+
///
/// A foreign key was found.
/// This event is in the category.
diff --git a/src/EFCore.Jet/Internal/JetLoggerExtensions.cs b/src/EFCore.Jet/Internal/JetLoggerExtensions.cs
index 120d277..8bc598b 100644
--- a/src/EFCore.Jet/Internal/JetLoggerExtensions.cs
+++ b/src/EFCore.Jet/Internal/JetLoggerExtensions.cs
@@ -307,6 +307,28 @@ namespace EntityFrameworkCore.Jet.Internal
// 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 IndexSkipped(
+ [NotNull] this IDiagnosticsLogger diagnostics,
+ [NotNull] string indexName,
+ [NotNull] string tableName,
+ bool unique)
+ {
+ var definition = JetResources.LogFoundIndex(diagnostics);
+
+ if (diagnostics.ShouldLog(definition))
+ {
+ definition.Log(diagnostics, indexName, tableName, unique);
+ }
+
+ // 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.Jet/Properties/JetStrings.Designer.cs b/src/EFCore.Jet/Properties/JetStrings.Designer.cs
index 128284b..60aab8c 100644
--- a/src/EFCore.Jet/Properties/JetStrings.Designer.cs
+++ b/src/EFCore.Jet/Properties/JetStrings.Designer.cs
@@ -592,7 +592,30 @@ namespace EntityFrameworkCore.Jet.Internal
return (EventDefinition)definition;
}
+
+ ///
+ /// Skipped index with name: {indexName}, table: {tableName}, is unique: {isUnique}.
+ ///
+ public static EventDefinition LogSkippedIndex([NotNull] IDiagnosticsLogger logger)
+ {
+ var definition = ((Diagnostics.Internal.JetLoggingDefinitions)logger.Definitions).LogSkippedIndex;
+ if (definition == null)
+ {
+ definition = LazyInitializer.EnsureInitialized(
+ ref ((Diagnostics.Internal.JetLoggingDefinitions)logger.Definitions).LogSkippedIndex,
+ () => new EventDefinition(
+ logger.Options,
+ JetEventId.IndexSkipped,
+ LogLevel.Debug,
+ "JetEventId.SkippedIndex",
+ level => LoggerMessage.Define(
+ level,
+ JetEventId.IndexFound,
+ _resourceManager.GetString("LogSkippedIndex"))));
+ }
+ return (EventDefinition)definition;
+ }
///
/// Found primary key with name: {primaryKeyName}, table: {tableName}.
///
diff --git a/src/EFCore.Jet/Properties/JetStrings.resx b/src/EFCore.Jet/Properties/JetStrings.resx
index 546f9d1..2635ebc 100644
--- a/src/EFCore.Jet/Properties/JetStrings.resx
+++ b/src/EFCore.Jet/Properties/JetStrings.resx
@@ -207,6 +207,10 @@
Found index with name: {indexName}, table: {tableName}, is unique: {isUnique}.
Debug JetEventId.IndexFound string string bool
+
+ Skipped index with name: {indexName}, table: {tableName}, is unique: {isUnique} as the name is equal to an existing foreign key and would result in a runtime error.
+ Debug JetEventId.IndexSkipped string string bool
+
Found primary key with name: {primaryKeyName}, table: {tableName}.
Debug JetEventId.PrimaryKeyFound string string
diff --git a/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs b/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
index 0b07bec..2c424c8 100644
--- a/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
+++ b/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
@@ -168,8 +168,8 @@ namespace EntityFrameworkCore.Jet.Scaffolding.Internal
if (tables.Count > 0)
{
GetColumns(connection, tables);
- GetIndexes(connection, tables);
GetRelations(connection, tables);
+ GetIndexes(connection, tables);
}
return tables;
@@ -372,35 +372,52 @@ namespace EntityFrameworkCore.Jet.Scaffolding.Internal
table.PrimaryKey = primaryKey;
indexOrKey = primaryKey;
}
- else if (indexType == "UNIQUE" &&
- !nullable)
+ else
{
- var uniqueConstraint = new DatabaseUniqueConstraint
+ var isUnique = indexType == "UNIQUE";
+
+ if (isUnique &&
+ !nullable)
{
- Table = table,
- Name = indexName,
- };
+ var uniqueConstraint = new DatabaseUniqueConstraint
+ {
+ Table = table,
+ Name = indexName,
+ };
- _logger.UniqueConstraintFound(indexName, tableName);
+ _logger.UniqueConstraintFound(indexName, tableName);
- table.UniqueConstraints.Add(uniqueConstraint);
- indexOrKey = uniqueConstraint;
- }
- else
- {
- var index = new DatabaseIndex
+ table.UniqueConstraints.Add(uniqueConstraint);
+ indexOrKey = uniqueConstraint;
+ }
+ else
{
- Table = table,
- Name = indexName,
- IsUnique = indexType == "UNIQUE",
- };
+ // In contrast to SQL Standard, MS Access will implicitly create an index for every FK
+ // constraint.
+ // According to https://docs.microsoft.com/en-us/office/client-developer/access/desktop-database-reference/constraint-clause-microsoft-access-sql,
+ // this behavior can be disabled, but manuall creating an index with the same name as an
+ // FK would still results in a runtime error.
+ // We therefore skip indexes with the same name as existing FKs.
+ if (table.ForeignKeys.Any(fk => fk.Name == indexName))
+ {
+ _logger.IndexSkipped(indexName, tableName, isUnique);
+ continue;
+ }
+
+ var index = new DatabaseIndex
+ {
+ Table = table,
+ Name = indexName,
+ IsUnique = isUnique,
+ };
- _logger.IndexFound(indexName, tableName, index.IsUnique);
+ _logger.IndexFound(indexName, tableName, index.IsUnique);
- table.Indexes.Add(index);
- indexOrKey = index;
+ table.Indexes.Add(index);
+ indexOrKey = index;
+ }
}
-
+
foreach (var indexColumn in indexColumns)
{
var columnName = indexColumn.GetValueOrDefault("COLUMN_NAME");
diff --git a/test/EFCore.Jet.FunctionalTests/Scaffolding/JetDatabaseModelFactoryTest.cs b/test/EFCore.Jet.FunctionalTests/Scaffolding/JetDatabaseModelFactoryTest.cs
index 1a7805b..02fa1da 100644
--- a/test/EFCore.Jet.FunctionalTests/Scaffolding/JetDatabaseModelFactoryTest.cs
+++ b/test/EFCore.Jet.FunctionalTests/Scaffolding/JetDatabaseModelFactoryTest.cs
@@ -1475,7 +1475,46 @@ CREATE TABLE DependentTable (
DROP TABLE DependentTable;
DROP TABLE PrincipalTable;");
}
+
+ ///
+ /// In contrast to SQL Standard, MS Access will implicitly create an index for every FK constraint.
+ /// According to https://docs.microsoft.com/en-us/office/client-developer/access/desktop-database-reference/constraint-clause-microsoft-access-sql,
+ /// this behavior can be disabled, but manuall creating an index with the same name as an FK would still results
+ /// in a runtime error.
+ /// We therefore skip indexes with the same name as existing FKs.
+ ///
+ [ConditionalFact]
+ public void EnsureNoForeignKeyIndexCreationOperationsAreCreated()
+ {
+ Test(
+ @"
+CREATE TABLE PrincipalTable (
+ Id int PRIMARY KEY
+);
+CREATE TABLE DependentTable (
+ Id int PRIMARY KEY,
+ IndexColumn int,
+ ForeignKeyId int,
+ CONSTRAINT MYFK FOREIGN KEY (ForeignKeyId) REFERENCES PrincipalTable(Id) ON DELETE CASCADE
+);
+ CREATE INDEX `IX_DependentTable_IndexColumn` ON `DependentTable` (`IndexColumn`);
+",
+ Enumerable.Empty(),
+ Enumerable.Empty(),
+ dbModel =>
+ {
+ var table = dbModel.Tables.Single(t => t.Name == "DependentTable");
+
+ Assert.Equal(1, table.ForeignKeys.Count);
+ Assert.Equal(1, table.Indexes.Count);
+ Assert.False(table.Indexes.Any(i => i.Name == "MYFK"));
+ },
+ @"
+DROP TABLE DependentTable;
+DROP TABLE PrincipalTable;");
+ }
+
[ConditionalFact]
public void Set_referential_action_for_foreign_key()
{