Permalink
Browse files

Allow providers to limit the length of the generated identifiers.

Move identifier uniquification to conventions to make it deterministic.
Fix default decimal type mapping validation in SqlServer.

Fixes #9209
Fixes #10213
Fixes #10329
  • Loading branch information...
AndriySvyryd committed Nov 16, 2017
1 parent 6a83914 commit e8fcce13870909944e88a0cfba88b37453fc81f5
Showing with 835 additions and 290 deletions.
  1. +1 −0 samples/OracleProvider/src/OracleProvider/Metadata/Conventions/OracleConventionSetBuilder.cs
  2. +3 −2 samples/OracleProvider/test/OracleProvider.FunctionalTests/MonsterFixupChangedChangingOracleTest.cs
  3. +18 −1 samples/OracleProvider/test/OracleProvider.FunctionalTests/UpdatesOracleTest.cs
  4. +1 −0 src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
  5. +25 −0 src/EFCore.Relational.Specification.Tests/StoreGeneratedFixupRelationalTestBase.cs
  6. +5 −0 src/EFCore.Relational.Specification.Tests/UpdatesRelationalTestBase.cs
  7. +39 −0 src/EFCore.Relational/Metadata/Conventions/Internal/RelationalMaxConvention.cs
  8. +133 −26 src/EFCore.Relational/Metadata/Conventions/Internal/SharedTableConvention.cs
  9. +107 −45 src/EFCore.Relational/Metadata/Internal/ConstraintNamer.cs
  10. +8 −1 src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyBuilderAnnotations.cs
  11. +7 −0 src/EFCore.Relational/Metadata/Internal/RelationalIndexBuilderAnnotations.cs
  12. +7 −0 src/EFCore.Relational/Metadata/Internal/RelationalKeyBuilderAnnotations.cs
  13. +6 −0 src/EFCore.Relational/Metadata/Internal/RelationalModelBuilderAnnotations.cs
  14. +21 −0 src/EFCore.Relational/Metadata/Internal/RelationalModelExtensions.cs
  15. +5 −0 src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
  16. +5 −2 src/EFCore.Relational/Metadata/RelationalEntityTypeAnnotations.cs
  17. +19 −2 src/EFCore.Relational/Metadata/RelationalModelAnnotations.cs
  18. +1 −53 src/EFCore.Relational/Metadata/RelationalPropertyAnnotations.cs
  19. +1 −1 src/EFCore.Relational/RelationalReferenceCollectionBuilderExtensions.cs
  20. +1 −1 src/EFCore.Relational/RelationalReferenceOwnershipBuilderExtensions.cs
  21. +1 −1 src/EFCore.Relational/RelationalReferenceReferenceBuilderExtensions.cs
  22. +17 −4 src/EFCore.Specification.Tests/MonsterFixupTestBase.cs
  23. +1 −1 src/EFCore.Specification.Tests/Query/ChangeTrackingTestBase.cs
  24. +28 −0 src/EFCore.Specification.Tests/TestModels/UpdatesModel/Login.cs
  25. +28 −0 src/EFCore.Specification.Tests/TestModels/UpdatesModel/Profile.cs
  26. +36 −0 src/EFCore.Specification.Tests/UpdatesFixtureBase.cs
  27. +13 −5 src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs
  28. +1 −0 src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs
  29. +1 −1 src/EFCore/Metadata/Internal/PropertyExtensions.cs
  30. +3 −2 test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs
  31. +3 −3 test/EFCore.Relational.Tests/Metadata/InternalRelationalMetadataBuilderExtensionsTest.cs
  32. +126 −74 test/EFCore.Relational.Tests/RelationalModelValidatorTest.cs
  33. +5 −0 test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs
  34. +20 −2 test/EFCore.SqlServer.FunctionalTests/MonsterFixupChangedChangingSqlServerTest.cs
  35. +6 −0 test/EFCore.SqlServer.FunctionalTests/Query/ChangeTrackingSqlServerTest.cs
  36. +4 −0 test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQuerySqlServerFixture.cs
  37. +5 −0 test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs
  38. +2 −2 test/EFCore.SqlServer.FunctionalTests/StoreGeneratedFixupSqlServerTest.cs
  39. +6 −0 test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerFixture.cs
  40. +16 −0 test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs
  41. +15 −11 test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerValueGenerationStrategyConventionTest.cs
  42. +4 −3 test/EFCore.SqlServer.Tests/Metadata/SqlServerInternalMetadataBuilderExtensionsTest.cs
  43. +27 −27 test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs
  44. +4 −2 test/EFCore.Sqlite.FunctionalTests/MonsterFixupChangedOnlySqliteTest.cs
  45. +6 −0 test/EFCore.Sqlite.FunctionalTests/Query/ChangeTrackingSqliteTest.cs
  46. +2 −2 test/EFCore.Sqlite.FunctionalTests/StoreGeneratedFixupSqliteTest.cs
  47. +18 −1 test/EFCore.Sqlite.FunctionalTests/UpdatesSqliteTest.cs
  48. +4 −3 test/EFCore.Sqlite.Tests/Metadata/Internal/SqliteInternalMetadataBuilderExtensionsTest.cs
  49. +11 −10 test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs
  50. +9 −2 test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
@@ -25,6 +25,7 @@ public override ConventionSet AddConventions(ConventionSet conventionSet)

var valueGenerationStrategyConvention = new OracleValueGenerationStrategyConvention();
conventionSet.ModelInitializedConventions.Add(valueGenerationStrategyConvention);
conventionSet.ModelInitializedConventions.Add(new RelationalMaxIdentifierLengthConvention(128));

ValueGeneratorConvention valueGeneratorConvention = new OracleValueGeneratorConvention();
ReplaceConvention(conventionSet.BaseEntityTypeChangedConventions, valueGeneratorConvention);
@@ -21,9 +21,10 @@ public class MonsterFixupChangedChangingOracleFixture : MonsterFixupChangedChang
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(w => w.Log(RelationalEventId.QueryClientEvaluationWarning));

protected override void OnModelCreating<TMessage, TProductPhoto, TProductReview>(ModelBuilder builder)
protected override void OnModelCreating<TMessage, TProduct, TProductPhoto, TProductReview, TComputerDetail, TDimensions>(
ModelBuilder builder)
{
base.OnModelCreating<TMessage, TProductPhoto, TProductReview>(builder);
base.OnModelCreating<TMessage, TProduct, TProductPhoto, TProductReview, TComputerDetail, TDimensions>(builder);

builder.Entity<TMessage>().Property(e => e.MessageId).UseOracleIdentityColumn();
builder.Entity<TProductPhoto>().Property(e => e.PhotoId).UseOracleIdentityColumn();
@@ -1,6 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Linq;
using Microsoft.EntityFrameworkCore.TestModels.UpdatesModel;
using Xunit;

namespace Microsoft.EntityFrameworkCore
{
public class UpdatesOracleTest : UpdatesRelationalTestBase<UpdatesOracleFixture>
@@ -9,5 +13,18 @@ public UpdatesOracleTest(UpdatesOracleFixture fixture)
: base(fixture)
{
}

public override void Identifiers_are_generated_correctly()
{
using (var context = CreateContext())
{
var entityType = context.Model.FindEntityType(typeof(
LoginEntityTypeWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWorkingCorrectly));
Assert.Equal("LoginEntityTypeWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWorking~", entityType.Relational().TableName);
Assert.Equal("PK_LoginEntityTypeWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWork~", entityType.GetKeys().Single().Relational().Name);
Assert.Equal("FK_LoginEntityTypeWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWork~", entityType.GetForeignKeys().Single().Relational().Name);
Assert.Equal("IX_LoginEntityTypeWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWork~", entityType.GetIndexes().Single().Relational().Name);
}
}
}
}
@@ -62,6 +62,7 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui
GenerateFluentApiForAnnotation(ref annotations, RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema), stringBuilder);

IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.DbFunction);
IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.MaxIdentifierLength);

GenerateAnnotations(annotations, stringBuilder);
}
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore
{
public abstract class StoreGeneratedFixupRelationalTestBase<TFixture> : StoreGeneratedFixupTestBase<TFixture>
where TFixture : StoreGeneratedFixupRelationalTestBase<TFixture>.StoreGeneratedFixupRelationalFixtureBase, new()
{
protected StoreGeneratedFixupRelationalTestBase(TFixture fixture)
: base(fixture)
{
}

public abstract class StoreGeneratedFixupRelationalFixtureBase : StoreGeneratedFixupFixtureBase
{
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
{
base.OnModelCreating(modelBuilder, context);

modelBuilder.Entity<Item>().HasOne(i => i.Game).WithMany(g => g.Items).HasConstraintName("FK_GameEntity_Game_GameId");
modelBuilder.Entity<Actor>().HasOne(i => i.Game).WithMany(g => g.Actors).HasConstraintName("FK_GameEntity_Game_GameId");
}
}
}
}
@@ -4,7 +4,9 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Xunit;

// ReSharper disable InconsistentNaming
namespace Microsoft.EntityFrameworkCore
{
public abstract class UpdatesRelationalTestBase<TFixture> : UpdatesTestBase<TFixture>
@@ -15,6 +17,9 @@ protected UpdatesRelationalTestBase(TFixture fixture)
{
}

[Fact]
public abstract void Identifiers_are_generated_correctly();

protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());

@@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class RelationalMaxIdentifierLengthConvention : IModelInitializedConvention
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public RelationalMaxIdentifierLengthConvention(int maxIdentifierLength)
{
MaxIdentifierLength = maxIdentifierLength;
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual int MaxIdentifierLength { get; }

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
modelBuilder.Relational(ConfigurationSource.Convention).HasMaxIdentifierLength(MaxIdentifierLength);
return modelBuilder;
}
}
}
@@ -88,33 +88,35 @@ private static void SetOwnedTable(ForeignKey foreignKey)
/// </summary>
public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
var tables = new Dictionary<string, (List<EntityType> MappedEntityTypes, Dictionary<string, Property> Columns)>();
var maxLength = modelBuilder.Relational(ConfigurationSource.Convention).MaxIdentifierLength;
var tables = new Dictionary<string,
(Dictionary<string, Property> Columns,
Dictionary<string, Key> Keys,
Dictionary<string, ForeignKey> ForeignKeys,
Dictionary<string, Index> Indexes)>();
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
var annotations = entityType.Relational();
var tableName = Format(annotations.Schema, annotations.TableName);
var tableName = FormatName(entityType.Relational());

if (tables.TryGetValue(tableName, out var tableMapping))
if (!tables.TryGetValue(tableName, out var tableObjects))
{
if (tableMapping.MappedEntityTypes.Count == 1)
{
TryUniquifyColumnNames(tableMapping.MappedEntityTypes[0], tableMapping.Columns);
}
tableMapping.MappedEntityTypes.Add(entityType);
TryUniquifyColumnNames(entityType, tableMapping.Columns);
}
else
{
var mappedEntityTypes = new List<EntityType>();
tables[tableName] = (mappedEntityTypes, new Dictionary<string, Property>());
mappedEntityTypes.Add(entityType);
tableObjects = (new Dictionary<string, Property>(),
new Dictionary<string, Key>(),
new Dictionary<string, ForeignKey>(),
new Dictionary<string, Index>());
tables[tableName] = tableObjects;
}

TryUniquifyColumnNames(entityType, tableObjects.Columns, maxLength);
TryUniquifyKeyNames(entityType, tableObjects.Keys, maxLength);
TryUniquifyForeignKeyNames(entityType, tableObjects.ForeignKeys, maxLength);
TryUniquifyIndexNames(entityType, tableObjects.Indexes, maxLength);
}

return modelBuilder;
}

private static void TryUniquifyColumnNames(EntityType entityType, Dictionary<string, Property> properties)
private static void TryUniquifyColumnNames(EntityType entityType, Dictionary<string, Property> properties, int maxLength)
{
foreach (var property in entityType.GetDeclaredProperties())
{
@@ -130,7 +132,7 @@ private static void TryUniquifyColumnNames(EntityType entityType, Dictionary<str
var relationalPropertyBuilder = property.Builder.Relational(ConfigurationSource.Convention);
if (relationalPropertyBuilder.CanSetColumnName(null))
{
columnName = Uniquify(columnName, property.DeclaringEntityType.ShortName(), properties);
columnName = Uniquify(columnName, property.DeclaringEntityType.ShortName(), properties, maxLength);
relationalPropertyBuilder.ColumnName = columnName;
properties[columnName] = property;
continue;
@@ -143,33 +145,138 @@ private static void TryUniquifyColumnNames(EntityType entityType, Dictionary<str
if (otherRelationalPropertyBuilder.CanSetColumnName(null))
{
properties[columnName] = property;
columnName = Uniquify(columnName, otherProperty.DeclaringEntityType.ShortName(), properties);
columnName = Uniquify(columnName, otherProperty.DeclaringEntityType.ShortName(), properties, maxLength);
otherRelationalPropertyBuilder.ColumnName = columnName;
properties[columnName] = otherProperty;
}
}
}
}

private static string Uniquify<T>(string baseIdentifier, string prefix, Dictionary<string, T> existingIdentifiers)
private static void TryUniquifyKeyNames(EntityType entityType, Dictionary<string, Key> keys, int maxLength)
{
foreach (var key in entityType.GetDeclaredKeys())
{
var keyName = key.Relational().Name;
if (!keys.TryGetValue(keyName, out var otherKey))
{
keys[keyName] = key;
continue;
}

if (!key.IsPrimaryKey())
{
var relationalKeyBuilder = key.Builder.Relational(ConfigurationSource.Convention);
if (relationalKeyBuilder.CanSetName(null))
{
keyName = Uniquify(keyName, null, keys, maxLength);
relationalKeyBuilder.Name = keyName;
keys[keyName] = key;
continue;
}
}

if (!otherKey.IsPrimaryKey())
{
var otherRelationalKeyBuilder = otherKey.Builder.Relational(ConfigurationSource.Convention);
if (otherRelationalKeyBuilder.CanSetName(null))
{
keys[keyName] = key;
keyName = Uniquify(keyName, null, keys, maxLength);
otherRelationalKeyBuilder.Name = keyName;
keys[keyName] = otherKey;
}
}
}
}

private static void TryUniquifyIndexNames(EntityType entityType, Dictionary<string, Index> indexes, int maxLength)
{
foreach (var index in entityType.GetDeclaredIndexes())
{
var indexName = index.Relational().Name;
if (!indexes.TryGetValue(indexName, out var otherIndex))
{
indexes[indexName] = index;
continue;
}

var relationalIndexBuilder = index.Builder.Relational(ConfigurationSource.Convention);
if (relationalIndexBuilder.CanSetName(null))
{
indexName = Uniquify(indexName, null, indexes, maxLength);
relationalIndexBuilder.Name = indexName;
indexes[indexName] = index;
continue;
}

var otherRelationalIndexBuilder = otherIndex.Builder.Relational(ConfigurationSource.Convention);
if (otherRelationalIndexBuilder.CanSetName(null))
{
indexes[indexName] = index;
indexName = Uniquify(indexName, null, indexes, maxLength);
otherRelationalIndexBuilder.Name = indexName;
indexes[indexName] = otherIndex;
}
}
}

private static void TryUniquifyForeignKeyNames(EntityType entityType, Dictionary<string, ForeignKey> foreignKeys, int maxLength)
{
foreach (var foreignKey in entityType.GetDeclaredForeignKeys())
{
var foreignKeyName = foreignKey.Relational().Name;
if (!foreignKeys.TryGetValue(foreignKeyName, out var otherForeignKey))
{
foreignKeys[foreignKeyName] = foreignKey;
continue;
}

if (FormatName(foreignKey.DeclaringEntityType.Relational())
== FormatName(foreignKey.PrincipalEntityType.Relational()))
{
continue;
}

var relationalKeyBuilder = foreignKey.Builder.Relational(ConfigurationSource.Convention);
if (relationalKeyBuilder.CanSetName(null))
{
foreignKeyName = Uniquify(foreignKeyName, null, foreignKeys, maxLength);
relationalKeyBuilder.Name = foreignKeyName;
foreignKeys[foreignKeyName] = foreignKey;
continue;
}

var otherRelationalKeyBuilder = otherForeignKey.Builder.Relational(ConfigurationSource.Convention);
if (otherRelationalKeyBuilder.CanSetName(null))
{
foreignKeys[foreignKeyName] = foreignKey;
foreignKeyName = Uniquify(foreignKeyName, null, foreignKeys, maxLength);
otherRelationalKeyBuilder.Name = foreignKeyName;
foreignKeys[foreignKeyName] = otherForeignKey;
}
}
}

private static string Uniquify<T>(string baseIdentifier, string prefix, Dictionary<string, T> existingIdentifiers, int maxLength)
{
if (!baseIdentifier.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrEmpty(prefix)
&& !baseIdentifier.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
baseIdentifier = prefix + "_" + baseIdentifier;
}

var finalIdentifier = ConstraintNamer.Truncate(baseIdentifier, null, maxLength);
var suffix = 1;
var finalIdentifier = baseIdentifier;
while (existingIdentifiers.ContainsKey(finalIdentifier))
{
finalIdentifier = baseIdentifier + suffix;
suffix++;
finalIdentifier = ConstraintNamer.Truncate(baseIdentifier, suffix++, maxLength);
}

return finalIdentifier;
}

private static string Format(string schema, string name)
=> (string.IsNullOrEmpty(schema) ? "" : schema + ".") + name;
private static string FormatName(IRelationalEntityTypeAnnotations annotations)
=> (string.IsNullOrEmpty(annotations.Schema) ? "" : annotations.Schema + ".") + annotations.TableName;
}
}
Oops, something went wrong.

0 comments on commit e8fcce1

Please sign in to comment.