diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 0c11b12fd87..71b224c5f57 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -334,12 +334,11 @@ protected virtual void GenerateSequence( .Append("(") .Append(Code.Literal(sequence.Name)); - if (!string.IsNullOrEmpty(sequence.Schema) - && sequence.Model.GetDefaultSchema() != sequence.Schema) + if (!string.IsNullOrEmpty(sequence.ModelSchema)) { sequenceBuilderNameBuilder .Append(", ") - .Append(Code.Literal(sequence.Schema)); + .Append(Code.Literal(sequence.ModelSchema)); } sequenceBuilderNameBuilder.Append(")"); diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index c50af70cfec..0313ecc4853 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -150,7 +150,7 @@ private static void UpdateSequences(IReadOnlyModel model, string version) var sequencesDictionary = new SortedDictionary<(string, string?), ISequence>(); foreach (var sequence in sequences) { - sequencesDictionary[(sequence.Name, sequence.Schema)] = sequence; + sequencesDictionary[(sequence.Name, sequence.ModelSchema)] = sequence; } if (sequencesDictionary.Count > 0) diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 8a254359412..ef53d095fd2 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Text; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -328,7 +329,8 @@ private string CreateModelBuilder( mainBuilder, methodBuilder, namespaces, - variables); + variables, + nullable); foreach (var typeConfiguration in model.GetTypeMappingConfigurations()) { @@ -471,7 +473,7 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str CreateSkipNavigation(navigation, navigationNumber++, mainBuilder, methodBuilder, namespaces, className, nullable); } - CreateAnnotations(entityType, mainBuilder, methodBuilder, namespaces, className); + CreateAnnotations(entityType, mainBuilder, methodBuilder, namespaces, className, nullable); } mainBuilder.AppendLine("}"); @@ -522,7 +524,8 @@ private void CreateEntityType( mainBuilder, methodBuilder, namespaces, - variables); + variables, + nullable); Create(entityType, parameters); @@ -1150,7 +1153,8 @@ private void CreateForeignKey( mainBuilder, methodBuilder, namespaces, - variables); + variables, + nullable); var navigation = foreignKey.DependentToPrincipal; if (navigation != null) @@ -1249,7 +1253,8 @@ private void CreateSkipNavigation( mainBuilder, methodBuilder, namespaces, - variables); + variables, + nullable); mainBuilder .Append("var ").Append(navigationVariable).Append(" = ") @@ -1352,7 +1357,8 @@ private void CreateAnnotations( IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, SortedSet namespaces, - string className) + string className, + bool nullable) { mainBuilder.AppendLine() .Append("public static void CreateAnnotations") @@ -1373,7 +1379,8 @@ private void CreateAnnotations( mainBuilder, methodBuilder, namespaces, - variables)); + variables, + nullable)); mainBuilder .AppendLine() diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index 0c2f8b3c52b..ff97982778b 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -68,8 +68,15 @@ public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGenerator { parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!); var sequencesVariable = Dependencies.CSharpHelper.Identifier("sequences", parameters.ScopeVariables, capitalize: false); - parameters.MainBuilder - .Append("var ").Append(sequencesVariable).AppendLine(" = new SortedDictionary<(string, string), ISequence>();"); + var mainBuilder = parameters.MainBuilder; + mainBuilder.Append("var ").Append(sequencesVariable).Append(" = new SortedDictionary<(string, string"); + + if (parameters.UseNullableReferenceTypes) + { + mainBuilder.Append("?"); + } + + mainBuilder.AppendLine("), ISequence>();"); foreach (var sequencePair in sequences) { @@ -279,6 +286,12 @@ private void Create(ISequence sequence, string sequencesVariable, CSharpRuntimeA .Append("maxValue: ").Append(code.Literal(sequence.MaxValue)); } + if (sequence.ModelSchema is null && sequence.Schema is not null) + { + mainBuilder.AppendLine(",") + .Append("modelSchemaIsNull: ").Append(code.Literal(true)); + } + mainBuilder.AppendLine(");").DecrementIndent() .AppendLine(); @@ -289,7 +302,7 @@ private void Create(ISequence sequence, string sequencesVariable, CSharpRuntimeA mainBuilder .Append(sequencesVariable).Append("[(").Append(code.Literal(sequence.Name)).Append(", ") - .Append(code.Literal(sequence.Schema)).Append(")] = ") + .Append(code.Literal(sequence.ModelSchema)).Append(")] = ") .Append(sequenceVariable).AppendLine(";") .AppendLine(); } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 4a0eafeb4c9..8195a89607d 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -303,7 +303,8 @@ private static RuntimeSequence Create(ISequence sequence, RuntimeModel runtimeMo sequence.IncrementBy, sequence.IsCyclic, sequence.MinValue, - sequence.MaxValue); + sequence.MaxValue, + sequence.ModelSchema is null); /// /// Updates the sequence annotations that will be set on the read-only object. diff --git a/src/EFCore.Relational/Metadata/IReadOnlySequence.cs b/src/EFCore.Relational/Metadata/IReadOnlySequence.cs index 487f3c20c7e..5766bf6d5aa 100644 --- a/src/EFCore.Relational/Metadata/IReadOnlySequence.cs +++ b/src/EFCore.Relational/Metadata/IReadOnlySequence.cs @@ -18,6 +18,13 @@ public interface IReadOnlySequence : IReadOnlyAnnotatable /// string Name { get; } + /// + /// Gets the model schema of the sequence. This is the one specified in + /// and the one to use + /// with . + /// + string? ModelSchema { get; } + /// /// Gets the database schema that contains the sequence. /// @@ -80,10 +87,10 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt .Append(indentString) .Append("Sequence: "); - if (Schema != null) + if (ModelSchema != null) { builder - .Append(Schema) + .Append(ModelSchema) .Append('.'); } diff --git a/src/EFCore.Relational/Metadata/Internal/Sequence.cs b/src/EFCore.Relational/Metadata/Internal/Sequence.cs index 75aa2c0c11e..8166ba25d3c 100644 --- a/src/EFCore.Relational/Metadata/Internal/Sequence.cs +++ b/src/EFCore.Relational/Metadata/Internal/Sequence.cs @@ -190,7 +190,7 @@ public static Sequence AddSequence( sequence.EnsureMutable(); var sequences = (SortedDictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; - var tuple = (sequence.Name, sequence.Schema); + var tuple = (sequence.Name, sequence.ModelSchema); if (sequences == null || !sequences.ContainsKey(tuple)) { @@ -201,7 +201,7 @@ public static Sequence AddSequence( sequence.Name = name; - sequences.Add((name, sequence.Schema), sequence); + sequences.Add((name, sequence.ModelSchema), sequence); return sequence; } @@ -280,6 +280,15 @@ public override bool IsReadOnly /// public virtual string Name { get; set; } + /// + /// 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 virtual string? ModelSchema + => _schema; + /// /// 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/src/EFCore.Relational/Metadata/RuntimeSequence.cs b/src/EFCore.Relational/Metadata/RuntimeSequence.cs index 973318f7a29..0de0c11d43a 100644 --- a/src/EFCore.Relational/Metadata/RuntimeSequence.cs +++ b/src/EFCore.Relational/Metadata/RuntimeSequence.cs @@ -20,6 +20,7 @@ public class RuntimeSequence : AnnotatableBase, ISequence private readonly long? _minValue; private readonly long? _maxValue; private readonly bool _isCyclic; + private readonly bool _modelSchemaIsNull; /// /// Initializes a new instance of the class. @@ -33,6 +34,7 @@ public class RuntimeSequence : AnnotatableBase, ISequence /// Whether the sequence is cyclic. /// The minimum value. /// The maximum value. + /// A value indicating whether is null. public RuntimeSequence( string name, RuntimeModel model, @@ -42,7 +44,8 @@ public RuntimeSequence( int incrementBy = Sequence.DefaultIncrementBy, bool cyclic = false, long? minValue = null, - long? maxValue = null) + long? maxValue = null, + bool modelSchemaIsNull = false) { Model = model; Name = name; @@ -53,6 +56,7 @@ public RuntimeSequence( _isCyclic = cyclic; _minValue = minValue; _maxValue = maxValue; + _modelSchemaIsNull = modelSchemaIsNull; } /// @@ -65,6 +69,12 @@ public RuntimeSequence( /// public virtual string Name { get; } + /// + /// Gets the metadata schema of the sequence. + /// + public virtual string? ModelSchema + => _modelSchemaIsNull ? null : _schema; + /// /// Gets the database schema that contains the sequence. /// diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs index 57f029a4111..e7acf0ff957 100644 --- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs +++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs @@ -29,7 +29,8 @@ public CSharpRuntimeAnnotationCodeGeneratorParameters( IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, ISet namespaces, - ISet scopeVariables) + ISet scopeVariables, + bool nullable) { TargetName = targetName; ClassName = className; @@ -37,6 +38,7 @@ public CSharpRuntimeAnnotationCodeGeneratorParameters( MethodBuilder = methodBuilder; Namespaces = namespaces; ScopeVariables = scopeVariables; + UseNullableReferenceTypes = nullable; } /// @@ -78,4 +80,10 @@ public CSharpRuntimeAnnotationCodeGeneratorParameters( /// Indicates whether the given annotations are runtime annotations. /// public bool IsRuntime { get; init; } + + /// + /// Gets or sets a value indicating whther nullable reference types are enabled. + /// + /// A value indicating whther nullable reference types are enabled. + public bool UseNullableReferenceTypes { get; init; } } diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 2a026f17ac1..8df411c52b3 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -1278,6 +1278,42 @@ public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() Assert.Equal("bar", sequence["foo"]); }); + [ConditionalFact] + public virtual void HiLoSequence_with_default_model_schema() + => Test( + modelBuilder => modelBuilder + .HasDefaultSchema("dbo") + .Entity("Entity").Property("Id").UseHiLo(schema: "dbo"), + AddBoilerPlate(@" + modelBuilder + .HasDefaultSchema(""dbo"") + .HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.HasSequence(""EntityFrameworkHiLoSequence"", ""dbo"") + .IncrementsBy(10); + + modelBuilder.Entity(""Entity"", b => + { + b.Property(""Id"") + .ValueGeneratedOnAdd() + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseHiLo(b.Property(""Id""), ""EntityFrameworkHiLoSequence"", ""dbo""); + + b.HasKey(""Id""); + + b.ToTable(""Entity"", ""dbo""); + });"), + model => + { + Assert.Equal("dbo", model.GetDefaultSchema()); + + var sequence = Assert.Single(model.GetSequences()); + Assert.Equal("dbo", sequence.Schema); + }); + [ConditionalFact] public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api() => Test( diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index d6bad2c56fe..3d3951627f0 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -2384,14 +2384,15 @@ partial void Initialize() PrincipalBaseEntityType.CreateAnnotations(principalBase); PrincipalDerivedEntityType.CreateAnnotations(principalDerived); - var sequences = new SortedDictionary<(string, string), ISequence>(); + var sequences = new SortedDictionary<(string, string?), ISequence>(); var principalBaseSequence = new RuntimeSequence( ""PrincipalBaseSequence"", this, typeof(long), - schema: ""TPC""); + schema: ""TPC"", + modelSchemaIsNull: true); - sequences[(""PrincipalBaseSequence"", ""TPC"")] = principalBaseSequence; + sequences[(""PrincipalBaseSequence"", null)] = principalBaseSequence; AddAnnotation(""Relational:Sequences"", sequences); AddAnnotation(""Relational:DefaultSchema"", ""TPC""); @@ -2936,6 +2937,9 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal( new[] { dependentBase, principalBase, principalDerived }, model.GetEntityTypes()); + + var principalBaseSequence = model.FindSequence("PrincipalBaseSequence"); + Assert.Equal("TPC", principalBaseSequence.Schema); }, typeof(SqlServerNetTopologySuiteDesignTimeServices)); diff --git a/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs b/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs index 88bc0c85bda..ae56dd3778b 100644 --- a/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs +++ b/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs @@ -38,6 +38,9 @@ private class FakeSequence : Annotatable, IReadOnlySequence public string Name => "SequenceName"; + public string ModelSchema + => throw new NotImplementedException(); + public string Schema => throw new NotImplementedException();