From 04dece90fe09abda6a68fe0719cdf34d357004f7 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Tue, 25 Feb 2020 17:58:40 -0800 Subject: [PATCH] Store all sequences in a single annotation Fixes #14103 --- .../Design/CSharpSnapshotGenerator.cs | 93 ++++++ .../Design/MigrationsCodeGenerator.cs | 3 +- .../Internal/SnapshotModelProcessor.cs | 30 +- .../Internal/CSharpDbContextGenerator.cs | 4 +- .../RelationalEntityTypeExtensions.cs | 23 +- .../Extensions/RelationalModelExtensions.cs | 26 +- .../Metadata/Internal/Sequence.cs | 267 +++++++++--------- .../Metadata/RelationalAnnotationNames.cs | 10 +- src/EFCore/Metadata/Internal/Property.cs | 34 +-- .../Design/CSharpMigrationsGeneratorTest.cs | 10 +- .../Design/SnapshotModelProcessorTest.cs | 84 +++++- .../Migrations/ModelSnapshotSqlServerTest.cs | 42 +-- .../RelationalMetadataExtensionsTest.cs | 3 +- .../Metadata/SequenceTest.cs | 150 +++------- ...erValueGenerationStrategyConventionTest.cs | 2 +- .../SqlServerMetadataExtensionsTest.cs | 140 --------- 16 files changed, 438 insertions(+), 483 deletions(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 3f80b2fa144..ecd60227ecb 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -62,6 +62,7 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui CoreAnnotationNames.ChangeTrackingStrategy, CoreAnnotationNames.OwnedTypes, RelationalAnnotationNames.CheckConstraints, + RelationalAnnotationNames.Sequences, RelationalAnnotationNames.Tables, RelationalAnnotationNames.Views); @@ -81,6 +82,11 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui stringBuilder.AppendLine(";"); } + foreach (var sequence in model.GetSequences()) + { + GenerateSequence(builderName, sequence, stringBuilder); + } + GenerateEntityTypes(builderName, Sort(model.GetEntityTypes().Where(et => !et.IsIgnoredByMigrations()).ToList()), stringBuilder); } @@ -338,6 +344,93 @@ protected virtual void GenerateBaseType( } } + /// + /// Generates code for an . + /// + /// The name of the builder variable. + /// The sequence. + /// The builder code is added to. + protected virtual void GenerateSequence( + [NotNull] string builderName, + [NotNull] ISequence sequence, + [NotNull] IndentedStringBuilder stringBuilder) + { + stringBuilder + .AppendLine() + .Append(builderName) + .Append(".HasSequence"); + + if (sequence.ClrType != Sequence.DefaultClrType) + { + stringBuilder + .Append("<") + .Append(Code.Reference(sequence.ClrType)) + .Append(">"); + } + + stringBuilder + .Append("(") + .Append(Code.Literal(sequence.Name)); + + if (!string.IsNullOrEmpty(sequence.Schema) + && sequence.Model.GetDefaultSchema() != sequence.Schema) + { + stringBuilder + .Append(", ") + .Append(Code.Literal(sequence.Schema)); + } + + stringBuilder.Append(")"); + + using (stringBuilder.Indent()) + { + if (sequence.StartValue != Sequence.DefaultStartValue) + { + stringBuilder + .AppendLine() + .Append(".StartsAt(") + .Append(Code.Literal(sequence.StartValue)) + .Append(")"); + } + + if (sequence.IncrementBy != Sequence.DefaultIncrementBy) + { + stringBuilder + .AppendLine() + .Append(".IncrementsBy(") + .Append(Code.Literal(sequence.IncrementBy)) + .Append(")"); + } + + if (sequence.MinValue != Sequence.DefaultMinValue) + { + stringBuilder + .AppendLine() + .Append(".HasMin(") + .Append(Code.Literal(sequence.MinValue)) + .Append(")"); + } + + if (sequence.MaxValue != Sequence.DefaultMaxValue) + { + stringBuilder + .AppendLine() + .Append(".HasMax(") + .Append(Code.Literal(sequence.MaxValue)) + .Append(")"); + } + + if (sequence.IsCyclic != Sequence.DefaultIsCyclic) + { + stringBuilder + .AppendLine() + .Append(".IsCyclic()"); + } + } + + stringBuilder.AppendLine(";"); + } + /// /// Generates code for objects. /// diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index 7ba24ef6c2d..e525186dcdd 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -242,6 +242,7 @@ private IEnumerable GetAnnotationNamespaces(IEnumerable it CoreAnnotationNames.DefiningQuery, CoreAnnotationNames.QueryFilter, RelationalAnnotationNames.CheckConstraints, + RelationalAnnotationNames.Sequences, RelationalAnnotationNames.Tables, RelationalAnnotationNames.TableMappings, RelationalAnnotationNames.TableColumnMappings, @@ -252,7 +253,7 @@ private IEnumerable GetAnnotationNamespaces(IEnumerable it var ignoredAnnotationTypes = new List { - RelationalAnnotationNames.DbFunction, RelationalAnnotationNames.SequencePrefix + RelationalAnnotationNames.DbFunction }; return items.SelectMany( diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index 7be05d1a4e6..7aea5fd4d2e 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Migrations.Internal @@ -61,6 +60,7 @@ public virtual IModel Process(IModel model) if (version != null) { ProcessElement(model, version); + UpdateSequences(model, version); foreach (var entityType in model.GetEntityTypes()) { @@ -141,6 +141,34 @@ private void ProcessElement(IAnnotatable metadata, string version) } } + private void UpdateSequences(IModel model, string version) + { + if ((!version.StartsWith("1.", StringComparison.Ordinal) + && !version.StartsWith("2.", StringComparison.Ordinal) + && !version.StartsWith("3.", StringComparison.Ordinal)) + || !(model is IMutableModel mutableModel)) + { + return; + } + + var sequences = model.GetAnnotations() +#pragma warning disable CS0618 // Type or member is obsolete + .Where(a => a.Name.StartsWith(RelationalAnnotationNames.SequencePrefix, StringComparison.Ordinal)) + .Select(a => new Sequence(model, a.Name)); +#pragma warning restore CS0618 // Type or member is obsolete + + var sequencesDictionary = new SortedDictionary<(string, string), Sequence>(); + foreach (var sequence in sequences) + { + sequencesDictionary[(sequence.Name, sequence.Schema)] = sequence; + } + + if (sequencesDictionary.Count > 0) + { + mutableModel[RelationalAnnotationNames.Sequences] = sequencesDictionary; + } + } + private void UpdateOwnedTypes(IMutableEntityType entityType) { var ownerships = entityType.GetDeclaredReferencingForeignKeys().Where(fk => fk.IsOwnership && fk.IsUnique) diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 18421a9900e..3800b6f7477 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -263,15 +263,13 @@ protected virtual void GenerateOnModelCreating( RemoveAnnotation(ref annotations, ChangeDetector.SkipDetectChangesAnnotation); RemoveAnnotation(ref annotations, RelationalAnnotationNames.MaxIdentifierLength); RemoveAnnotation(ref annotations, RelationalAnnotationNames.CheckConstraints); + RemoveAnnotation(ref annotations, RelationalAnnotationNames.Sequences); RemoveAnnotation(ref annotations, RelationalAnnotationNames.Tables); RemoveAnnotation(ref annotations, RelationalAnnotationNames.Views); RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DatabaseName); RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.EntityTypeErrors); var annotationsToRemove = new List(); - annotationsToRemove.AddRange( - annotations.Where( - a => a.Name.StartsWith(RelationalAnnotationNames.SequencePrefix, StringComparison.Ordinal))); var lines = new List(); diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index 60a7d2f4ef5..0ff936a6119 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -449,29 +449,28 @@ public static bool IsIgnoredByMigrations([NotNull] this IEntityType entityType) return entityType.BaseType.IsIgnoredByMigrations(); } - if (entityType.GetDefiningQuery() != null) + if (entityType.GetTableName() != null) { - return true; + return false; } if (entityType.FindAnnotation(RelationalAnnotationNames.QueryableFunctionResultType) != null) return true; var viewDefinition = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition); - if (viewDefinition == null) + if (viewDefinition?.Value != null) { - var ownership = entityType.FindOwnership(); - if (ownership != null - && ownership.IsUnique - && entityType.FindAnnotation(RelationalAnnotationNames.TableName) == null) - { - return ownership.PrincipalEntityType.IsIgnoredByMigrations(); - } - return false; } - return viewDefinition.Value == null; + var ownership = entityType.FindOwnership(); + if (ownership != null + && ownership.IsUnique) + { + return ownership.PrincipalEntityType.IsIgnoredByMigrations(); + } + + return true; } } } diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index 4666e7bf083..c32f12aefb2 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -149,12 +149,8 @@ public static void SetMaxIdentifierLength([NotNull] this IConventionModel model, /// the given schema was found. /// public static ISequence FindSequence([NotNull] this IModel model, [NotNull] string name, [CanBeNull] string schema = null) - { - Check.NotEmpty(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - - return Sequence.FindSequence(model, name, schema); - } + => Sequence.FindSequence( + Check.NotNull(model, nameof(model)), Check.NotEmpty(name, nameof(name)), Check.NullButNotEmpty(schema, nameof(schema))); /// /// Finds an with the given name. @@ -194,7 +190,7 @@ public static IConventionSequence FindSequence( /// The sequence. public static IMutableSequence AddSequence( [NotNull] this IMutableModel model, [NotNull] string name, [CanBeNull] string schema = null) - => new Sequence(model, name, schema, ConfigurationSource.Explicit); + => Sequence.AddSequence(model, name, schema, ConfigurationSource.Explicit); /// /// Either returns the existing with the given name in the given schema @@ -207,7 +203,7 @@ public static IMutableSequence AddSequence( /// The sequence. public static IConventionSequence AddSequence( [NotNull] this IConventionModel model, [NotNull] string name, [CanBeNull] string schema = null, bool fromDataAnnotation = false) - => new Sequence( + => Sequence.AddSequence( (IMutableModel)model, name, schema, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -223,7 +219,7 @@ public static IConventionSequence AddSequence( /// public static IMutableSequence RemoveSequence( [NotNull] this IMutableModel model, [NotNull] string name, [CanBeNull] string schema = null) - => (IMutableSequence)Sequence.RemoveSequence(model, name, schema); + => Sequence.RemoveSequence(model, name, schema); /// /// Removes the with the given name. @@ -243,22 +239,22 @@ public static IConventionSequence RemoveSequence( /// Returns all s contained in the model. /// /// The model to get the sequences in. - public static IReadOnlyList GetSequences([NotNull] this IModel model) - => Sequence.GetSequences(model, RelationalAnnotationNames.SequencePrefix).ToList(); + public static IEnumerable GetSequences([NotNull] this IModel model) + => Sequence.GetSequences(model); /// /// Returns all s contained in the model. /// /// The model to get the sequences in. - public static IReadOnlyList GetSequences([NotNull] this IMutableModel model) - => (IReadOnlyList)((IModel)model).GetSequences(); + public static IEnumerable GetSequences([NotNull] this IMutableModel model) + => (IEnumerable)((IModel)model).GetSequences(); /// /// Returns all s contained in the model. /// /// The model to get the sequences in. - public static IReadOnlyList GetSequences([NotNull] this IConventionModel model) - => (IReadOnlyList)((IModel)model).GetSequences(); + public static IEnumerable GetSequences([NotNull] this IConventionModel model) + => (IEnumerable)((IModel)model).GetSequences(); /// /// Finds a that is mapped to the method represented by the given . diff --git a/src/EFCore.Relational/Metadata/Internal/Sequence.cs b/src/EFCore.Relational/Metadata/Internal/Sequence.cs index 948bc27f96a..f88dde18f3b 100644 --- a/src/EFCore.Relational/Metadata/Internal/Sequence.cs +++ b/src/EFCore.Relational/Metadata/Internal/Sequence.cs @@ -22,7 +22,23 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal public class Sequence : IMutableSequence, IConventionSequence { private readonly IModel _model; - private readonly string _annotationName; + + private string _name { get; set; } + private string _schema { get; set; } + private long? _startValue { get; set; } + private int? _incrementBy { get; set; } + private long? _minValue { get; set; } + private long? _maxValue { get; set; } + private Type _clrType { get; set; } + private bool? _isCyclic { get; set; } + + private ConfigurationSource _configurationSource; + private ConfigurationSource? _startValueConfigurationSource; + private ConfigurationSource? _incrementByConfigurationSource; + private ConfigurationSource? _minValueConfigurationSource; + private ConfigurationSource? _maxValueConfigurationSource; + private ConfigurationSource? _clrTypeConfigurationSource; + private ConfigurationSource? _isCyclicConfigurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -88,28 +104,9 @@ public Sequence( Check.NullButNotEmpty(schema, nameof(schema)); _model = model; - _annotationName = BuildSequenceAnnotationName(RelationalAnnotationNames.SequencePrefix, name, schema); - - var data = new SequenceData - { - Name = name, - Schema = schema, - ClrType = DefaultClrType, - IncrementBy = DefaultIncrementBy, - StartValue = DefaultStartValue - }.Serialize(); - - if (configurationSource == ConfigurationSource.Explicit) - { - model.AddAnnotation(_annotationName, data); - } - else - { - ((IConventionModel)model).AddAnnotation( - _annotationName, - data, - configurationSource == ConfigurationSource.DataAnnotation); - } + _name = name; + _schema = schema; + _configurationSource = configurationSource; } /// @@ -118,13 +115,24 @@ public Sequence( /// 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. /// + [Obsolete("Use the other constructor")] public Sequence([NotNull] IModel model, [NotNull] string annotationName) { Check.NotNull(model, nameof(model)); Check.NotEmpty(annotationName, nameof(annotationName)); _model = model; - _annotationName = annotationName; + _configurationSource = ConfigurationSource.Explicit; + + var data = SequenceData.Deserialize((string)model[annotationName]); + _name = data.Name; + _schema = data.Schema; + _startValue = data.StartValue; + _incrementBy = data.IncrementBy; + _minValue = data.MinValue; + _maxValue = data.MaxValue; + _clrType = data.ClrType; + _isCyclic = data.IsCyclic; } /// @@ -133,14 +141,67 @@ public Sequence([NotNull] IModel model, [NotNull] string annotationName) /// 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 IEnumerable GetSequences([NotNull] IModel model, [NotNull] string annotationPrefix) + public static IEnumerable GetSequences([NotNull] IModel model) + => ((SortedDictionary<(string, string), Sequence>)model[RelationalAnnotationNames.Sequences]) + ?.Values ?? Enumerable.Empty(); + + /// + /// 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 FindSequence([NotNull] IModel model, [NotNull] string name, [CanBeNull] string schema) { - Check.NotNull(model, nameof(model)); - Check.NotEmpty(annotationPrefix, nameof(annotationPrefix)); + var sequences = (SortedDictionary<(string, string), Sequence>)model[RelationalAnnotationNames.Sequences]; + if (sequences == null + || !sequences.TryGetValue((name, schema), out var sequence)) + { + return null; + } + + return sequence; + } - return model.GetAnnotations() - .Where(a => a.Name.StartsWith(annotationPrefix, StringComparison.Ordinal)) - .Select(a => new Sequence(model, a.Name)); + /// + /// 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 AddSequence( + [NotNull] IMutableModel model, [NotNull] string name, [CanBeNull] string schema, ConfigurationSource configurationSource) + { + var sequence = new Sequence(model, name, schema, configurationSource); + var sequences = (SortedDictionary<(string, string), Sequence>)model[RelationalAnnotationNames.Sequences]; + if (sequences == null) + { + sequences = new SortedDictionary<(string, string), Sequence>(); + model[RelationalAnnotationNames.Sequences] = sequences; + } + + sequences.Add((name, 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 + /// 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 IMutableSequence RemoveSequence([NotNull] IMutableModel model, [NotNull] string name, [CanBeNull] string schema) + { + var sequences = (SortedDictionary<(string, string), Sequence>)model[RelationalAnnotationNames.Sequences]; + if (sequences == null + || !sequences.TryGetValue((name, schema), out var sequence)) + { + return null; + } + + sequences.Remove((name, schema)); + + return sequence; } /// @@ -165,7 +226,7 @@ public static IEnumerable GetSequences([NotNull] IModel model, [NotNul /// 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 => GetData().Name; + public virtual string Name => _name; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -173,7 +234,7 @@ public static IEnumerable GetSequences([NotNull] IModel model, [NotNul /// 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 Schema => GetData().Schema ?? Model.GetDefaultSchema(); + public virtual string Schema => _schema ?? Model.GetDefaultSchema(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -183,7 +244,7 @@ public static IEnumerable GetSequences([NotNull] IModel model, [NotNul /// public virtual long StartValue { - get => GetData().StartValue; + get => _startValue ?? DefaultStartValue; set => SetStartValue(value, ConfigurationSource.Explicit); } @@ -195,9 +256,11 @@ public virtual long StartValue /// public virtual void SetStartValue(long? startValue, ConfigurationSource configurationSource) { - var data = GetData(); - data.StartValue = startValue ?? DefaultStartValue; - SetData(data); + _startValue = startValue; + + _startValueConfigurationSource = startValue == null + ? (ConfigurationSource?)null + : configurationSource.Max(_startValueConfigurationSource); } /// @@ -207,7 +270,7 @@ public virtual void SetStartValue(long? startValue, ConfigurationSource configur /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource? GetStartValueConfigurationSource() - => ConfigurationSource.Explicit; + => _startValueConfigurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -217,7 +280,7 @@ public virtual void SetStartValue(long? startValue, ConfigurationSource configur /// public virtual int IncrementBy { - get => GetData().IncrementBy; + get => _incrementBy ?? DefaultIncrementBy; set => SetIncrementBy(value, ConfigurationSource.Explicit); } @@ -229,9 +292,11 @@ public virtual int IncrementBy /// public virtual void SetIncrementBy(int? incrementBy, ConfigurationSource configurationSource) { - var data = GetData(); - data.IncrementBy = incrementBy ?? DefaultIncrementBy; - SetData(data); + _incrementBy = incrementBy; + + _incrementByConfigurationSource = incrementBy == null + ? (ConfigurationSource?)null + : configurationSource.Max(_incrementByConfigurationSource); } /// @@ -241,7 +306,7 @@ public virtual void SetIncrementBy(int? incrementBy, ConfigurationSource configu /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource? GetIncrementByConfigurationSource() - => ConfigurationSource.Explicit; + => _incrementByConfigurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -251,7 +316,7 @@ public virtual void SetIncrementBy(int? incrementBy, ConfigurationSource configu /// public virtual long? MinValue { - get => GetData().MinValue; + get => _minValue ?? DefaultMinValue; set => SetMinValue(value, ConfigurationSource.Explicit); } @@ -263,9 +328,11 @@ public virtual long? MinValue /// public virtual void SetMinValue(long? minValue, ConfigurationSource configurationSource) { - var data = GetData(); - data.MinValue = minValue ?? DefaultMinValue; - SetData(data); + _minValue = minValue; + + _minValueConfigurationSource = minValue == null + ? (ConfigurationSource?)null + : configurationSource.Max(_minValueConfigurationSource); } /// @@ -275,7 +342,7 @@ public virtual void SetMinValue(long? minValue, ConfigurationSource configuratio /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource? GetMinValueConfigurationSource() - => ConfigurationSource.Explicit; + => _minValueConfigurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -285,7 +352,7 @@ public virtual void SetMinValue(long? minValue, ConfigurationSource configuratio /// public virtual long? MaxValue { - get => GetData().MaxValue; + get => _maxValue; set => SetMaxValue(value, ConfigurationSource.Explicit); } @@ -297,9 +364,11 @@ public virtual long? MaxValue /// public virtual void SetMaxValue(long? maxValue, ConfigurationSource configurationSource) { - var data = GetData(); - data.MaxValue = maxValue ?? DefaultMaxValue; - SetData(data); + _maxValue = maxValue; + + _maxValueConfigurationSource = maxValue == null + ? (ConfigurationSource?)null + : configurationSource.Max(_maxValueConfigurationSource); } /// @@ -309,7 +378,7 @@ public virtual void SetMaxValue(long? maxValue, ConfigurationSource configuratio /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource? GetMaxValueConfigurationSource() - => ConfigurationSource.Explicit; + => _maxValueConfigurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -328,7 +397,7 @@ public virtual void SetMaxValue(long? maxValue, ConfigurationSource configuratio /// public virtual Type ClrType { - get => GetData().ClrType; + get => _clrType ?? DefaultClrType; set => SetClrType(value, ConfigurationSource.Explicit); } @@ -340,15 +409,17 @@ public virtual Type ClrType /// public virtual void SetClrType([CanBeNull] Type clrType, ConfigurationSource configurationSource) { - clrType ??= DefaultClrType; - if (!SupportedTypes.Contains(clrType)) + if (clrType != null + && !SupportedTypes.Contains(clrType)) { throw new ArgumentException(RelationalStrings.BadSequenceType); } - var data = GetData(); - data.ClrType = clrType; - SetData(data); + _clrType = clrType; + + _clrTypeConfigurationSource = clrType == null + ? (ConfigurationSource?)null + : configurationSource.Max(_clrTypeConfigurationSource); } /// @@ -358,7 +429,7 @@ public virtual void SetClrType([CanBeNull] Type clrType, ConfigurationSource con /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource? GetClrTypeConfigurationSource() - => ConfigurationSource.Explicit; + => _clrTypeConfigurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -368,7 +439,7 @@ public virtual void SetClrType([CanBeNull] Type clrType, ConfigurationSource con /// public virtual bool IsCyclic { - get => GetData().IsCyclic; + get => _isCyclic ?? DefaultIsCyclic; set => SetIsCyclic(value, ConfigurationSource.Explicit); } @@ -380,9 +451,11 @@ public virtual bool IsCyclic /// public virtual void SetIsCyclic(bool? cyclic, ConfigurationSource configurationSource) { - var data = GetData(); - data.IsCyclic = cyclic ?? DefaultIsCyclic; - SetData(data); + _isCyclic = cyclic; + + _isCyclicConfigurationSource = cyclic == null + ? (ConfigurationSource?)null + : configurationSource.Max(_isCyclicConfigurationSource); } /// @@ -392,7 +465,7 @@ public virtual void SetIsCyclic(bool? cyclic, ConfigurationSource configurationS /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource? GetIsCyclicConfigurationSource() - => ConfigurationSource.Explicit; + => _isCyclicConfigurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -401,7 +474,7 @@ public virtual void SetIsCyclic(bool? cyclic, ConfigurationSource configurationS /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource GetConfigurationSource() - => ((IConventionModel)Model).FindAnnotation(_annotationName).GetConfigurationSource(); + => _configurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -410,14 +483,7 @@ public virtual ConfigurationSource GetConfigurationSource() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource) - => ((Model)Model).FindAnnotation(_annotationName).UpdateConfigurationSource(configurationSource); - - private SequenceData GetData() => SequenceData.Deserialize((string)Model[_annotationName]); - - private void SetData(SequenceData data) - { - Model[_annotationName] = data.Serialize(); - } + => _configurationSource = _configurationSource.Max(configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -427,37 +493,6 @@ private void SetData(SequenceData data) /// IConventionModel IConventionSequence.Model => (IConventionModel)Model; - /// - /// 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 ISequence FindSequence([NotNull] IModel model, [NotNull] string name, [CanBeNull] string schema) - { - var annotationName = BuildSequenceAnnotationName(RelationalAnnotationNames.SequencePrefix, name, schema); - - return model[annotationName] == null ? null : new Sequence(model, annotationName); - } - - /// - /// 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 ISequence RemoveSequence([NotNull] IMutableModel model, [NotNull] string name, [CanBeNull] string schema = null) - { - Check.NotEmpty(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - - var annotationName = BuildSequenceAnnotationName(RelationalAnnotationNames.SequencePrefix, name, schema); - return model.RemoveAnnotation(annotationName) == null ? null : new Sequence(model, annotationName); - } - - private static string BuildSequenceAnnotationName(string annotationPrefix, string name, string schema) - => annotationPrefix + schema + "." + name; - /// /// 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 @@ -520,6 +555,7 @@ void IConventionSequence.SetClrType(Type clrType, bool fromDataAnnotation) void IConventionSequence.SetIsCyclic(bool? cyclic, bool fromDataAnnotation) => SetIsCyclic(cyclic, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + [Obsolete("Don't use this in any new code")] private sealed class SequenceData { public string Name { get; set; } @@ -538,29 +574,6 @@ private sealed class SequenceData public bool IsCyclic { get; set; } - public string Serialize() - { - var builder = new StringBuilder(); - - EscapeAndQuote(builder, Name); - builder.Append(", "); - EscapeAndQuote(builder, Schema); - builder.Append(", "); - EscapeAndQuote(builder, StartValue); - builder.Append(", "); - EscapeAndQuote(builder, IncrementBy); - builder.Append(", "); - EscapeAndQuote(builder, MinValue); - builder.Append(", "); - EscapeAndQuote(builder, MaxValue); - builder.Append(", "); - EscapeAndQuote(builder, ClrType.Name); - builder.Append(", "); - EscapeAndQuote(builder, IsCyclic); - - return builder.ToString(); - } - public static SequenceData Deserialize([NotNull] string value) { Check.NotEmpty(value, nameof(value)); diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index d0da9d806a3..0dd7869292a 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -1,6 +1,8 @@ // 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; + namespace Microsoft.EntityFrameworkCore.Metadata { /// @@ -82,8 +84,14 @@ public static class RelationalAnnotationNames /// /// The prefix for serialized sequence annotations. /// + [Obsolete("All sequences are stored in a single annotation now")] public const string SequencePrefix = Prefix + "Sequence:"; + /// + /// The name for sequence annotation. + /// + public const string Sequences = Prefix + "Sequences"; + /// /// The name for check constraint annotations. /// @@ -95,7 +103,7 @@ public static class RelationalAnnotationNames public const string Filter = Prefix + "Filter"; /// - /// The name for DbFunction annotations. + /// The name for DbFunction annotation. /// public const string DbFunction = Prefix + "DbFunction"; diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index e6bb2d46d32..3013df6e679 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -161,7 +161,8 @@ public virtual void SetIsNullable(bool? nullable, ConfigurationSource configurat } } - UpdateIsNullableConfigurationSource(configurationSource); + _isNullableConfigurationSource = configurationSource.Max(_isNullableConfigurationSource); + _isNullable = nullable; if (isChanging) @@ -180,9 +181,6 @@ public virtual void SetIsNullable(bool? nullable, ConfigurationSource configurat /// public virtual ConfigurationSource? GetIsNullableConfigurationSource() => _isNullableConfigurationSource; - private void UpdateIsNullableConfigurationSource(ConfigurationSource configurationSource) - => _isNullableConfigurationSource = configurationSource.Max(_isNullableConfigurationSource); - /// /// 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 @@ -214,14 +212,9 @@ public virtual void SetValueGenerated(ValueGenerated? valueGenerated, Configurat { _valueGenerated = valueGenerated; - if (valueGenerated == null) - { - _valueGeneratedConfigurationSource = null; - } - else - { - UpdateValueGeneratedConfigurationSource(configurationSource); - } + _valueGeneratedConfigurationSource = valueGenerated == null + ? (ConfigurationSource?)null + : configurationSource.Max(_valueGeneratedConfigurationSource); } private static ValueGenerated DefaultValueGenerated => ValueGenerated.Never; @@ -234,9 +227,6 @@ public virtual void SetValueGenerated(ValueGenerated? valueGenerated, Configurat /// public virtual ConfigurationSource? GetValueGeneratedConfigurationSource() => _valueGeneratedConfigurationSource; - private void UpdateValueGeneratedConfigurationSource(ConfigurationSource configurationSource) - => _valueGeneratedConfigurationSource = configurationSource.Max(_valueGeneratedConfigurationSource); - /// /// 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 @@ -262,14 +252,9 @@ public virtual void SetIsConcurrencyToken(bool? concurrencyToken, ConfigurationS _isConcurrencyToken = concurrencyToken; } - if (concurrencyToken == null) - { - _isConcurrencyTokenConfigurationSource = null; - } - else - { - UpdateIsConcurrencyTokenConfigurationSource(configurationSource); - } + _isConcurrencyTokenConfigurationSource = concurrencyToken == null + ? (ConfigurationSource?)null + : configurationSource.Max(_isConcurrencyTokenConfigurationSource); } private static bool DefaultIsConcurrencyToken => false; @@ -282,9 +267,6 @@ public virtual void SetIsConcurrencyToken(bool? concurrencyToken, ConfigurationS /// public virtual ConfigurationSource? GetIsConcurrencyTokenConfigurationSource() => _isConcurrencyTokenConfigurationSource; - private void UpdateIsConcurrencyTokenConfigurationSource(ConfigurationSource configurationSource) - => _isConcurrencyTokenConfigurationSource = configurationSource.Max(_isConcurrencyTokenConfigurationSource); - /// /// 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 diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 35a2480e16e..af7ac45e953 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -61,11 +61,13 @@ public void Test_new_annotations_handled_for_entity_types() CoreAnnotationNames.DuplicateServiceProperties, RelationalAnnotationNames.ColumnName, RelationalAnnotationNames.ColumnType, + RelationalAnnotationNames.TableColumnMappings, + RelationalAnnotationNames.ViewColumnMappings, RelationalAnnotationNames.DefaultValueSql, RelationalAnnotationNames.ComputedColumnSql, RelationalAnnotationNames.DefaultValue, RelationalAnnotationNames.Name, - RelationalAnnotationNames.SequencePrefix, + RelationalAnnotationNames.Sequences, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.DefaultSchema, RelationalAnnotationNames.Filter, @@ -145,10 +147,14 @@ public void Test_new_annotations_handled_for_properties() CoreAnnotationNames.AmbiguousNavigations, CoreAnnotationNames.DuplicateServiceProperties, RelationalAnnotationNames.TableName, + RelationalAnnotationNames.ViewName, RelationalAnnotationNames.Schema, + RelationalAnnotationNames.ViewSchema, RelationalAnnotationNames.DefaultSchema, + RelationalAnnotationNames.TableMappings, + RelationalAnnotationNames.ViewMappings, RelationalAnnotationNames.Name, - RelationalAnnotationNames.SequencePrefix, + RelationalAnnotationNames.Sequences, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Filter, RelationalAnnotationNames.DbFunction, diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index 6582c29197d..4466f5b348a 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -192,10 +192,30 @@ public void Can_diff_against_older_ownership_model(Type snapshotType) Assert.Empty(differences); } + [ConditionalTheory] + [InlineData(typeof(SequenceModelSnapshot1_1))] + [InlineData(typeof(SequenceModelSnapshot2_2))] + [InlineData(typeof(SequenceModelSnapshot3_1))] + public void Can_diff_against_older_sequence_model(Type snapshotType) + { + using var context = new SequenceContext(); + var differ = context.GetService(); + var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType); + var reporter = new TestOperationReporter(); + var processor = new SnapshotModelProcessor(reporter); + + var differences = differ.GetDifferences(processor.Process(snapshot.Model), context.Model); + + Assert.Empty(differences); + } + private void AddAnnotations(IMutableAnnotatable element) { foreach (var annotationName in GetAnnotationNames() - .Where(a => a != RelationalAnnotationNames.MaxIdentifierLength) + .Where(a => a != RelationalAnnotationNames.MaxIdentifierLength +#pragma warning disable CS0618 // Type or member is obsolete + && a != RelationalAnnotationNames.SequencePrefix) +#pragma warning restore CS0618 // Type or member is obsolete .Select(a => "Unicorn" + a.Substring(RelationalAnnotationNames.Prefix.Length - 1))) { element[annotationName] = "Value"; @@ -211,7 +231,10 @@ private void AssertAnnotations(IMutableAnnotatable element) && a != RelationalAnnotationNames.TableColumnMappings && a != RelationalAnnotationNames.Views && a != RelationalAnnotationNames.ViewMappings - && a != RelationalAnnotationNames.ViewColumnMappings)) + && a != RelationalAnnotationNames.ViewColumnMappings +#pragma warning disable CS0618 // Type or member is obsolete + && a != RelationalAnnotationNames.SequencePrefix)) +#pragma warning restore CS0618 // Type or member is obsolete { Assert.Equal("Value", (string)element[annotationName]); } @@ -1163,6 +1186,63 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning restore 612, 618 } } + + private class SequenceModelSnapshot1_1 : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ChangeDetector.SkipDetectChanges", "true") + .HasAnnotation("ProductVersion", "1.1.6") + .HasAnnotation("Relational:Sequence:Bar.Foo", "'Foo', 'Bar', '2', '2', '1', '3', 'Int32', 'True'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + } + } + + private class SequenceModelSnapshot2_2 : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ChangeDetector.SkipDetectChanges", "true") + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("Relational:Sequence:Bar.Foo", "'Foo', 'Bar', '2', '2', '1', '3', 'Int32', 'True'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); +#pragma warning restore 612, 618 + } + } + + private class SequenceModelSnapshot3_1 : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("Relational:Sequence:Bar.Foo", "'Foo', 'Bar', '2', '2', '1', '3', 'Int32', 'True'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); +#pragma warning restore 612, 618 + } + } + + private class SequenceContext : DbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Ownership"); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.HasSequence("Foo", "Bar") + .StartsAt(2) + .HasMin(1) + .HasMax(3) + .IncrementsBy(2) + .IsCyclic(); + } + } } } diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 70181284111..5b395a2009e 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -332,31 +332,6 @@ public void Views_are_ignored() } [ConditionalFact] - public virtual void Sequence_is_stored_in_snapshot_as_annotations() - { - Test( - builder => - { - builder.HasSequence("Foo", "Bar") - .StartsAt(2) - .HasMin(1) - .HasMax(3) - .IncrementsBy(2) - .IsCyclic(); - }, - AddBoilerPlate( - @" - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""Relational:Sequence:Bar.Foo"", ""'Foo', 'Bar', '2', '2', '1', '3', 'Int32', 'True'"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), - o => - { - Assert.Equal(3, o.GetAnnotations().Count()); - }); - } - - [ConditionalFact(Skip = "Issue #14103")] public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() { Test( @@ -370,15 +345,14 @@ public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() .IsCyclic(); }, AddBoilerPlate( - @" - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasSequence(""Foo"", ""Bar"") - .StartsAt(2) - .HasMin(1) - .HasMax(3) - .IncrementsBy(2) - .IsCyclic();"), + GetHeading() + + @" + modelBuilder.HasSequence(""Foo"", ""Bar"") + .StartsAt(2L) + .IncrementsBy(2) + .HasMin(1L) + .HasMax(3L) + .IsCyclic();"), o => { Assert.Equal(3, o.GetAnnotations().Count()); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs index 5c49a782a8f..2c0a3698ba8 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs @@ -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.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -583,7 +584,7 @@ public void Can_get_multiple_sequences() var sequences = model.GetSequences(); - Assert.Equal(2, sequences.Count); + Assert.Equal(2, sequences.Count()); Assert.Contains(sequences, s => s.Name == "Fibonacci"); Assert.Contains(sequences, s => s.Name == "Golomb"); } diff --git a/test/EFCore.Relational.Tests/Metadata/SequenceTest.cs b/test/EFCore.Relational.Tests/Metadata/SequenceTest.cs index 1c58be9a6d3..b0f4adcc3cf 100644 --- a/test/EFCore.Relational.Tests/Metadata/SequenceTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/SequenceTest.cs @@ -13,7 +13,9 @@ public class SequenceTest [ConditionalFact] public void Can_be_created_with_default_values() { - var sequence = ((IMutableModel)new Model()).AddSequence("Foo"); + IMutableModel model = new Model(); + + var sequence = model.AddSequence("Foo"); Assert.Equal("Foo", sequence.Name); Assert.Null(sequence.Schema); @@ -22,17 +24,37 @@ public void Can_be_created_with_default_values() Assert.Null(sequence.MinValue); Assert.Null(sequence.MaxValue); Assert.Same(typeof(long), sequence.ClrType); + Assert.False(sequence.IsCyclic); + Assert.Same(model, sequence.Model); + + model.SetDefaultSchema("db0"); + + Assert.Equal("db0", sequence.Schema); + + var conventionSequence = (IConventionSequence)sequence; + Assert.Equal(ConfigurationSource.Explicit, conventionSequence.GetConfigurationSource()); + Assert.Null(conventionSequence.GetIncrementByConfigurationSource()); + Assert.Null(conventionSequence.GetStartValueConfigurationSource()); + Assert.Null(conventionSequence.GetMinValueConfigurationSource()); + Assert.Null(conventionSequence.GetMaxValueConfigurationSource()); + Assert.Null(conventionSequence.GetClrTypeConfigurationSource()); + Assert.Null(conventionSequence.GetIsCyclicConfigurationSource()); } [ConditionalFact] public void Can_be_created_with_specified_values() { - var sequence = new Model().AddSequence("Foo", "Smoo"); + IMutableModel model = new Model(); + + model.SetDefaultSchema("db0"); + + var sequence = model.AddSequence("Foo", "Smoo"); sequence.StartValue = 1729; sequence.IncrementBy = 11; sequence.MinValue = 2001; sequence.MaxValue = 2010; sequence.ClrType = typeof(int); + sequence.IsCyclic = true; Assert.Equal("Foo", sequence.Name); Assert.Equal("Smoo", sequence.Schema); @@ -41,6 +63,15 @@ public void Can_be_created_with_specified_values() Assert.Equal(2001, sequence.MinValue); Assert.Equal(2010, sequence.MaxValue); Assert.Same(typeof(int), sequence.ClrType); + + var conventionSequence = (IConventionSequence)sequence; + Assert.Equal(ConfigurationSource.Explicit, conventionSequence.GetConfigurationSource()); + Assert.Equal(ConfigurationSource.Explicit, conventionSequence.GetIncrementByConfigurationSource()); + Assert.Equal(ConfigurationSource.Explicit, conventionSequence.GetStartValueConfigurationSource()); + Assert.Equal(ConfigurationSource.Explicit, conventionSequence.GetMinValueConfigurationSource()); + Assert.Equal(ConfigurationSource.Explicit, conventionSequence.GetMaxValueConfigurationSource()); + Assert.Equal(ConfigurationSource.Explicit, conventionSequence.GetClrTypeConfigurationSource()); + Assert.Equal(ConfigurationSource.Explicit, conventionSequence.GetIsCyclicConfigurationSource()); } [ConditionalFact] @@ -63,120 +94,5 @@ public void Can_only_be_created_for_byte_short_int_and_long_decimal() Assert.Throws( () => sequence.ClrType = typeof(bool)).Message); } - - [ConditionalFact] - public void Can_get_model() - { - IMutableModel model = new Model(); - - var sequence = model.AddSequence("Foo"); - - Assert.Same(model, sequence.Model); - } - - [ConditionalFact] - public void Can_get_model_default_schema_if_sequence_schema_not_specified() - { - IMutableModel model = new Model(); - - var sequence = model.AddSequence("Foo"); - - Assert.Null(sequence.Schema); - - model.SetDefaultSchema("db0"); - - Assert.Equal("db0", sequence.Schema); - } - - [ConditionalFact] - public void Can_get_sequence_schema_if_specified_explicitly() - { - IMutableModel model = new Model(); - - model.SetDefaultSchema("db0"); - var sequence = model.AddSequence("Foo", "db1"); - - Assert.Equal("db1", sequence.Schema); - } - - [ConditionalFact] - public void Can_serialize_and_deserialize() - { - IMutableModel model = new Model(); - var sequence = model.AddSequence("Foo", "Smoo"); - sequence.StartValue = 1729; - sequence.IncrementBy = 11; - sequence.MinValue = 2001; - sequence.MaxValue = 2010; - sequence.ClrType = typeof(int); - - model.FindSequence("Foo", "Smoo"); - - Assert.Equal("Foo", sequence.Name); - Assert.Equal("Smoo", sequence.Schema); - Assert.Equal(11, sequence.IncrementBy); - Assert.Equal(1729, sequence.StartValue); - Assert.Equal(2001, sequence.MinValue); - Assert.Equal(2010, sequence.MaxValue); - Assert.Same(typeof(int), sequence.ClrType); - } - - [ConditionalFact] - public void Can_serialize_and_deserialize_with_defaults() - { - IMutableModel model = new Model(); - model.AddSequence("Foo"); - - var sequence = model.FindSequence("Foo"); - - Assert.Equal("Foo", sequence.Name); - Assert.Null(sequence.Schema); - Assert.Equal(1, sequence.IncrementBy); - Assert.Equal(1, sequence.StartValue); - Assert.Null(sequence.MinValue); - Assert.Null(sequence.MaxValue); - Assert.Same(typeof(long), sequence.ClrType); - } - - [ConditionalFact] - public void Can_serialize_and_deserialize_with_funky_names() - { - IMutableModel model = new Model(); - var sequence = model.AddSequence("'Foo'", "''S'''m'oo'''"); - sequence.StartValue = 1729; - sequence.IncrementBy = 11; - sequence.ClrType = typeof(int); - - sequence = model.FindSequence("'Foo'", "''S'''m'oo'''"); - - Assert.Equal("'Foo'", sequence.Name); - Assert.Equal("''S'''m'oo'''", sequence.Schema); - Assert.Equal(11, sequence.IncrementBy); - Assert.Equal(1729, sequence.StartValue); - Assert.Null(sequence.MinValue); - Assert.Null(sequence.MaxValue); - Assert.Same(typeof(int), sequence.ClrType); - } - - [ConditionalFact] - public void Throws_on_bad_serialized_form() - { - IMutableModel model = new Model(); - var sequence = model.AddSequence("Foo", "Smoo"); - sequence.StartValue = 1729; - sequence.IncrementBy = 11; - sequence.MinValue = 2001; - sequence.MaxValue = 2010; - sequence.ClrType = typeof(int); - - var annotationName = RelationalAnnotationNames.SequencePrefix + "Smoo.Foo"; - - model[annotationName] = ((string)model[annotationName]).Replace("1", "Z"); - - Assert.Equal( - RelationalStrings.BadSequenceString, - Assert.Throws( - () => model.FindSequence("Foo", "Smoo").ClrType).Message); - } } } diff --git a/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerValueGenerationStrategyConventionTest.cs b/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerValueGenerationStrategyConventionTest.cs index bfbe7dfc6f0..def2b65d273 100644 --- a/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerValueGenerationStrategyConventionTest.cs +++ b/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerValueGenerationStrategyConventionTest.cs @@ -40,7 +40,7 @@ public void Annotations_are_added_when_conventional_model_builder_is_used_with_s Assert.Equal(RelationalAnnotationNames.MaxIdentifierLength, annotations[0].Name); Assert.Equal( - RelationalAnnotationNames.SequencePrefix + "." + SqlServerModelExtensions.DefaultHiLoSequenceName, + RelationalAnnotationNames.Sequences, annotations[1].Name); Assert.NotNull(annotations[1].Value); diff --git a/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataExtensionsTest.cs b/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataExtensionsTest.cs index 791f514e670..7f8fface526 100644 --- a/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataExtensionsTest.cs +++ b/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataExtensionsTest.cs @@ -190,146 +190,6 @@ public void Can_get_and_set_key_clustering() Assert.Null(key.IsClustered()); } - [ConditionalFact] - public void Can_get_and_set_sequence() - { - var modelBuilder = GetModelBuilder(); - var model = modelBuilder.Model; - - Assert.Null(model.FindSequence("Foo")); - Assert.Null(model.FindSequence("Foo")); - Assert.Null(((IModel)model).FindSequence("Foo")); - - var sequence = model.AddSequence("Foo"); - - Assert.Equal("Foo", model.FindSequence("Foo").Name); - Assert.Equal("Foo", ((IModel)model).FindSequence("Foo").Name); - Assert.Equal("Foo", model.FindSequence("Foo").Name); - Assert.Equal("Foo", ((IModel)model).FindSequence("Foo").Name); - - Assert.Equal("Foo", sequence.Name); - Assert.Null(sequence.Schema); - Assert.Equal(1, sequence.IncrementBy); - Assert.Equal(1, sequence.StartValue); - Assert.Null(sequence.MinValue); - Assert.Null(sequence.MaxValue); - Assert.Same(typeof(long), sequence.ClrType); - - Assert.NotNull(model.FindSequence("Foo")); - - var sequence2 = model.FindSequence("Foo"); - - sequence.StartValue = 1729; - sequence.IncrementBy = 11; - sequence.MinValue = 2001; - sequence.MaxValue = 2010; - sequence.ClrType = typeof(int); - - Assert.Equal("Foo", sequence.Name); - Assert.Null(sequence.Schema); - Assert.Equal(11, sequence.IncrementBy); - Assert.Equal(1729, sequence.StartValue); - Assert.Equal(2001, sequence.MinValue); - Assert.Equal(2010, sequence.MaxValue); - Assert.Same(typeof(int), sequence.ClrType); - - Assert.Equal(sequence2.Name, sequence.Name); - Assert.Equal(sequence2.Schema, sequence.Schema); - Assert.Equal(sequence2.IncrementBy, sequence.IncrementBy); - Assert.Equal(sequence2.StartValue, sequence.StartValue); - Assert.Equal(sequence2.MinValue, sequence.MinValue); - Assert.Equal(sequence2.MaxValue, sequence.MaxValue); - Assert.Same(sequence2.ClrType, sequence.ClrType); - } - - [ConditionalFact] - public void Can_get_and_set_sequence_with_schema_name() - { - var modelBuilder = GetModelBuilder(); - var model = modelBuilder.Model; - - Assert.Null(model.FindSequence("Foo", "Smoo")); - Assert.Null(model.FindSequence("Foo", "Smoo")); - Assert.Null(((IModel)model).FindSequence("Foo", "Smoo")); - - var sequence = model.AddSequence("Foo", "Smoo"); - - Assert.Equal("Foo", model.FindSequence("Foo", "Smoo").Name); - Assert.Equal("Foo", ((IModel)model).FindSequence("Foo", "Smoo").Name); - Assert.Equal("Foo", model.FindSequence("Foo", "Smoo").Name); - Assert.Equal("Foo", ((IModel)model).FindSequence("Foo", "Smoo").Name); - - Assert.Equal("Foo", sequence.Name); - Assert.Equal("Smoo", sequence.Schema); - Assert.Equal(1, sequence.IncrementBy); - Assert.Equal(1, sequence.StartValue); - Assert.Null(sequence.MinValue); - Assert.Null(sequence.MaxValue); - Assert.Same(typeof(long), sequence.ClrType); - - Assert.NotNull(model.FindSequence("Foo", "Smoo")); - - var sequence2 = model.FindSequence("Foo", "Smoo"); - - sequence.StartValue = 1729; - sequence.IncrementBy = 11; - sequence.MinValue = 2001; - sequence.MaxValue = 2010; - sequence.ClrType = typeof(int); - - Assert.Equal("Foo", sequence.Name); - Assert.Equal("Smoo", sequence.Schema); - Assert.Equal(11, sequence.IncrementBy); - Assert.Equal(1729, sequence.StartValue); - Assert.Equal(2001, sequence.MinValue); - Assert.Equal(2010, sequence.MaxValue); - Assert.Same(typeof(int), sequence.ClrType); - - Assert.Equal(sequence2.Name, sequence.Name); - Assert.Equal(sequence2.Schema, sequence.Schema); - Assert.Equal(sequence2.IncrementBy, sequence.IncrementBy); - Assert.Equal(sequence2.StartValue, sequence.StartValue); - Assert.Equal(sequence2.MinValue, sequence.MinValue); - Assert.Equal(sequence2.MaxValue, sequence.MaxValue); - Assert.Same(sequence2.ClrType, sequence.ClrType); - } - - [ConditionalFact] - public void Can_get_multiple_sequences() - { - var modelBuilder = GetModelBuilder(); - var model = modelBuilder.Model; - - model.AddSequence("Fibonacci"); - model.AddSequence("Golomb"); - - var sequences = model.GetSequences(); - - Assert.Equal(2, sequences.Count); - Assert.Contains(sequences, s => s.Name == "Fibonacci"); - Assert.Contains(sequences, s => s.Name == "Golomb"); - } - - [ConditionalFact] - public void Can_get_multiple_sequences_when_overridden() - { - var modelBuilder = GetModelBuilder(); - var model = modelBuilder.Model; - - model.AddSequence("Fibonacci").StartValue = 1; - model.FindSequence("Fibonacci").StartValue = 3; - model.AddSequence("Golomb"); - - var sequences = model.GetSequences(); - - Assert.Equal(2, sequences.Count); - Assert.Contains(sequences, s => s.Name == "Golomb"); - - var sequence = sequences.FirstOrDefault(s => s.Name == "Fibonacci"); - Assert.NotNull(sequence); - Assert.Equal(3, sequence.StartValue); - } - [ConditionalFact] public void Can_get_and_set_value_generation_on_model() {