Skip to content

Commit

Permalink
Allow using foreign keys in Dataverse reverse engineering
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMpn committed Sep 16, 2024
1 parent 65a4588 commit 5a14601
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ public class SqlServerLoggingDefinitions : RelationalLoggingDefinitions
/// </summary>
public EventDefinitionBase? LogReflexiveConstraintIgnored;

/// <summary>
/// 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.
/// </summary>
public EventDefinitionBase? LogDataverseForeignKeyInvalid;

/// <summary>
/// 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
Expand Down
9 changes: 9 additions & 0 deletions src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ private enum Id
ColumnWithoutTypeWarning,
ForeignKeyReferencesUnknownPrincipalTableWarning,
MissingViewDefinitionRightsWarning,
DataverseForeignKeyInvalidWarning,
}

private static readonly string ValidationPrefix = DbLoggerCategory.Model.Validation.Name + ".";
Expand Down Expand Up @@ -286,6 +287,14 @@ private static EventId MakeScaffoldingId(Id id)
/// </remarks>
public static readonly EventId ReflexiveConstraintIgnored = MakeScaffoldingId(Id.ReflexiveConstraintIgnored);

/// <summary>
/// An invalid foreign key constraint was skipped.
/// </summary>
/// <remarks>
/// This event is in the <see cref="DbLoggerCategory.Scaffolding"/> category.
/// </remarks>
public static readonly EventId DataverseForeignKeyInvalidWarning = MakeScaffoldingId(Id.DataverseForeignKeyInvalidWarning);

/// <summary>
/// A duplicate foreign key constraint was skipped.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,27 @@ public static void ReflexiveConstraintIgnored(
// No DiagnosticsSource events because these are purely design-time messages
}

/// <summary>
/// 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.
/// </summary>
public static void DataverseForeignKeyInvalidWarning(
this IDiagnosticsLogger<DbLoggerCategory.Scaffolding> 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
}

/// <summary>
/// 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
Expand Down
25 changes: 25 additions & 0 deletions src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/EFCore.SqlServer/Properties/SqlServerStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@
<value>Skipping foreign key '{foreignKeyName}' on table '{tableName}' since all of its columns reference themselves.</value>
<comment>Debug SqlServerEventId.ReflexiveConstraintIgnored string string</comment>
</data>
<data name="LogDataverseForeignKeyInvalidWarning" xml:space="preserve">
<value>Skipping foreign key '{foreignKeyName}' on table '{tableName}' since it is not supported by the Dataverse TDS Endpoint</value>
<comment>Debug SqlServerEventId.DataverseForeignKeyInvalidWarning string string</comment>
</data>
<data name="LogSavepointsDisabledBecauseOfMARS" xml:space="preserve">
<value>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 =&gt; w.Throw(SqlServerEventId.SavepointsDisabledBecauseOfMARS))'.</value>
<comment>Warning SqlServerEventId.SavepointsDisabledBecauseOfMARS</comment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -1028,7 +1025,7 @@ private void GetIndexes(DbConnection connection, IReadOnlyList<DatabaseTable> 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]
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1137,6 +1134,14 @@ 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)
Expand Down Expand Up @@ -1377,6 +1382,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))
Expand Down Expand Up @@ -1406,6 +1419,26 @@ 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<DatabaseTable> tables, string tableFilter)
Expand Down Expand Up @@ -1456,9 +1489,6 @@ private bool SupportsMemoryOptimizedTable()
private bool SupportsSequences()
=> _compatibilityLevel >= 110 && IsFullFeaturedEngineEdition();

private bool SupportsIndexes()
=> _engineEdition != EngineEdition.DynamicsTdsEndpoint;

private bool SupportsViews()
=> _engineEdition != EngineEdition.DynamicsTdsEndpoint;

Expand Down

0 comments on commit 5a14601

Please sign in to comment.