Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate more SqlServer-specific configuration on shared database objects #20185

Merged
merged 1 commit into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ namespace Microsoft.EntityFrameworkCore
public static class RelationalEntityTypeExtensions
{
/// <summary>
/// Returns the name of the table to which the entity type is mapped.
/// Returns the name of the table to which the entity type is mapped
/// or <c>null</c> if not mapped to a table.
/// </summary>
/// <param name="entityType"> The entity type to get the table name for. </param>
/// <returns> The name of the table to which the entity type is mapped. </returns>
Expand Down Expand Up @@ -161,6 +162,24 @@ public static void SetSchema(
=> entityType.FindAnnotation(RelationalAnnotationNames.Schema)
?.GetConfigurationSource();

/// <summary>
/// Returns the name of the table to which the entity type is mapped prepended by the schema
/// or <c>null</c> if not mapped to a table.
/// </summary>
/// <param name="entityType"> The entity type to get the table name for. </param>
/// <returns> The name of the table to which the entity type is mapped prepended by the schema. </returns>
public static string GetSchemaQualifiedTableName([NotNull] this IEntityType entityType)
{
var tableName = entityType.GetTableName();
if (tableName == null)
{
return null;
}

var schema = entityType.GetSchema();
return (string.IsNullOrEmpty(schema) ? "" : schema + ".") + tableName;
}

/// <summary>
/// Returns the tables to which the entity type is mapped.
/// </summary>
Expand Down Expand Up @@ -190,7 +209,7 @@ public static IEnumerable<IViewMapping> GetViewMappings([NotNull] this IEntityTy
?? Enumerable.Empty<IViewMapping>();

/// <summary>
/// Returns the name of the view to which the entity type is mapped.
/// Returns the name of the view to which the entity type is mapped or <c>null</c> if not mapped to a view.
/// </summary>
/// <param name="entityType"> The entity type to get the view name for. </param>
/// <returns> The name of the view to which the entity type is mapped. </returns>
Expand Down
319 changes: 189 additions & 130 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
public static class RelationalPropertyExtensions
{
/// <summary>
/// Creates a comma-separated list of property names.
/// Creates a comma-separated list of column names.
/// </summary>
/// <param name="properties"> The properties to format. </param>
/// <returns> A comma-separated list of property names. </returns>
/// <returns> A comma-separated list of column names. </returns>
public static string FormatColumns([NotNull] this IEnumerable<IProperty> properties)
=> "{" + string.Join(", ", properties.Select(p => "'" + p.GetColumnName() + "'")) + "}";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public static bool AreCompatible([NotNull] this IForeignKey foreignKey, [NotNull
foreignKey.DeclaringEntityType.DisplayName(),
duplicateForeignKey.Properties.Format(),
duplicateForeignKey.DeclaringEntityType.DisplayName(),
Format(foreignKey.DeclaringEntityType),
foreignKey.DeclaringEntityType.GetSchemaQualifiedTableName(),
foreignKey.GetConstraintName(),
Format(principalType),
Format(duplicatePrincipalType)));
principalType.GetSchemaQualifiedTableName(),
duplicatePrincipalType.GetSchemaQualifiedTableName()));
}

return false;
Expand All @@ -58,7 +58,7 @@ public static bool AreCompatible([NotNull] this IForeignKey foreignKey, [NotNull
foreignKey.DeclaringEntityType.DisplayName(),
duplicateForeignKey.Properties.Format(),
duplicateForeignKey.DeclaringEntityType.DisplayName(),
Format(foreignKey.DeclaringEntityType),
foreignKey.DeclaringEntityType.GetSchemaQualifiedTableName(),
foreignKey.GetConstraintName(),
foreignKey.Properties.FormatColumns(),
duplicateForeignKey.Properties.FormatColumns()));
Expand All @@ -81,7 +81,7 @@ public static bool AreCompatible([NotNull] this IForeignKey foreignKey, [NotNull
foreignKey.DeclaringEntityType.DisplayName(),
duplicateForeignKey.Properties.Format(),
duplicateForeignKey.DeclaringEntityType.DisplayName(),
Format(foreignKey.DeclaringEntityType),
foreignKey.DeclaringEntityType.GetSchemaQualifiedTableName(),
foreignKey.GetConstraintName(),
foreignKey.PrincipalKey.Properties.FormatColumns(),
duplicateForeignKey.PrincipalKey.Properties.FormatColumns()));
Expand All @@ -100,7 +100,7 @@ public static bool AreCompatible([NotNull] this IForeignKey foreignKey, [NotNull
foreignKey.DeclaringEntityType.DisplayName(),
duplicateForeignKey.Properties.Format(),
duplicateForeignKey.DeclaringEntityType.DisplayName(),
Format(foreignKey.DeclaringEntityType),
foreignKey.DeclaringEntityType.GetSchemaQualifiedTableName(),
foreignKey.GetConstraintName()));
}

Expand All @@ -117,7 +117,7 @@ public static bool AreCompatible([NotNull] this IForeignKey foreignKey, [NotNull
foreignKey.DeclaringEntityType.DisplayName(),
duplicateForeignKey.Properties.Format(),
duplicateForeignKey.DeclaringEntityType.DisplayName(),
Format(foreignKey.DeclaringEntityType),
foreignKey.DeclaringEntityType.GetSchemaQualifiedTableName(),
foreignKey.GetConstraintName(),
foreignKey.DeleteBehavior,
duplicateForeignKey.DeleteBehavior));
Expand All @@ -128,8 +128,5 @@ public static bool AreCompatible([NotNull] this IForeignKey foreignKey, [NotNull

return true;
}

private static string Format(IEntityType entityType)
=> (string.IsNullOrEmpty(entityType.GetSchema()) ? "" : entityType.GetSchema() + ".") + entityType.GetTableName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static bool AreCompatible([NotNull] this IIndex index, [NotNull] IIndex d
index.DeclaringEntityType.DisplayName(),
duplicateIndex.Properties.Format(),
duplicateIndex.DeclaringEntityType.DisplayName(),
Format(index.DeclaringEntityType),
index.DeclaringEntityType.GetSchemaQualifiedTableName(),
index.GetName(),
index.Properties.FormatColumns(),
duplicateIndex.Properties.FormatColumns()));
Expand All @@ -55,7 +55,7 @@ public static bool AreCompatible([NotNull] this IIndex index, [NotNull] IIndex d
index.DeclaringEntityType.DisplayName(),
duplicateIndex.Properties.Format(),
duplicateIndex.DeclaringEntityType.DisplayName(),
Format(index.DeclaringEntityType),
index.DeclaringEntityType.GetSchemaQualifiedTableName(),
index.GetName()));
}

Expand All @@ -64,8 +64,5 @@ public static bool AreCompatible([NotNull] this IIndex index, [NotNull] IIndex d

return true;
}

private static string Format(IEntityType entityType)
=> (string.IsNullOrEmpty(entityType.GetSchema()) ? "" : entityType.GetSchema() + ".") + entityType.GetTableName();
}
}
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ private static void PopulateForeignKeyConstraints(Table table)
}

if (columns == null
|| StructuralComparisons.StructuralEqualityComparer.Equals(columns, principalColumns))
|| columns.SequenceEqual(principalColumns))
{
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -81,7 +80,7 @@ public bool Equals(ITableMapping x, ITableMapping y)
=> x.EntityType == y.EntityType
&& x.Table == y.Table
&& x.IncludesDerivedTypes == y.IncludesDerivedTypes
&& StructuralComparisons.StructuralEqualityComparer.Equals(x.ColumnMappings, y.ColumnMappings);
&& x.ColumnMappings.SequenceEqual(y.ColumnMappings);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -81,7 +80,7 @@ public bool Equals(IViewMapping x, IViewMapping y)
=> x.EntityType == y.EntityType
&& x.View == y.View
&& x.IncludesDerivedTypes == y.IncludesDerivedTypes
&& StructuralComparisons.StructuralEqualityComparer.Equals(x.ColumnMappings, y.ColumnMappings);
&& x.ColumnMappings.SequenceEqual(y.ColumnMappings);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,7 @@ public static bool CanSetIncludeProperties(

return (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
.Overrides(indexBuilder.Metadata.GetIncludePropertiesConfigurationSource())
|| StructuralComparisons.StructuralEqualityComparer.Equals(
propertyNames, indexBuilder.Metadata.GetIncludeProperties());
|| propertyNames.SequenceEqual(indexBuilder.Metadata.GetIncludeProperties());
}

/// <summary>
Expand Down
143 changes: 89 additions & 54 deletions src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -214,84 +215,118 @@ protected override void ValidateSharedColumnsCompatibility(
{
base.ValidateSharedColumnsCompatibility(mappedTypes, tableName, logger);

var identityColumns = new List<IProperty>();
var propertyMappings = new Dictionary<string, IProperty>();
var identityColumns = new Dictionary<string, IProperty>();

foreach (var property in mappedTypes.SelectMany(et => et.GetDeclaredProperties()))
{
var columnName = property.GetColumnName();
if (propertyMappings.TryGetValue(columnName, out var duplicateProperty))
if (property.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn)
{
var propertyStrategy = property.GetValueGenerationStrategy();
var duplicatePropertyStrategy = duplicateProperty.GetValueGenerationStrategy();
if (propertyStrategy != duplicatePropertyStrategy
&& (propertyStrategy == SqlServerValueGenerationStrategy.IdentityColumn
|| duplicatePropertyStrategy == SqlServerValueGenerationStrategy.IdentityColumn))
identityColumns[property.GetColumnName()] = property;
}
}

if (identityColumns.Count > 1)
{
var sb = new StringBuilder()
.AppendJoin(identityColumns.Values.Select(p => "'" + p.DeclaringEntityType.DisplayName() + "." + p.Name + "'"));
throw new InvalidOperationException(SqlServerStrings.MultipleIdentityColumns(sb, tableName));
}
}

/// <inheritdoc />
protected override void ValidateCompatible(IProperty property, IProperty duplicateProperty, string columnName, string tableName)
{
base.ValidateCompatible(property, duplicateProperty, columnName, tableName);

var propertyStrategy = property.GetValueGenerationStrategy();
var duplicatePropertyStrategy = duplicateProperty.GetValueGenerationStrategy();
if (propertyStrategy != duplicatePropertyStrategy)
{
throw new InvalidOperationException(
SqlServerStrings.DuplicateColumnNameValueGenerationStrategyMismatch(
duplicateProperty.DeclaringEntityType.DisplayName(),
duplicateProperty.Name,
property.DeclaringEntityType.DisplayName(),
property.Name,
columnName,
tableName));
}

switch (propertyStrategy)
{
case SqlServerValueGenerationStrategy.IdentityColumn:
var increment = property.GetIdentityIncrement();
var duplicateIncrement = duplicateProperty.GetIdentityIncrement();
if (increment != duplicateIncrement)
{
throw new InvalidOperationException(
SqlServerStrings.DuplicateColumnNameValueGenerationStrategyMismatch(
SqlServerStrings.DuplicateColumnIdentityIncrementMismatch(
duplicateProperty.DeclaringEntityType.DisplayName(),
duplicateProperty.Name,
property.DeclaringEntityType.DisplayName(),
property.Name,
columnName,
tableName));
}
}
else
{
propertyMappings[columnName] = property;
if (property.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn)

var seed = property.GetIdentitySeed();
var duplicateSeed = duplicateProperty.GetIdentitySeed();
if (seed != duplicateSeed)
{
identityColumns.Add(property);
throw new InvalidOperationException(
SqlServerStrings.DuplicateColumnIdentitySeedMismatch(
duplicateProperty.DeclaringEntityType.DisplayName(),
duplicateProperty.Name,
property.DeclaringEntityType.DisplayName(),
property.Name,
columnName,
tableName));
}
}
}

if (identityColumns.Count > 1)
{
var sb = new StringBuilder()
.AppendJoin(identityColumns.Select(p => "'" + p.DeclaringEntityType.DisplayName() + "." + p.Name + "'"));
throw new InvalidOperationException(SqlServerStrings.MultipleIdentityColumns(sb, tableName));
break;
case SqlServerValueGenerationStrategy.SequenceHiLo:
if (property.GetHiLoSequenceName() != duplicateProperty.GetHiLoSequenceName()
|| property.GetHiLoSequenceSchema() != duplicateProperty.GetHiLoSequenceSchema())
{
throw new InvalidOperationException(
SqlServerStrings.DuplicateColumnSequenceMismatch(
duplicateProperty.DeclaringEntityType.DisplayName(),
duplicateProperty.Name,
property.DeclaringEntityType.DisplayName(),
property.Name,
columnName,
tableName));
}

break;
}
}

/// <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>
protected override void ValidateSharedKeysCompatibility(
IReadOnlyList<IEntityType> mappedTypes, string tableName, IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
/// <inheritdoc />
protected override void ValidateCompatible(IKey key, IKey duplicateKey, string keyName, string tableName)
{
base.ValidateSharedKeysCompatibility(mappedTypes, tableName, logger);
base.ValidateCompatible(key, duplicateKey, keyName, tableName);

var keyMappings = new Dictionary<string, IKey>();

foreach (var key in mappedTypes.SelectMany(et => et.GetDeclaredKeys()))
if (key.IsClustered()
!= duplicateKey.IsClustered())
{
var keyName = key.GetName();
throw new InvalidOperationException(
SqlServerStrings.DuplicateKeyMismatchedClustering(
key.Properties.Format(),
key.DeclaringEntityType.DisplayName(),
duplicateKey.Properties.Format(),
duplicateKey.DeclaringEntityType.DisplayName(),
tableName,
keyName));
}
}

if (!keyMappings.TryGetValue(keyName, out var duplicateKey))
{
keyMappings[keyName] = key;
continue;
}
/// <inheritdoc />
protected override void ValidateCompatible(IIndex index, IIndex duplicateIndex, string indexName, string tableName)
{
base.ValidateCompatible(index, duplicateIndex, indexName, tableName);

if (key.IsClustered()
!= duplicateKey.IsClustered())
{
throw new InvalidOperationException(
SqlServerStrings.DuplicateKeyMismatchedClustering(
key.Properties.Format(),
key.DeclaringEntityType.DisplayName(),
duplicateKey.Properties.Format(),
duplicateKey.DeclaringEntityType.DisplayName(),
tableName,
keyName));
}
}
index.AreCompatibleForSqlServer(duplicateIndex, shouldThrow: true);
}
}
}
Loading