Skip to content

Commit d4fdfb8

Browse files
committed
Allow SQLServer Sequence/HiLo on non-key properties
Closes #29758
1 parent 3a62379 commit d4fdfb8

File tree

7 files changed

+94
-60
lines changed

7 files changed

+94
-60
lines changed

src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs

-29
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ public override void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.
4444

4545
ValidateDecimalColumns(model, logger);
4646
ValidateByteIdentityMapping(model, logger);
47-
ValidateNonKeyValueGeneration(model, logger);
4847
ValidateTemporalTables(model, logger);
4948
}
5049

@@ -113,34 +112,6 @@ protected virtual void ValidateByteIdentityMapping(
113112
}
114113
}
115114

116-
/// <summary>
117-
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
118-
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
119-
/// any release. You should only use it directly in your code with extreme caution and knowing that
120-
/// doing so can result in application failures when updating to a new Entity Framework Core release.
121-
/// </summary>
122-
protected virtual void ValidateNonKeyValueGeneration(
123-
IModel model,
124-
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
125-
{
126-
foreach (var entityType in model.GetEntityTypes())
127-
{
128-
foreach (var property in entityType.GetDeclaredProperties()
129-
.Where(
130-
p => (p.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.SequenceHiLo
131-
|| p.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.Sequence)
132-
&& ((IConventionProperty)p).GetValueGenerationStrategyConfigurationSource() != null
133-
&& !p.IsKey()
134-
&& p.ValueGenerated != ValueGenerated.Never
135-
&& (!(p.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy) is IConventionAnnotation strategy)
136-
|| !ConfigurationSource.Convention.Overrides(strategy.GetConfigurationSource()))))
137-
{
138-
throw new InvalidOperationException(
139-
SqlServerStrings.NonKeyValueGeneration(property.Name, property.DeclaringEntityType.DisplayName()));
140-
}
141-
}
142-
}
143-
144115
/// <summary>
145116
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
146117
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs

-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EFCore.SqlServer/Properties/SqlServerStrings.resx

-3
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,6 @@
278278
<data name="NoInitialCatalog" xml:space="preserve">
279279
<value>The database name could not be determined. To use 'EnsureDeleted', the connection string must specify 'Initial Catalog'.</value>
280280
</data>
281-
<data name="NonKeyValueGeneration" xml:space="preserve">
282-
<value>The property '{property}' on entity type '{entityType}' is configured to use 'SequenceHiLo' value generator, which is only intended for keys. If this was intentional, configure an alternate key on the property, otherwise call 'ValueGeneratedNever' or configure store generation for this property.</value>
283-
</data>
284281
<data name="NoSavepointRelease" xml:space="preserve">
285282
<value>SQL Server does not support releasing a savepoint.</value>
286283
</data>

src/EFCore/Infrastructure/ModelRuntimeInitializer.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ public virtual IModel Initialize(
5555
bool designTime = true,
5656
IDiagnosticsLogger<DbLoggerCategory.Model.Validation>? validationLogger = null)
5757
{
58-
if (model is Model mutableModel
59-
&& !mutableModel.IsReadOnly)
58+
if (model is Model { IsReadOnly: false } mutableModel)
6059
{
6160
lock (SyncObject)
6261
{

test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs

+48
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,54 @@ await Test(
956956
""");
957957
}
958958

959+
[ConditionalFact]
960+
public virtual async Task Add_column_sequence()
961+
{
962+
await Test(
963+
builder => builder.Entity("People").Property<string>("Id"),
964+
builder => { },
965+
builder => builder.Entity("People").Property<int>("SequenceColumn").UseSequence(),
966+
model =>
967+
{
968+
var table = Assert.Single(model.Tables);
969+
var column = Assert.Single(table.Columns, c => c.Name == "SequenceColumn");
970+
971+
// Note: #29863 tracks recognizing sequence columns as such
972+
Assert.Equal("(NEXT VALUE FOR [PeopleSequence])", column.DefaultValueSql);
973+
});
974+
975+
AssertSql(
976+
"""
977+
CREATE SEQUENCE [PeopleSequence] START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;
978+
""",
979+
//
980+
"""
981+
ALTER TABLE [People] ADD [SequenceColumn] int NOT NULL DEFAULT (NEXT VALUE FOR [PeopleSequence]);
982+
""");
983+
}
984+
985+
[ConditionalFact]
986+
public virtual async Task Add_column_hilo()
987+
{
988+
await Test(
989+
builder => builder.Entity("People").Property<string>("Id"),
990+
builder => { },
991+
builder => builder.Entity("People").Property<int>("SequenceColumn").UseHiLo(),
992+
_ =>
993+
{
994+
// Reverse-engineering of hilo columns isn't supported
995+
});
996+
997+
AssertSql(
998+
"""
999+
CREATE SEQUENCE [EntityFrameworkHiLoSequence] START WITH 1 INCREMENT BY 10 NO MINVALUE NO MAXVALUE NO CYCLE;
1000+
""",
1001+
//
1002+
"""
1003+
ALTER TABLE [People] ADD [SequenceColumn] int NOT NULL DEFAULT 0;
1004+
""");
1005+
}
1006+
9591007
public override async Task Alter_column_change_type()
9601008
{
9611009
await base.Alter_column_change_type();

test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs

+45
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,51 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
143143
}
144144
}
145145

146+
[ConditionalFact]
147+
public void Insert_with_non_key_sequence()
148+
{
149+
using var testStore = SqlServerTestStore.CreateInitialized(DatabaseName);
150+
using (var context = new BlogContextNonKeySequence(testStore.Name))
151+
{
152+
context.Database.EnsureCreatedResiliently();
153+
154+
context.AddRange(
155+
new Blog { Name = "One Unicorn" }, new Blog { Name = "Two Unicorns" });
156+
157+
context.SaveChanges();
158+
}
159+
160+
using (var context = new BlogContextNonKeySequence(testStore.Name))
161+
{
162+
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
163+
164+
Assert.Equal(1, blogs[0].Id);
165+
Assert.Equal(1, blogs[0].OtherId);
166+
Assert.Equal(2, blogs[1].Id);
167+
Assert.Equal(2, blogs[1].OtherId);
168+
}
169+
}
170+
171+
public class BlogContextNonKeySequence : ContextBase
172+
{
173+
public BlogContextNonKeySequence(string databaseName)
174+
: base(databaseName)
175+
{
176+
}
177+
178+
protected override void OnModelCreating(ModelBuilder modelBuilder)
179+
{
180+
base.OnModelCreating(modelBuilder);
181+
182+
modelBuilder.Entity<Blog>(
183+
eb =>
184+
{
185+
eb.Property(b => b.OtherId).UseSequence();
186+
eb.Property(b => b.OtherId).ValueGeneratedOnAdd();
187+
});
188+
}
189+
}
190+
146191
[ConditionalFact]
147192
public void Insert_with_default_value_from_sequence()
148193
{

test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs

-18
Original file line numberDiff line numberDiff line change
@@ -614,15 +614,6 @@ public void Passes_for_non_key_identity_on_model()
614614
Validate(modelBuilder);
615615
}
616616

617-
[ConditionalFact]
618-
public void Detects_non_key_SequenceHiLo()
619-
{
620-
var modelBuilder = CreateConventionModelBuilder();
621-
modelBuilder.Entity<Dog>().Property(c => c.Type).UseHiLo();
622-
623-
VerifyError(SqlServerStrings.NonKeyValueGeneration(nameof(Dog.Type), nameof(Dog)), modelBuilder);
624-
}
625-
626617
[ConditionalFact]
627618
public void Passes_for_non_key_SequenceHiLo_on_model()
628619
{
@@ -635,15 +626,6 @@ public void Passes_for_non_key_SequenceHiLo_on_model()
635626
Validate(modelBuilder);
636627
}
637628

638-
[ConditionalFact]
639-
public void Detects_non_key_KeySequence()
640-
{
641-
var modelBuilder = CreateConventionModelBuilder();
642-
modelBuilder.Entity<Dog>().Property(c => c.Type).UseSequence();
643-
644-
VerifyError(SqlServerStrings.NonKeyValueGeneration(nameof(Dog.Type), nameof(Dog)), modelBuilder);
645-
}
646-
647629
[ConditionalFact]
648630
public void Passes_for_non_key_KeySequence_on_model()
649631
{

0 commit comments

Comments
 (0)