Skip to content

Commit

Permalink
Improve SQL Server insertion logic and other update pipeline optimiza…
Browse files Browse the repository at this point in the history
…tions (#27573)

Also make RETURNING the default INSERT strategy for retrieving
db-generated values (for other providers).

Fixes #27372
Fixes #27503
  • Loading branch information
roji authored Mar 17, 2022
1 parent bbc4c7b commit b906ca3
Show file tree
Hide file tree
Showing 72 changed files with 4,699 additions and 844 deletions.
97 changes: 90 additions & 7 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -715,30 +715,55 @@ protected virtual void GenerateEntityTypeAnnotations(
}

var isExcludedAnnotation = annotations.Find(RelationalAnnotationNames.IsTableExcludedFromMigrations);
var isExcludedFromMigrations = (isExcludedAnnotation?.Value as bool?) == true;
if (isExcludedAnnotation is not null)
{
annotations.Remove(isExcludedAnnotation.Name);
}

var hasTriggers = entityType.GetTriggers().Any();
var requiresTableBuilder = isExcludedFromMigrations || hasTriggers;

if (schema != null
|| (schemaAnnotation != null && tableName != null))
{
stringBuilder
.Append(", ");

if (schema == null
&& ((bool?)isExcludedAnnotation?.Value) != true)
if (schema == null && !requiresTableBuilder)
{
stringBuilder.Append("(string)");
}

stringBuilder.Append(Code.UnknownLiteral(schema));
}

if (isExcludedAnnotation != null)
if (requiresTableBuilder)
{
if (((bool?)isExcludedAnnotation.Value) == true)
if (isExcludedFromMigrations && !hasTriggers)
{
stringBuilder
.Append(", t => t.ExcludeFromMigrations()");
stringBuilder.Append(", t => t.ExcludeFromMigrations()");
}
else
{
stringBuilder
.AppendLine(", t =>")
.AppendLine("{");

annotations.Remove(isExcludedAnnotation.Name);
using (stringBuilder.Indent())
{
if (isExcludedFromMigrations)
{
stringBuilder
.AppendLine("t.ExcludeFromMigrations();")
.AppendLine();
}

GenerateTriggers("t", entityType, stringBuilder);
}

stringBuilder.Append("}");
}
}

stringBuilder.AppendLine(");");
Expand Down Expand Up @@ -943,6 +968,64 @@ protected virtual void GenerateCheckConstraint(
stringBuilder.AppendLine(");");
}

/// <summary>
/// Generates code for <see cref="ITrigger" /> objects.
/// </summary>
/// <param name="tableBuilderName">The name of the table builder variable.</param>
/// <param name="entityType">The entity type.</param>
/// <param name="stringBuilder">The builder code is added to.</param>
protected virtual void GenerateTriggers(
string tableBuilderName,
IEntityType entityType,
IndentedStringBuilder stringBuilder)
{
foreach (var trigger in entityType.GetTriggers())
{
GenerateTrigger(tableBuilderName, trigger, stringBuilder);
}
}

/// <summary>
/// Generates code for an <see cref="ITrigger" />.
/// </summary>
/// <param name="tableBuilderName">The name of the table builder variable.</param>
/// <param name="trigger">The check constraint.</param>
/// <param name="stringBuilder">The builder code is added to.</param>
protected virtual void GenerateTrigger(
string tableBuilderName,
ITrigger trigger,
IndentedStringBuilder stringBuilder)
{
var triggerBuilderNameStringBuilder = new StringBuilder();
triggerBuilderNameStringBuilder
.Append(tableBuilderName)
.Append(".HasTrigger(")
.Append(Code.Literal(trigger.ModelName))
.Append(")");
var triggerBuilderName = triggerBuilderNameStringBuilder.ToString();

stringBuilder.Append(triggerBuilderName);

// Note that GenerateAnnotations below does the corresponding decrement
stringBuilder.IncrementIndent();

if (trigger.Name != null
&& trigger.Name != (trigger.GetDefaultName() ?? trigger.ModelName))
{
stringBuilder
.AppendLine()
.Append(".HasName(")
.Append(Code.Literal(trigger.Name))
.Append(")");
}

var annotations = Dependencies.AnnotationCodeGenerator
.FilterIgnoredAnnotations(trigger.GetAnnotations())
.ToDictionary(a => a.Name, a => a);

GenerateAnnotations(triggerBuilderName, trigger, stringBuilder, annotations, inChainedCall: true);
}

/// <summary>
/// Generates code for <see cref="IForeignKey" /> objects.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator
private static readonly ISet<string> IgnoredRelationalAnnotations = new HashSet<string>
{
RelationalAnnotationNames.CheckConstraints,
RelationalAnnotationNames.Triggers,
RelationalAnnotationNames.Sequences,
RelationalAnnotationNames.DbFunctions,
RelationalAnnotationNames.RelationalOverrides
Expand Down
13 changes: 13 additions & 0 deletions src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,17 @@ IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
IDictionary<string, IAnnotation> annotations)
=> Array.Empty<MethodCallCodeFragment>();

/// <summary>
/// For the given annotations which have corresponding fluent API calls, returns those fluent API calls
/// and removes the annotations.
/// </summary>
/// <param name="trigger">The trigger to which the annotations are applied.</param>
/// <param name="annotations">The set of annotations from which to generate fluent API calls.</param>
IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
ITrigger trigger,
IDictionary<string, IAnnotation> annotations)
=> Array.Empty<MethodCallCodeFragment>();

/// <summary>
/// For the given annotations which have corresponding fluent API calls, returns those fluent API calls
/// and removes the annotations.
Expand All @@ -240,6 +251,8 @@ IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(IAnnotatable annota
INavigation navigation => GenerateFluentApiCalls(navigation, annotations),
ISkipNavigation skipNavigation => GenerateFluentApiCalls(skipNavigation, annotations),
IIndex index => GenerateFluentApiCalls(index, annotations),
ITrigger trigger => GenerateFluentApiCalls(trigger, annotations),

_ => throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType()))
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,63 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod
annotations[RelationalAnnotationNames.ViewSchema] = entityType.GetViewSchema();
annotations[RelationalAnnotationNames.SqlQuery] = entityType.GetSqlQuery();
annotations[RelationalAnnotationNames.FunctionName] = entityType.GetFunctionName();

if (annotations.TryGetAndRemove(
RelationalAnnotationNames.Triggers,
out SortedDictionary<string, ITrigger> triggers))
{
parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!);
var triggersVariable = Dependencies.CSharpHelper.Identifier("triggers", parameters.ScopeVariables, capitalize: false);
parameters.MainBuilder
.Append("var ").Append(triggersVariable).AppendLine(" = new SortedDictionary<string, ITrigger>();").AppendLine();

foreach (var (_, trigger) in triggers)
{
Create(trigger, triggersVariable, parameters);
}

GenerateSimpleAnnotation(RelationalAnnotationNames.Triggers, triggersVariable, parameters);
}
}

base.Generate(entityType, parameters);
}

private void Create(ITrigger trigger, string triggersVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
{
var code = Dependencies.CSharpHelper;
var triggerVariable = code.Identifier(trigger.ModelName, parameters.ScopeVariables, capitalize: false);
var mainBuilder = parameters.MainBuilder;
mainBuilder
.Append("var ").Append(triggerVariable).AppendLine(" = new RuntimeTrigger(").IncrementIndent()
.Append(parameters.TargetName).AppendLine(",")
.Append(code.Literal(trigger.ModelName)).AppendLine(",")
.Append(code.UnknownLiteral(trigger.Name)).AppendLine(",")
.Append(code.Literal(trigger.TableName)).AppendLine(",")
.Append(code.UnknownLiteral(trigger.TableSchema))
.AppendLine(");")
.DecrementIndent()
.AppendLine();

CreateAnnotations(
trigger,
Generate,
parameters with { TargetName = triggerVariable });

mainBuilder
.Append(triggersVariable).Append("[").Append(code.Literal(trigger.ModelName)).Append("] = ")
.Append(triggerVariable).AppendLine(";")
.AppendLine();
}

/// <summary>
/// Generates code to create the given annotations.
/// </summary>
/// <param name="trigger">The trigger to which the annotations are applied.</param>
/// <param name="parameters">Additional parameters used during code generation.</param>
public virtual void Generate(ITrigger trigger, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
=> GenerateSimpleAnnotations(parameters);

/// <summary>
/// Generates code to create the given annotations.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1846,4 +1846,55 @@ public static bool CanSetComment(
RelationalAnnotationNames.Comment,
comment,
fromDataAnnotation);

/// <summary>
/// Configures a database trigger when targeting a relational database.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The entity type builder.</param>
/// <param name="name">The name of the trigger.</param>
/// <param name="tableName">The name of the table on which this trigger is defined.</param>
/// <param name="tableSchema">The schema of the table on which this trigger is defined.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>The same builder instance if the check constraint was configured, <see langword="null" /> otherwise.</returns>
public static IConventionTriggerBuilder? HasTrigger(
this IConventionEntityTypeBuilder entityTypeBuilder,
string name,
string? tableName,
string? tableSchema,
bool fromDataAnnotation = false)
=> InternalTriggerBuilder.HasTrigger(
entityTypeBuilder.Metadata,
name,
tableName,
tableSchema,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
?.Builder;

/// <summary>
/// Returns a value indicating whether the trigger can be configured.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <param name="name">The name of the trigger.</param>
/// <param name="tableName">The name of the table on which this trigger is defined.</param>
/// <param name="tableSchema">The schema of the table on which this trigger is defined.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns><see langword="true" /> if the configuration can be applied.</returns>
public static bool CanHaveTrigger(
this IConventionEntityTypeBuilder entityTypeBuilder,
string name,
string? tableName,
string? tableSchema,
bool fromDataAnnotation = false)
=> InternalTriggerBuilder.CanHaveTrigger(
entityTypeBuilder.Metadata,
name,
tableName,
tableSchema,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
}
Loading

0 comments on commit b906ca3

Please sign in to comment.