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

Fix for 19608. Truncate/Uniquify Sequence Names. #20834

Merged
merged 4 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
public class SequenceUniquificationConvention : IModelFinalizingConvention
{
/// <summary>
/// Creates a new instance of <see cref="SequenceUniquificationConvention" />.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
/// <param name="relationalDependencies"> Parameter object containing relational dependencies for this convention. </param>
public SequenceUniquificationConvention(
[NotNull] ProviderConventionSetBuilderDependencies dependencies,
[NotNull] RelationalConventionSetBuilderDependencies relationalDependencies)
{
Dependencies = dependencies;
}

/// <summary>
/// Parameter object containing service dependencies.
/// </summary>
protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; }

/// <inheritdoc />
public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> 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.UpdateSequence((IMutableModel)model, sequence.Value, newSequenceName);
}
}
}
}
}
37 changes: 33 additions & 4 deletions src/EFCore.Relational/Metadata/Internal/Sequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -187,6 +186,36 @@ public static Sequence AddSequence(
return sequence;
}

/// <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 Sequence UpdateSequence(
lajones marked this conversation as resolved.
Show resolved Hide resolved
[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;
}

/// <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 Expand Up @@ -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.
/// </summary>
public virtual string Name => _name;
public virtual string Name { get; [param: NotNull] set; }

/// <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
@@ -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<DbContext>())));
var relationalDependencies = CreateRelationalDependencies();
conventionSet.ModelFinalizingConventions.Add(
new SequenceUniquificationConvention(dependencies, relationalDependencies));

return new ModelBuilder(conventionSet);
}

private ProviderConventionSetBuilderDependencies CreateDependencies()
=> RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService<ProviderConventionSetBuilderDependencies>();

private RelationalConventionSetBuilderDependencies CreateRelationalDependencies()
=> RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService<RelationalConventionSetBuilderDependencies>();
}
}