Skip to content

Commit

Permalink
Validate shared views
Browse files Browse the repository at this point in the history
Fixes #20950
  • Loading branch information
AndriySvyryd committed Jul 21, 2020
1 parent 13e69ca commit ab2ea8f
Show file tree
Hide file tree
Showing 19 changed files with 462 additions and 346 deletions.
88 changes: 32 additions & 56 deletions src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

// ReSharper disable once CheckNamespace
Expand All @@ -25,29 +24,23 @@ public static class RelationalForeignKeyExtensions
/// <returns> The foreign key constraint name. </returns>
public static string GetConstraintName([NotNull] this IForeignKey foreignKey)
=> foreignKey.GetConstraintName(
foreignKey.DeclaringEntityType.GetTableName(), foreignKey.DeclaringEntityType.GetSchema(),
foreignKey.PrincipalEntityType.GetTableName(), foreignKey.PrincipalEntityType.GetSchema());
StoreObjectIdentifier.Table(foreignKey.DeclaringEntityType.GetTableName(), foreignKey.DeclaringEntityType.GetSchema()),
StoreObjectIdentifier.Table(foreignKey.PrincipalEntityType.GetTableName(), foreignKey.PrincipalEntityType.GetSchema()));

/// <summary>
/// Returns the foreign key constraint name.
/// </summary>
/// <param name="foreignKey"> The foreign key. </param>
/// <param name="tableName"> The table name. </param>
/// <param name="schema"> The schema. </param>
/// <param name="principalTableName"> The principal table name. </param>
/// <param name="principalSchema"> The principal schema. </param>
/// <param name="storeObject"> The identifier of the containing store object. </param>
/// <param name="principalStoreObject"> The identifier of the principal store object. </param>
/// <returns> The foreign key constraint name. </returns>
public static string GetConstraintName(
[NotNull] this IForeignKey foreignKey,
[NotNull] string tableName,
[CanBeNull] string schema,
[NotNull] string principalTableName,
[CanBeNull] string principalSchema)
[NotNull] this IForeignKey foreignKey, StoreObjectIdentifier storeObject, StoreObjectIdentifier principalStoreObject)
{
var annotation = foreignKey.FindAnnotation(RelationalAnnotationNames.Name);
return annotation != null
? (string)annotation.Value
: foreignKey.GetDefaultName(tableName, schema, principalTableName, principalSchema);
: foreignKey.GetDefaultName(storeObject, principalStoreObject);
}

/// <summary>
Expand Down Expand Up @@ -77,19 +70,14 @@ public static string GetDefaultName([NotNull] this IForeignKey foreignKey)
/// Returns the default constraint name that would be used for this foreign key.
/// </summary>
/// <param name="foreignKey"> The foreign key. </param>
/// <param name="tableName"> The table name. </param>
/// <param name="schema"> The schema. </param>
/// <param name="principalTableName"> The principal table name. </param>
/// <param name="principalSchema"> The principal schema. </param>
/// <param name="storeObject"> The identifier of the containing store object. </param>
/// <param name="principalStoreObject"> The identifier of the principal store object. </param>
/// <returns> The default constraint name that would be used for this foreign key. </returns>
public static string GetDefaultName(
[NotNull] this IForeignKey foreignKey,
[NotNull] string tableName,
[CanBeNull] string schema,
[NotNull] string principalTableName,
[CanBeNull] string principalSchema)
StoreObjectIdentifier storeObject,
StoreObjectIdentifier principalStoreObject)
{
var storeObject = StoreObjectIdentifier.Table(tableName, schema);
var propertyNames = foreignKey.Properties.Select(p => p.GetColumnName(storeObject)).ToList();
var principalPropertyNames = foreignKey.PrincipalKey.Properties.Select(p => p.GetColumnName(storeObject)).ToList();
var rootForeignKey = foreignKey;
Expand All @@ -101,8 +89,8 @@ public static string GetDefaultName(
var linkedForeignKey = rootForeignKey.DeclaringEntityType
.FindRowInternalForeignKeys(storeObject)
.SelectMany(fk => fk.PrincipalEntityType.GetForeignKeys())
.FirstOrDefault(k => principalTableName == k.PrincipalEntityType.GetTableName()
&& principalSchema == k.PrincipalEntityType.GetSchema()
.FirstOrDefault(k => principalStoreObject.Name == k.PrincipalEntityType.GetTableName()
&& principalStoreObject.Schema == k.PrincipalEntityType.GetSchema()
&& propertyNames.SequenceEqual(k.Properties.Select(p => p.GetColumnName(storeObject)))
&& principalPropertyNames.SequenceEqual(k.PrincipalKey.Properties.Select(p => p.GetColumnName(storeObject))));
if (linkedForeignKey == null)
Expand All @@ -115,14 +103,14 @@ public static string GetDefaultName(

if (rootForeignKey != foreignKey)
{
return rootForeignKey.GetConstraintName(tableName, schema, principalTableName, principalSchema);
return rootForeignKey.GetConstraintName(storeObject, principalStoreObject);
}

var baseName = new StringBuilder()
.Append("FK_")
.Append(tableName)
.Append(storeObject.Name)
.Append("_")
.Append(principalTableName)
.Append(principalStoreObject.Name)
.Append("_")
.AppendJoin(foreignKey.Properties.Select(p => p.GetColumnName(storeObject)), "_")
.ToString();
Expand Down Expand Up @@ -178,39 +166,33 @@ public static IEnumerable<IForeignKeyConstraint> GetMappedConstraints([NotNull]

/// <summary>
/// <para>
/// Finds the first <see cref="IForeignKey" /> that is mapped to the same constraint in a shared table.
/// Finds the first <see cref="IForeignKey" /> that is mapped to the same constraint in a shared table-like object.
/// </para>
/// <para>
/// This method is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <param name="foreignKey"> The foreign key. </param>
/// <param name="tableName"> The table name. </param>
/// <param name="schema"> The schema. </param>
/// <param name="storeObject"> The identifier of the containing store object. </param>
/// <returns> The foreign key if found, or <see langword="null" /> if none was found.</returns>
public static IForeignKey FindSharedTableRootForeignKey(
[NotNull] this IForeignKey foreignKey,
[NotNull] string tableName,
[CanBeNull] string schema)
public static IForeignKey FindSharedObjectRootForeignKey([NotNull] this IForeignKey foreignKey, StoreObjectIdentifier storeObject)
{
Check.NotNull(foreignKey, nameof(foreignKey));
Check.NotNull(tableName, nameof(tableName));

var foreignKeyName = foreignKey.GetConstraintName(tableName, schema,
foreignKey.PrincipalEntityType.GetTableName(), foreignKey.PrincipalEntityType.GetSchema());
var foreignKeyName = foreignKey.GetConstraintName(storeObject,
StoreObjectIdentifier.Table(foreignKey.PrincipalEntityType.GetTableName(), foreignKey.PrincipalEntityType.GetSchema()));
var rootForeignKey = foreignKey;

var storeObject = StoreObjectIdentifier.Table(tableName, schema);
// Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
// Using a hashset is detrimental to the perf when there are no cycles
for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
{
var linkedKey = rootForeignKey.DeclaringEntityType
.FindRowInternalForeignKeys(storeObject)
.SelectMany(fk => fk.PrincipalEntityType.GetForeignKeys())
.FirstOrDefault(k => k.GetConstraintName(tableName, schema,
k.PrincipalEntityType.GetTableName(), k.PrincipalEntityType.GetSchema())
.FirstOrDefault(k => k.GetConstraintName(storeObject,
StoreObjectIdentifier.Table(k.PrincipalEntityType.GetTableName(), k.PrincipalEntityType.GetSchema()))
== foreignKeyName);
if (linkedKey == null)
{
Expand All @@ -225,40 +207,34 @@ public static IForeignKey FindSharedTableRootForeignKey(

/// <summary>
/// <para>
/// Finds the first <see cref="IMutableForeignKey" /> that is mapped to the same constraint in a shared table.
/// Finds the first <see cref="IMutableForeignKey" /> that is mapped to the same constraint in a shared table-like object.
/// </para>
/// <para>
/// This method is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <param name="foreignKey"> The foreign key. </param>
/// <param name="tableName"> The table name. </param>
/// <param name="schema"> The schema. </param>
/// <param name="storeObject"> The identifier of the containing store object. </param>
/// <returns> The foreign key if found, or <see langword="null" /> if none was found.</returns>
public static IMutableForeignKey FindSharedTableRootForeignKey(
[NotNull] this IMutableForeignKey foreignKey,
[NotNull] string tableName,
[CanBeNull] string schema)
=> (IMutableForeignKey)((IForeignKey)foreignKey).FindSharedTableRootForeignKey(tableName, schema);
public static IMutableForeignKey FindSharedObjectRootForeignKey(
[NotNull] this IMutableForeignKey foreignKey, StoreObjectIdentifier storeObject)
=> (IMutableForeignKey)((IForeignKey)foreignKey).FindSharedObjectRootForeignKey(storeObject);

/// <summary>
/// <para>
/// Finds the first <see cref="IConventionForeignKey" /> that is mapped to the same constraint in a shared table.
/// Finds the first <see cref="IConventionForeignKey" /> that is mapped to the same constraint in a shared table-like object.
/// </para>
/// <para>
/// This method is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <param name="foreignKey"> The foreign key. </param>
/// <param name="tableName"> The table name. </param>
/// <param name="schema"> The schema. </param>
/// <param name="storeObject"> The identifier of the containing store object. </param>
/// <returns> The foreign key if found, or <see langword="null" /> if none was found.</returns>
public static IConventionForeignKey FindSharedTableRootForeignKey(
[NotNull] this IConventionForeignKey foreignKey,
[NotNull] string tableName,
[CanBeNull] string schema)
=> (IConventionForeignKey)((IForeignKey)foreignKey).FindSharedTableRootForeignKey(tableName, schema);
public static IConventionForeignKey FindSharedObjectRootForeignKey(
[NotNull] this IConventionForeignKey foreignKey, StoreObjectIdentifier storeObject)
=> (IConventionForeignKey)((IForeignKey)foreignKey).FindSharedObjectRootForeignKey(storeObject);
}
}
Loading

0 comments on commit ab2ea8f

Please sign in to comment.