Skip to content

Commit fa6a0fa

Browse files
committed
Improve SQL Server insertion logic and other update pipeline optimizations
Also make RETURNING the default INSERT strategy for retrieving db-generated values (for other providers). Fixes #27372 Fixes #27503
1 parent bbc4c7b commit fa6a0fa

File tree

72 files changed

+4699
-844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+4699
-844
lines changed

src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -715,30 +715,55 @@ protected virtual void GenerateEntityTypeAnnotations(
715715
}
716716

717717
var isExcludedAnnotation = annotations.Find(RelationalAnnotationNames.IsTableExcludedFromMigrations);
718+
var isExcludedFromMigrations = (isExcludedAnnotation?.Value as bool?) == true;
719+
if (isExcludedAnnotation is not null)
720+
{
721+
annotations.Remove(isExcludedAnnotation.Name);
722+
}
723+
724+
var hasTriggers = entityType.GetTriggers().Any();
725+
var requiresTableBuilder = isExcludedFromMigrations || hasTriggers;
726+
718727
if (schema != null
719728
|| (schemaAnnotation != null && tableName != null))
720729
{
721730
stringBuilder
722731
.Append(", ");
723732

724-
if (schema == null
725-
&& ((bool?)isExcludedAnnotation?.Value) != true)
733+
if (schema == null && !requiresTableBuilder)
726734
{
727735
stringBuilder.Append("(string)");
728736
}
729737

730738
stringBuilder.Append(Code.UnknownLiteral(schema));
731739
}
732740

733-
if (isExcludedAnnotation != null)
741+
if (requiresTableBuilder)
734742
{
735-
if (((bool?)isExcludedAnnotation.Value) == true)
743+
if (isExcludedFromMigrations && !hasTriggers)
736744
{
737-
stringBuilder
738-
.Append(", t => t.ExcludeFromMigrations()");
745+
stringBuilder.Append(", t => t.ExcludeFromMigrations()");
739746
}
747+
else
748+
{
749+
stringBuilder
750+
.AppendLine(", t =>")
751+
.AppendLine("{");
740752

741-
annotations.Remove(isExcludedAnnotation.Name);
753+
using (stringBuilder.Indent())
754+
{
755+
if (isExcludedFromMigrations)
756+
{
757+
stringBuilder
758+
.AppendLine("t.ExcludeFromMigrations();")
759+
.AppendLine();
760+
}
761+
762+
GenerateTriggers("t", entityType, stringBuilder);
763+
}
764+
765+
stringBuilder.Append("}");
766+
}
742767
}
743768

744769
stringBuilder.AppendLine(");");
@@ -943,6 +968,64 @@ protected virtual void GenerateCheckConstraint(
943968
stringBuilder.AppendLine(");");
944969
}
945970

971+
/// <summary>
972+
/// Generates code for <see cref="ITrigger" /> objects.
973+
/// </summary>
974+
/// <param name="tableBuilderName">The name of the table builder variable.</param>
975+
/// <param name="entityType">The entity type.</param>
976+
/// <param name="stringBuilder">The builder code is added to.</param>
977+
protected virtual void GenerateTriggers(
978+
string tableBuilderName,
979+
IEntityType entityType,
980+
IndentedStringBuilder stringBuilder)
981+
{
982+
foreach (var trigger in entityType.GetTriggers())
983+
{
984+
GenerateTrigger(tableBuilderName, trigger, stringBuilder);
985+
}
986+
}
987+
988+
/// <summary>
989+
/// Generates code for an <see cref="ITrigger" />.
990+
/// </summary>
991+
/// <param name="tableBuilderName">The name of the table builder variable.</param>
992+
/// <param name="trigger">The check constraint.</param>
993+
/// <param name="stringBuilder">The builder code is added to.</param>
994+
protected virtual void GenerateTrigger(
995+
string tableBuilderName,
996+
ITrigger trigger,
997+
IndentedStringBuilder stringBuilder)
998+
{
999+
var triggerBuilderNameStringBuilder = new StringBuilder();
1000+
triggerBuilderNameStringBuilder
1001+
.Append(tableBuilderName)
1002+
.Append(".HasTrigger(")
1003+
.Append(Code.Literal(trigger.ModelName))
1004+
.Append(")");
1005+
var triggerBuilderName = triggerBuilderNameStringBuilder.ToString();
1006+
1007+
stringBuilder.Append(triggerBuilderName);
1008+
1009+
// Note that GenerateAnnotations below does the corresponding decrement
1010+
stringBuilder.IncrementIndent();
1011+
1012+
if (trigger.Name != null
1013+
&& trigger.Name != (trigger.GetDefaultName() ?? trigger.ModelName))
1014+
{
1015+
stringBuilder
1016+
.AppendLine()
1017+
.Append(".HasName(")
1018+
.Append(Code.Literal(trigger.Name))
1019+
.Append(")");
1020+
}
1021+
1022+
var annotations = Dependencies.AnnotationCodeGenerator
1023+
.FilterIgnoredAnnotations(trigger.GetAnnotations())
1024+
.ToDictionary(a => a.Name, a => a);
1025+
1026+
GenerateAnnotations(triggerBuilderName, trigger, stringBuilder, annotations, inChainedCall: true);
1027+
}
1028+
9461029
/// <summary>
9471030
/// Generates code for <see cref="IForeignKey" /> objects.
9481031
/// </summary>

src/EFCore.Relational/Design/AnnotationCodeGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator
2828
private static readonly ISet<string> IgnoredRelationalAnnotations = new HashSet<string>
2929
{
3030
RelationalAnnotationNames.CheckConstraints,
31+
RelationalAnnotationNames.Triggers,
3132
RelationalAnnotationNames.Sequences,
3233
RelationalAnnotationNames.DbFunctions,
3334
RelationalAnnotationNames.RelationalOverrides

src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,17 @@ IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
223223
IDictionary<string, IAnnotation> annotations)
224224
=> Array.Empty<MethodCallCodeFragment>();
225225

226+
/// <summary>
227+
/// For the given annotations which have corresponding fluent API calls, returns those fluent API calls
228+
/// and removes the annotations.
229+
/// </summary>
230+
/// <param name="trigger">The trigger to which the annotations are applied.</param>
231+
/// <param name="annotations">The set of annotations from which to generate fluent API calls.</param>
232+
IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
233+
ITrigger trigger,
234+
IDictionary<string, IAnnotation> annotations)
235+
=> Array.Empty<MethodCallCodeFragment>();
236+
226237
/// <summary>
227238
/// For the given annotations which have corresponding fluent API calls, returns those fluent API calls
228239
/// and removes the annotations.
@@ -240,6 +251,8 @@ IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(IAnnotatable annota
240251
INavigation navigation => GenerateFluentApiCalls(navigation, annotations),
241252
ISkipNavigation skipNavigation => GenerateFluentApiCalls(skipNavigation, annotations),
242253
IIndex index => GenerateFluentApiCalls(index, annotations),
254+
ITrigger trigger => GenerateFluentApiCalls(trigger, annotations),
255+
243256
_ => throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType()))
244257
};
245258

src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,11 +325,63 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod
325325
annotations[RelationalAnnotationNames.ViewSchema] = entityType.GetViewSchema();
326326
annotations[RelationalAnnotationNames.SqlQuery] = entityType.GetSqlQuery();
327327
annotations[RelationalAnnotationNames.FunctionName] = entityType.GetFunctionName();
328+
329+
if (annotations.TryGetAndRemove(
330+
RelationalAnnotationNames.Triggers,
331+
out SortedDictionary<string, ITrigger> triggers))
332+
{
333+
parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!);
334+
var triggersVariable = Dependencies.CSharpHelper.Identifier("triggers", parameters.ScopeVariables, capitalize: false);
335+
parameters.MainBuilder
336+
.Append("var ").Append(triggersVariable).AppendLine(" = new SortedDictionary<string, ITrigger>();").AppendLine();
337+
338+
foreach (var (_, trigger) in triggers)
339+
{
340+
Create(trigger, triggersVariable, parameters);
341+
}
342+
343+
GenerateSimpleAnnotation(RelationalAnnotationNames.Triggers, triggersVariable, parameters);
344+
}
328345
}
329346

330347
base.Generate(entityType, parameters);
331348
}
332349

350+
private void Create(ITrigger trigger, string triggersVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
351+
{
352+
var code = Dependencies.CSharpHelper;
353+
var triggerVariable = code.Identifier(trigger.ModelName, parameters.ScopeVariables, capitalize: false);
354+
var mainBuilder = parameters.MainBuilder;
355+
mainBuilder
356+
.Append("var ").Append(triggerVariable).AppendLine(" = new RuntimeTrigger(").IncrementIndent()
357+
.Append(parameters.TargetName).AppendLine(",")
358+
.Append(code.Literal(trigger.ModelName)).AppendLine(",")
359+
.Append(code.UnknownLiteral(trigger.Name)).AppendLine(",")
360+
.Append(code.Literal(trigger.TableName)).AppendLine(",")
361+
.Append(code.UnknownLiteral(trigger.TableSchema))
362+
.AppendLine(");")
363+
.DecrementIndent()
364+
.AppendLine();
365+
366+
CreateAnnotations(
367+
trigger,
368+
Generate,
369+
parameters with { TargetName = triggerVariable });
370+
371+
mainBuilder
372+
.Append(triggersVariable).Append("[").Append(code.Literal(trigger.ModelName)).Append("] = ")
373+
.Append(triggerVariable).AppendLine(";")
374+
.AppendLine();
375+
}
376+
377+
/// <summary>
378+
/// Generates code to create the given annotations.
379+
/// </summary>
380+
/// <param name="trigger">The trigger to which the annotations are applied.</param>
381+
/// <param name="parameters">Additional parameters used during code generation.</param>
382+
public virtual void Generate(ITrigger trigger, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
383+
=> GenerateSimpleAnnotations(parameters);
384+
333385
/// <summary>
334386
/// Generates code to create the given annotations.
335387
/// </summary>

src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,4 +1846,55 @@ public static bool CanSetComment(
18461846
RelationalAnnotationNames.Comment,
18471847
comment,
18481848
fromDataAnnotation);
1849+
1850+
/// <summary>
1851+
/// Configures a database trigger when targeting a relational database.
1852+
/// </summary>
1853+
/// <remarks>
1854+
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
1855+
/// </remarks>
1856+
/// <param name="entityTypeBuilder">The entity type builder.</param>
1857+
/// <param name="name">The name of the trigger.</param>
1858+
/// <param name="tableName">The name of the table on which this trigger is defined.</param>
1859+
/// <param name="tableSchema">The schema of the table on which this trigger is defined.</param>
1860+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
1861+
/// <returns>The same builder instance if the check constraint was configured, <see langword="null" /> otherwise.</returns>
1862+
public static IConventionTriggerBuilder? HasTrigger(
1863+
this IConventionEntityTypeBuilder entityTypeBuilder,
1864+
string name,
1865+
string? tableName,
1866+
string? tableSchema,
1867+
bool fromDataAnnotation = false)
1868+
=> InternalTriggerBuilder.HasTrigger(
1869+
entityTypeBuilder.Metadata,
1870+
name,
1871+
tableName,
1872+
tableSchema,
1873+
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
1874+
?.Builder;
1875+
1876+
/// <summary>
1877+
/// Returns a value indicating whether the trigger can be configured.
1878+
/// </summary>
1879+
/// <remarks>
1880+
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
1881+
/// </remarks>
1882+
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
1883+
/// <param name="name">The name of the trigger.</param>
1884+
/// <param name="tableName">The name of the table on which this trigger is defined.</param>
1885+
/// <param name="tableSchema">The schema of the table on which this trigger is defined.</param>
1886+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
1887+
/// <returns><see langword="true" /> if the configuration can be applied.</returns>
1888+
public static bool CanHaveTrigger(
1889+
this IConventionEntityTypeBuilder entityTypeBuilder,
1890+
string name,
1891+
string? tableName,
1892+
string? tableSchema,
1893+
bool fromDataAnnotation = false)
1894+
=> InternalTriggerBuilder.CanHaveTrigger(
1895+
entityTypeBuilder.Metadata,
1896+
name,
1897+
tableName,
1898+
tableSchema,
1899+
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
18491900
}

0 commit comments

Comments
 (0)