diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
index d1ed7791b15..8c4905ccf1e 100644
--- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
@@ -106,6 +106,7 @@ public override ConventionSet CreateConventionSet()
conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention);
conventionSet.ModelFinalizingConventions.Add(tableNameFromDbSetConvention);
conventionSet.ModelFinalizingConventions.Add(storeGenerationConvention);
+ conventionSet.ModelFinalizingConventions.Add(new SequenceUniquificationConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(new SharedTableConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(new DbFunctionTypeMappingConvention(Dependencies, RelationalDependencies));
ReplaceConvention(
diff --git a/src/EFCore.Relational/Metadata/Conventions/SequenceUniquificationConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SequenceUniquificationConvention.cs
new file mode 100644
index 00000000000..a2dfbd000d0
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Conventions/SequenceUniquificationConvention.cs
@@ -0,0 +1,61 @@
+// 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.Collections.Generic;
+using System.Linq;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
+{
+ ///
+ /// A convention which ensures that all sequences in the model have unique names
+ /// within a schema when truncated to the maximum identifier length for the model.
+ ///
+ public class SequenceUniquificationConvention : IModelFinalizingConvention
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// Parameter object containing dependencies for this convention.
+ /// Parameter object containing relational dependencies for this convention.
+ public SequenceUniquificationConvention(
+ [NotNull] ProviderConventionSetBuilderDependencies dependencies,
+ [NotNull] RelationalConventionSetBuilderDependencies relationalDependencies)
+ {
+ Dependencies = dependencies;
+ }
+
+ ///
+ /// Parameter object containing service dependencies.
+ ///
+ protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; }
+
+ ///
+ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context)
+ {
+ var model = modelBuilder.Metadata;
+ var modelSequences =
+ (SortedDictionary<(string Name, string Schema), Sequence>)model[RelationalAnnotationNames.Sequences];
+
+ if (modelSequences != null)
+ {
+ var maxLength = model.GetMaxIdentifierLength();
+ var toReplace = modelSequences
+ .Where(s => s.Key.Name.Length > maxLength).ToList();
+
+ foreach (var sequence in toReplace)
+ {
+ var schemaName = sequence.Key.Schema;
+ var newSequenceName = Uniquifier.Uniquify(
+ sequence.Key.Name, modelSequences,
+ sequenceName => (sequenceName, schemaName), maxLength);
+ Sequence.SetName((IMutableModel)model, sequence.Value, newSequenceName);
+ }
+ }
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Internal/Sequence.cs b/src/EFCore.Relational/Metadata/Internal/Sequence.cs
index e4b336119f3..24f746b6137 100644
--- a/src/EFCore.Relational/Metadata/Internal/Sequence.cs
+++ b/src/EFCore.Relational/Metadata/Internal/Sequence.cs
@@ -24,7 +24,6 @@ public class Sequence : ConventionAnnotatable, IMutableSequence, IConventionSequ
{
private readonly IModel _model;
- private readonly string _name;
private readonly string _schema;
private long? _startValue;
private int? _incrementBy;
@@ -105,7 +104,7 @@ public Sequence(
Check.NullButNotEmpty(schema, nameof(schema));
_model = model;
- _name = name;
+ Name = name;
_schema = schema;
_configurationSource = configurationSource;
Builder = new InternalSequenceBuilder(this, ((IConventionModel)model).Builder);
@@ -127,7 +126,7 @@ public Sequence([NotNull] IModel model, [NotNull] string annotationName)
_configurationSource = ConfigurationSource.Explicit;
var data = SequenceData.Deserialize((string)model[annotationName]);
- _name = data.Name;
+ Name = data.Name;
_schema = data.Schema;
_startValue = data.StartValue;
_incrementBy = data.IncrementBy;
@@ -187,6 +186,36 @@ public static Sequence AddSequence(
return sequence;
}
+ ///
+ /// 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 Sequence SetName(
+ [NotNull] IMutableModel model, [NotNull] Sequence sequence, [NotNull] string name)
+ {
+ Check.NotNull(model, nameof(model));
+ Check.NotNull(sequence, nameof(sequence));
+ Check.NotEmpty(name, nameof(name));
+
+ var sequences = (SortedDictionary<(string, string), Sequence>)model[RelationalAnnotationNames.Sequences];
+ var tuple = (sequence.Name, sequence.Schema);
+ if (sequences == null
+ || !sequences.ContainsKey(tuple))
+ {
+ return null;
+ }
+
+ sequences.Remove(tuple);
+
+ sequence.Name = name;
+
+ sequences.Add((name, sequence.Schema), sequence);
+
+ return sequence;
+ }
+
///
/// 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
@@ -230,7 +259,7 @@ public static Sequence RemoveSequence([NotNull] IMutableModel model, [NotNull] s
/// 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 virtual string Name => _name;
+ public virtual string Name { get; [param: NotNull] set; }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/SequenceUniquificationConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/SequenceUniquificationConventionTest.cs
new file mode 100644
index 00000000000..9cc85ee33e5
--- /dev/null
+++ b/test/EFCore.Relational.Tests/Metadata/Conventions/SequenceUniquificationConventionTest.cs
@@ -0,0 +1,85 @@
+// 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.Infrastructure;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
+{
+ public class SequenceUniquificationConventionTest
+ {
+ [ConditionalFact]
+ public virtual void Sequence_names_are_truncated_and_uniquified()
+ {
+ var modelBuilder = GetModelBuilder();
+ modelBuilder.GetInfrastructure().HasMaxIdentifierLength(10);
+ modelBuilder.HasSequence("UniquifyMeToo", (string)null);
+ modelBuilder.HasSequence("UniquifyMeToo", "TestSchema");
+ modelBuilder.HasSequence("UniquifyM!Too", (string)null);
+ modelBuilder.HasSequence("UniquifyM!Too", "TestSchema");
+ // the below ensure we deal with clashes with existing
+ // sequence names that look like candidate uniquified names
+ modelBuilder.HasSequence("UniquifyM~", (string)null);
+ modelBuilder.HasSequence("UniquifyM~", "TestSchema");
+
+ var model = modelBuilder.Model;
+ model.FinalizeModel();
+
+ Assert.Collection(model.GetSequences(),
+ s0 =>
+ {
+ Assert.Equal("Uniquify~1", s0.Name);
+ Assert.Null(s0.Schema);
+ },
+ s1 =>
+ {
+ Assert.Equal("Uniquify~1", s1.Name);
+ Assert.Equal("TestSchema", s1.Schema);
+ },
+ s2 =>
+ {
+ Assert.Equal("Uniquify~2", s2.Name);
+ Assert.Null(s2.Schema);
+ },
+ s3 =>
+ {
+ Assert.Equal("Uniquify~2", s3.Name);
+ Assert.Equal("TestSchema", s3.Schema);
+ },
+ s4 =>
+ {
+ Assert.Equal("UniquifyM~", s4.Name);
+ Assert.Null(s4.Schema);
+ },
+ s5 =>
+ {
+ Assert.Equal("UniquifyM~", s5.Name);
+ Assert.Equal("TestSchema", s5.Schema);
+ });
+ }
+
+ private ModelBuilder GetModelBuilder()
+ {
+ var conventionSet = new ConventionSet();
+
+ var dependencies = CreateDependencies()
+ .With(new CurrentDbContext(new DbContext(new DbContextOptions())));
+ var relationalDependencies = CreateRelationalDependencies();
+ conventionSet.ModelFinalizingConventions.Add(
+ new SequenceUniquificationConvention(dependencies, relationalDependencies));
+
+ return new ModelBuilder(conventionSet);
+ }
+
+ private ProviderConventionSetBuilderDependencies CreateDependencies()
+ => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService();
+
+ private RelationalConventionSetBuilderDependencies CreateRelationalDependencies()
+ => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService();
+ }
+}