Skip to content

Commit de02c16

Browse files
committed
Sequence-based value generation, by default for TPC keys
Basically a copy-paste of: * dotnet/efcore#28477 * dotnet/efcore#28529 Closes #2445
1 parent 98ca6a6 commit de02c16

13 files changed

+662
-125
lines changed

src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs

Lines changed: 113 additions & 91 deletions
Large diffs are not rendered by default.

src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public static ModelBuilder UseHiLo(this ModelBuilder modelBuilder, string? name
3838
model.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.SequenceHiLo);
3939
model.SetHiLoSequenceName(name);
4040
model.SetHiLoSequenceSchema(schema);
41+
model.SetSequenceNameSuffix(null);
42+
model.SetSequenceSchema(null);
4143

4244
return modelBuilder;
4345
}
@@ -138,11 +140,13 @@ public static ModelBuilder UseIdentityAlwaysColumns(this ModelBuilder modelBuild
138140
{
139141
Check.NotNull(modelBuilder, nameof(modelBuilder));
140142

141-
var property = modelBuilder.Model;
143+
var model = modelBuilder.Model;
142144

143-
property.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.IdentityAlwaysColumn);
144-
property.SetHiLoSequenceName(null);
145-
property.SetHiLoSequenceSchema(null);
145+
model.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.IdentityAlwaysColumn);
146+
model.SetSequenceNameSuffix(null);
147+
model.SetSequenceSchema(null);
148+
model.SetHiLoSequenceName(null);
149+
model.SetHiLoSequenceSchema(null);
146150

147151
return modelBuilder;
148152
}
@@ -164,11 +168,13 @@ public static ModelBuilder UseIdentityByDefaultColumns(this ModelBuilder modelBu
164168
{
165169
Check.NotNull(modelBuilder, nameof(modelBuilder));
166170

167-
var property = modelBuilder.Model;
171+
var model = modelBuilder.Model;
168172

169-
property.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
170-
property.SetHiLoSequenceName(null);
171-
property.SetHiLoSequenceSchema(null);
173+
model.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
174+
model.SetSequenceNameSuffix(null);
175+
model.SetSequenceSchema(null);
176+
model.SetHiLoSequenceName(null);
177+
model.SetHiLoSequenceSchema(null);
172178

173179
return modelBuilder;
174180
}
@@ -203,15 +209,25 @@ public static ModelBuilder UseIdentityColumns(this ModelBuilder modelBuilder)
203209
NpgsqlValueGenerationStrategy? valueGenerationStrategy,
204210
bool fromDataAnnotation = false)
205211
{
206-
if (modelBuilder.CanSetAnnotation(
207-
NpgsqlAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation))
212+
if (modelBuilder.CanSetValueGenerationStrategy(valueGenerationStrategy, fromDataAnnotation))
208213
{
209214
modelBuilder.Metadata.SetValueGenerationStrategy(valueGenerationStrategy, fromDataAnnotation);
215+
210216
if (valueGenerationStrategy != NpgsqlValueGenerationStrategy.SequenceHiLo)
211217
{
212218
modelBuilder.HasHiLoSequence(null, null, fromDataAnnotation);
213219
}
214220

221+
if (valueGenerationStrategy != NpgsqlValueGenerationStrategy.Sequence)
222+
{
223+
if (modelBuilder.CanSetAnnotation(NpgsqlAnnotationNames.SequenceNameSuffix, null)
224+
&& modelBuilder.CanSetAnnotation(NpgsqlAnnotationNames.SequenceSchema, null))
225+
{
226+
modelBuilder.Metadata.SetSequenceNameSuffix(null, fromDataAnnotation);
227+
modelBuilder.Metadata.SetSequenceSchema(null, fromDataAnnotation);
228+
}
229+
}
230+
215231
return modelBuilder;
216232
}
217233

@@ -238,6 +254,39 @@ public static bool CanSetValueGenerationStrategy(
238254

239255
#endregion Identity
240256

257+
#region Sequence
258+
259+
/// <summary>
260+
/// Configures the model to use a sequence per hierarchy to generate values for key properties marked as
261+
/// <see cref="ValueGenerated.OnAdd" />, when targeting PostgreSQL.
262+
/// </summary>
263+
/// <param name="modelBuilder">The model builder.</param>
264+
/// <param name="nameSuffix">The name that will suffix the table name for each sequence created automatically.</param>
265+
/// <param name="schema">The schema of the sequence.</param>
266+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
267+
public static ModelBuilder UseKeySequences(
268+
this ModelBuilder modelBuilder,
269+
string? nameSuffix = null,
270+
string? schema = null)
271+
{
272+
Check.NullButNotEmpty(nameSuffix, nameof(nameSuffix));
273+
Check.NullButNotEmpty(schema, nameof(schema));
274+
275+
var model = modelBuilder.Model;
276+
277+
nameSuffix ??= NpgsqlModelExtensions.DefaultSequenceNameSuffix;
278+
279+
model.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.Sequence);
280+
model.SetSequenceNameSuffix(nameSuffix);
281+
model.SetSequenceSchema(schema);
282+
model.SetHiLoSequenceName(null);
283+
model.SetHiLoSequenceSchema(null);
284+
285+
return modelBuilder;
286+
}
287+
288+
#endregion Sequence
289+
241290
#region Extensions
242291

243292
/// <summary>

src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlPropertyBuilderExtensions.cs

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public static PropertyBuilder UseHiLo(
4444
property.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.SequenceHiLo);
4545
property.SetHiLoSequenceName(name);
4646
property.SetHiLoSequenceSchema(schema);
47+
property.SetSequenceName(null);
48+
property.SetSequenceSchema(null);
4749
property.RemoveIdentityOptions();
4850

4951
return propertyBuilder;
@@ -115,6 +117,106 @@ public static bool CanSetHiLoSequence(
115117

116118
#endregion HiLo
117119

120+
#region Sequence
121+
122+
/// <summary>
123+
/// Configures the key property to use a sequence-based key value generation pattern to generate values for new entities,
124+
/// when targeting PostgreSQL. This method sets the property to be <see cref="ValueGenerated.OnAdd" />.
125+
/// </summary>
126+
/// <param name="propertyBuilder">The builder for the property being configured.</param>
127+
/// <param name="name">The name of the sequence.</param>
128+
/// <param name="schema">The schema of the sequence.</param>
129+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
130+
public static PropertyBuilder UseSequence(
131+
this PropertyBuilder propertyBuilder,
132+
string? name = null,
133+
string? schema = null)
134+
{
135+
Check.NullButNotEmpty(name, nameof(name));
136+
Check.NullButNotEmpty(schema, nameof(schema));
137+
138+
var property = propertyBuilder.Metadata;
139+
140+
property.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.Sequence);
141+
property.SetSequenceName(name);
142+
property.SetSequenceSchema(schema);
143+
property.SetHiLoSequenceName(null);
144+
property.SetHiLoSequenceSchema(null);
145+
146+
return propertyBuilder;
147+
}
148+
149+
/// <summary>
150+
/// Configures the key property to use a sequence-based key value generation pattern to generate values for new entities,
151+
/// when targeting SQL Server. This method sets the property to be <see cref="ValueGenerated.OnAdd" />.
152+
/// </summary>
153+
/// <remarks>
154+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
155+
/// <see href="https://aka.ms/efcore-docs-Npgsql">Accessing SQL Server and SQL Azure databases with EF Core</see>
156+
/// for more information and examples.
157+
/// </remarks>
158+
/// <typeparam name="TProperty">The type of the property being configured.</typeparam>
159+
/// <param name="propertyBuilder">The builder for the property being configured.</param>
160+
/// <param name="name">The name of the sequence.</param>
161+
/// <param name="schema">The schema of the sequence.</param>
162+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
163+
public static PropertyBuilder<TProperty> UseSequence<TProperty>(
164+
this PropertyBuilder<TProperty> propertyBuilder,
165+
string? name = null,
166+
string? schema = null)
167+
=> (PropertyBuilder<TProperty>)UseSequence((PropertyBuilder)propertyBuilder, name, schema);
168+
169+
/// <summary>
170+
/// Configures the database sequence used for the key value generation pattern to generate values for the key property,
171+
/// when targeting PostgreSQL.
172+
/// </summary>
173+
/// <param name="propertyBuilder">The builder for the property being configured.</param>
174+
/// <param name="name">The name of the sequence.</param>
175+
/// <param name="schema">The schema of the sequence.</param>
176+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
177+
/// <returns>A builder to further configure the sequence.</returns>
178+
public static IConventionSequenceBuilder? HasSequence(
179+
this IConventionPropertyBuilder propertyBuilder,
180+
string? name,
181+
string? schema,
182+
bool fromDataAnnotation = false)
183+
{
184+
if (!propertyBuilder.CanSetSequence(name, schema, fromDataAnnotation))
185+
{
186+
return null;
187+
}
188+
189+
propertyBuilder.Metadata.SetSequenceName(name, fromDataAnnotation);
190+
propertyBuilder.Metadata.SetSequenceSchema(schema, fromDataAnnotation);
191+
192+
return name == null
193+
? null
194+
: propertyBuilder.Metadata.DeclaringEntityType.Model.Builder.HasSequence(name, schema, fromDataAnnotation);
195+
}
196+
197+
/// <summary>
198+
/// Returns a value indicating whether the given name and schema can be set for the key value generation sequence.
199+
/// </summary>
200+
/// <param name="propertyBuilder">The builder for the property being configured.</param>
201+
/// <param name="name">The name of the sequence.</param>
202+
/// <param name="schema">The schema of the sequence.</param>
203+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
204+
/// <returns><see langword="true" /> if the given name and schema can be set for the key value generation sequence.</returns>
205+
public static bool CanSetSequence(
206+
this IConventionPropertyBuilder propertyBuilder,
207+
string? name,
208+
string? schema,
209+
bool fromDataAnnotation = false)
210+
{
211+
Check.NullButNotEmpty(name, nameof(name));
212+
Check.NullButNotEmpty(schema, nameof(schema));
213+
214+
return propertyBuilder.CanSetAnnotation(NpgsqlAnnotationNames.SequenceName, name, fromDataAnnotation)
215+
&& propertyBuilder.CanSetAnnotation(NpgsqlAnnotationNames.SequenceSchema, schema, fromDataAnnotation);
216+
}
217+
218+
#endregion Sequence
219+
118220
#region Serial
119221

120222
/// <summary>
@@ -133,8 +235,8 @@ public static PropertyBuilder UseSerialColumn(
133235

134236
var property = propertyBuilder.Metadata;
135237
property.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.SerialColumn);
136-
property.SetHiLoSequenceName(null);
137-
property.SetHiLoSequenceSchema(null);
238+
property.SetSequenceName(null);
239+
property.SetSequenceSchema(null);
138240
property.RemoveHiLoOptions();
139241
property.RemoveIdentityOptions();
140242

@@ -178,6 +280,8 @@ public static PropertyBuilder UseIdentityAlwaysColumn(this PropertyBuilder prope
178280
property.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.IdentityAlwaysColumn);
179281
property.SetHiLoSequenceName(null);
180282
property.SetHiLoSequenceSchema(null);
283+
property.SetSequenceName(null);
284+
property.SetSequenceSchema(null);
181285

182286
return propertyBuilder;
183287
}
@@ -222,6 +326,8 @@ public static PropertyBuilder UseIdentityByDefaultColumn(this PropertyBuilder pr
222326
property.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
223327
property.SetHiLoSequenceName(null);
224328
property.SetHiLoSequenceSchema(null);
329+
property.SetSequenceName(null);
330+
property.SetSequenceSchema(null);
225331

226332
return propertyBuilder;
227333
}
@@ -303,11 +409,17 @@ public static PropertyBuilder<TProperty> UseIdentityColumn<TProperty>(
303409
NpgsqlAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation))
304410
{
305411
propertyBuilder.Metadata.SetValueGenerationStrategy(valueGenerationStrategy, fromDataAnnotation);
412+
306413
if (valueGenerationStrategy != NpgsqlValueGenerationStrategy.SequenceHiLo)
307414
{
308415
propertyBuilder.HasHiLoSequence(null, null, fromDataAnnotation);
309416
}
310417

418+
if (valueGenerationStrategy != NpgsqlValueGenerationStrategy.Sequence)
419+
{
420+
propertyBuilder.HasSequence(null, null, fromDataAnnotation);
421+
}
422+
311423
return propertyBuilder;
312424
}
313425

src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@ namespace Microsoft.EntityFrameworkCore;
66

77
public static class NpgsqlModelExtensions
88
{
9+
/// <summary>
10+
/// The default name for the hi-lo sequence.
11+
/// </summary>
912
public const string DefaultHiLoSequenceName = "EntityFrameworkHiLoSequence";
1013

14+
/// <summary>
15+
/// The default prefix for sequences applied to properties.
16+
/// </summary>
17+
public const string DefaultSequenceNameSuffix = "Sequence";
18+
1119
#region HiLo
1220

1321
/// <summary>
@@ -102,6 +110,106 @@ public static void SetHiLoSequenceSchema(this IMutableModel model, string? value
102110

103111
#endregion
104112

113+
#region Sequence
114+
115+
/// <summary>
116+
/// Returns the suffix to append to the name of automatically created sequences.
117+
/// </summary>
118+
/// <param name="model">The model.</param>
119+
/// <returns>The name to use for the default key value generation sequence.</returns>
120+
public static string GetSequenceNameSuffix(this IReadOnlyModel model)
121+
=> (string?)model[NpgsqlAnnotationNames.SequenceNameSuffix]
122+
?? DefaultSequenceNameSuffix;
123+
124+
/// <summary>
125+
/// Sets the suffix to append to the name of automatically created sequences.
126+
/// </summary>
127+
/// <param name="model">The model.</param>
128+
/// <param name="name">The value to set.</param>
129+
public static void SetSequenceNameSuffix(this IMutableModel model, string? name)
130+
{
131+
Check.NullButNotEmpty(name, nameof(name));
132+
133+
model.SetOrRemoveAnnotation(NpgsqlAnnotationNames.SequenceNameSuffix, name);
134+
}
135+
136+
/// <summary>
137+
/// Sets the suffix to append to the name of automatically created sequences.
138+
/// </summary>
139+
/// <param name="model">The model.</param>
140+
/// <param name="name">The value to set.</param>
141+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
142+
/// <returns>The configured value.</returns>
143+
public static string? SetSequenceNameSuffix(
144+
this IConventionModel model,
145+
string? name,
146+
bool fromDataAnnotation = false)
147+
{
148+
Check.NullButNotEmpty(name, nameof(name));
149+
150+
model.SetOrRemoveAnnotation(NpgsqlAnnotationNames.SequenceNameSuffix, name, fromDataAnnotation);
151+
152+
return name;
153+
}
154+
155+
/// <summary>
156+
/// Returns the <see cref="ConfigurationSource" /> for the default value generation sequence name suffix.
157+
/// </summary>
158+
/// <param name="model">The model.</param>
159+
/// <returns>The <see cref="ConfigurationSource" /> for the default key value generation sequence name.</returns>
160+
public static ConfigurationSource? GetSequenceNameSuffixConfigurationSource(this IConventionModel model)
161+
=> model.FindAnnotation(NpgsqlAnnotationNames.SequenceNameSuffix)?.GetConfigurationSource();
162+
163+
/// <summary>
164+
/// Returns the schema to use for the default value generation sequence.
165+
/// <see cref="NpgsqlPropertyBuilderExtensions.UseSequence" />
166+
/// </summary>
167+
/// <param name="model">The model.</param>
168+
/// <returns>The schema to use for the default key value generation sequence.</returns>
169+
public static string? GetSequenceSchema(this IReadOnlyModel model)
170+
=> (string?)model[NpgsqlAnnotationNames.SequenceSchema];
171+
172+
/// <summary>
173+
/// Sets the schema to use for the default key value generation sequence.
174+
/// </summary>
175+
/// <param name="model">The model.</param>
176+
/// <param name="value">The value to set.</param>
177+
public static void SetSequenceSchema(this IMutableModel model, string? value)
178+
{
179+
Check.NullButNotEmpty(value, nameof(value));
180+
181+
model.SetOrRemoveAnnotation(NpgsqlAnnotationNames.SequenceSchema, value);
182+
}
183+
184+
/// <summary>
185+
/// Sets the schema to use for the default key value generation sequence.
186+
/// </summary>
187+
/// <param name="model">The model.</param>
188+
/// <param name="value">The value to set.</param>
189+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
190+
/// <returns>The configured value.</returns>
191+
public static string? SetSequenceSchema(
192+
this IConventionModel model,
193+
string? value,
194+
bool fromDataAnnotation = false)
195+
{
196+
Check.NullButNotEmpty(value, nameof(value));
197+
198+
model.SetOrRemoveAnnotation(NpgsqlAnnotationNames.SequenceSchema, value, fromDataAnnotation);
199+
200+
return value;
201+
}
202+
203+
/// <summary>
204+
/// Returns the <see cref="ConfigurationSource" /> for the default key value generation sequence schema.
205+
/// </summary>
206+
/// <param name="model">The model.</param>
207+
/// <returns>The <see cref="ConfigurationSource" /> for the default key value generation sequence schema.</returns>
208+
public static ConfigurationSource? GetSequenceSchemaConfigurationSource(this IConventionModel model)
209+
=> model.FindAnnotation(NpgsqlAnnotationNames.SequenceSchema)?.GetConfigurationSource();
210+
211+
#endregion Sequence
212+
105213
#region Value Generation Strategy
106214

107215
/// <summary>

0 commit comments

Comments
 (0)