Skip to content

Commit 49d7651

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 49ea987 commit 49d7651

File tree

41 files changed

+2606
-839
lines changed

Some content is hidden

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

41 files changed

+2606
-839
lines changed

src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public sealed class InternalUsageDiagnosticAnalyzer : DiagnosticAnalyzer
1616
private static readonly int EFLen = "EntityFrameworkCore".Length;
1717

1818
private static readonly DiagnosticDescriptor Descriptor
19-
= new(
19+
= new DiagnosticDescriptor(
2020
Id,
2121
title: AnalyzerStrings.InternalUsageTitle,
2222
messageFormat: AnalyzerStrings.InternalUsageMessageFormat,

src/EFCore.Relational/Extensions/RelationalModelExtensions.cs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore;
1414
/// </remarks>
1515
public static class RelationalModelExtensions
1616
{
17+
#region Default schema
18+
1719
/// <summary>
1820
/// Returns the default schema to use for the model, or <see langword="null" /> if none has been set.
1921
/// </summary>
@@ -58,6 +60,8 @@ public static void SetDefaultSchema(this IMutableModel model, string? value)
5860
public static ConfigurationSource? GetDefaultSchemaConfigurationSource(this IConventionModel model)
5961
=> model.FindAnnotation(RelationalAnnotationNames.DefaultSchema)?.GetConfigurationSource();
6062

63+
#endregion Default schema
64+
6165
/// <summary>
6266
/// Returns the database model.
6367
/// </summary>
@@ -74,6 +78,8 @@ public static IRelationalModel GetRelationalModel(this IModel model)
7478
return databaseModel;
7579
}
7680

81+
#region Max identifier length
82+
7783
/// <summary>
7884
/// Returns the maximum length allowed for store identifiers.
7985
/// </summary>
@@ -112,6 +118,10 @@ public static void SetMaxIdentifierLength(this IMutableModel model, int? length)
112118
public static ConfigurationSource? GetMaxIdentifierLengthConfigurationSource(this IConventionModel model)
113119
=> model.FindAnnotation(RelationalAnnotationNames.MaxIdentifierLength)?.GetConfigurationSource();
114120

121+
#endregion Max identifier length
122+
123+
#region Sequence
124+
115125
/// <summary>
116126
/// Finds a sequence with the given name.
117127
/// </summary>
@@ -269,6 +279,10 @@ public static IEnumerable<IConventionSequence> GetSequences(this IConventionMode
269279
public static IEnumerable<IReadOnlySequence> GetSequences(this IReadOnlyModel model)
270280
=> Sequence.GetSequences(model);
271281

282+
#endregion Sequence
283+
284+
#region DbFunction
285+
272286
/// <summary>
273287
/// Finds a function that is mapped to the method represented by the given <see cref="MethodInfo" />.
274288
/// </summary>
@@ -467,6 +481,10 @@ public static IEnumerable<IConventionDbFunction> GetDbFunctions(this IConvention
467481
public static IEnumerable<IDbFunction> GetDbFunctions(this IModel model)
468482
=> DbFunction.GetDbFunctions(model);
469483

484+
#endregion DbFunction
485+
486+
#region Collation
487+
470488
/// <summary>
471489
/// Returns the database collation.
472490
/// </summary>
@@ -509,4 +527,120 @@ public static void SetCollation(this IMutableModel model, string? value)
509527
/// <returns>The configuration source for the collation.</returns>
510528
public static ConfigurationSource? GetCollationConfigurationSource(this IConventionModel model)
511529
=> model.FindAnnotation(RelationalAnnotationNames.Collation)?.GetConfigurationSource();
530+
531+
#endregion Collation
532+
533+
#region Trigger
534+
535+
/// <summary>
536+
/// Finds a trigger with the given name.
537+
/// </summary>
538+
/// <param name="entityType">The entity type to find the sequence on.</param>
539+
/// <param name="name">The trigger name.</param>
540+
/// <returns>The trigger or <see langword="null" /> if no trigger with the given name was found.</returns>
541+
public static IReadOnlyTrigger? FindTrigger(this IReadOnlyEntityType entityType, string name)
542+
=> Trigger.FindTrigger(entityType, Check.NotEmpty(name, nameof(name)));
543+
544+
/// <summary>
545+
/// Finds a trigger with the given name.
546+
/// </summary>
547+
/// <param name="entityType">The entity type to find the sequence on.</param>
548+
/// <param name="name">The trigger name.</param>
549+
/// <returns>The trigger or <see langword="null" /> if no trigger with the given name was found.</returns>
550+
public static IMutableTrigger? FindTrigger(this IMutableEntityType entityType, string name)
551+
=> (IMutableTrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name);
552+
553+
/// <summary>
554+
/// Finds a trigger with the given name.
555+
/// </summary>
556+
/// <param name="entityType">The entity type to find the sequence on.</param>
557+
/// <param name="name">The trigger name.</param>
558+
/// <returns>The trigger or <see langword="null" /> if no trigger with the given name was found.</returns>
559+
public static IConventionTrigger? FindTrigger(this IConventionEntityType entityType, string name)
560+
=> (IConventionTrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name);
561+
562+
/// <summary>
563+
/// Finds a trigger with the given name.
564+
/// </summary>
565+
/// <param name="entityType">The entity type to find the sequence on.</param>
566+
/// <param name="name">The trigger name.</param>
567+
/// <returns>The trigger or <see langword="null" /> if no trigger with the given name was found.</returns>
568+
public static ITrigger? FindTrigger(this IEntityType entityType, string name)
569+
=> (ITrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name);
570+
571+
/// <summary>
572+
/// Either returns the existing <see cref="IMutableTrigger" /> with the given name or creates a new trigger with the given name.
573+
/// </summary>
574+
/// <param name="entityType">The entity type to add the trigger to.</param>
575+
/// <param name="name">The trigger name.</param>
576+
/// <returns>The trigger.</returns>
577+
public static IMutableTrigger AddTrigger(this IMutableEntityType entityType, string name)
578+
=> Trigger.AddTrigger(entityType, name, ConfigurationSource.Explicit);
579+
580+
/// <summary>
581+
/// Either returns the existing <see cref="IMutableTrigger" /> with the given name or creates a new trigger with the given name.
582+
/// </summary>
583+
/// <param name="entityType">The entityType to add the trigger to.</param>
584+
/// <param name="name">The trigger name.</param>
585+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
586+
/// <returns>The trigger.</returns>
587+
public static IConventionTrigger? AddTrigger(
588+
this IConventionEntityType entityType,
589+
string name,
590+
bool fromDataAnnotation = false)
591+
=> Trigger.AddTrigger(
592+
(IMutableEntityType)entityType, name,
593+
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
594+
595+
/// <summary>
596+
/// Removes the <see cref="IMutableTrigger" /> with the given name.
597+
/// </summary>
598+
/// <param name="entityType">The entityType to find the trigger in.</param>
599+
/// <param name="name">The trigger name.</param>
600+
/// <returns>
601+
/// The removed <see cref="IMutableTrigger" /> or <see langword="null" /> if no trigger with the given name was found.
602+
/// </returns>
603+
public static IMutableTrigger? RemoveTrigger(this IMutableEntityType entityType, string name)
604+
=> Trigger.RemoveTrigger(entityType, name);
605+
606+
/// <summary>
607+
/// Removes the <see cref="IConventionTrigger" /> with the given name.
608+
/// </summary>
609+
/// <param name="entityType">The entityType to find the trigger in.</param>
610+
/// <param name="name">The trigger name.</param>
611+
/// <returns>
612+
/// The removed <see cref="IMutableTrigger" /> or <see langword="null" /> if no trigger with the given name was found.
613+
/// </returns>
614+
public static IConventionTrigger? RemoveTrigger(this IConventionEntityType entityType, string name)
615+
=> Trigger.RemoveTrigger((IMutableEntityType)entityType, name);
616+
617+
/// <summary>
618+
/// Returns all triggers on the entity type.
619+
/// </summary>
620+
/// <param name="entityType">The entity type to get the triggers on.</param>
621+
public static IEnumerable<ITrigger> GetTriggers(this IEntityType entityType)
622+
=> Trigger.GetTriggers(entityType);
623+
624+
/// <summary>
625+
/// Returns all triggers on the entity type.
626+
/// </summary>
627+
/// <param name="entityType">The entity type to get the triggers on.</param>
628+
public static IEnumerable<IMutableTrigger> GetTriggers(this IMutableEntityType entityType)
629+
=> Trigger.GetTriggers(entityType).Cast<IMutableTrigger>();
630+
631+
/// <summary>
632+
/// Returns all triggers on the entity type.
633+
/// </summary>
634+
/// <param name="entityType">The entity type to get the triggers on.</param>
635+
public static IEnumerable<IConventionTrigger> GetTriggers(this IConventionEntityType entityType)
636+
=> Trigger.GetTriggers(entityType).Cast<IConventionTrigger>();
637+
638+
/// <summary>
639+
/// Returns all triggers on the entity type.
640+
/// </summary>
641+
/// <param name="entityType">The entity type to get the triggers on.</param>
642+
public static IEnumerable<IReadOnlyTrigger> GetTriggers(this IReadOnlyEntityType entityType)
643+
=> Trigger.GetTriggers(entityType);
644+
645+
#endregion Trigger
512646
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
5+
6+
/// <summary>
7+
/// Provides an API point for provider-specific extensions for configuring a <see cref="IConventionSequence" />.
8+
/// </summary>
9+
/// <remarks>
10+
/// See <see href="https://aka.ms/efcore-docs-conventions">Model building conventions</see> for more information and examples.
11+
/// </remarks>
12+
public interface IConventionTriggerBuilder : IConventionAnnotatableBuilder
13+
{
14+
/// <summary>
15+
/// The trigger being configured.
16+
/// </summary>
17+
new IConventionTrigger Metadata { get; }
18+
}

src/EFCore.Relational/Metadata/Builders/TableBuilder.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.ComponentModel;
5+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
56

67
namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
78

@@ -48,6 +49,28 @@ public virtual TableBuilder ExcludeFromMigrations(bool excluded = true)
4849
return this;
4950
}
5051

52+
/// <summary>
53+
/// Configures a database trigger on the table.
54+
/// </summary>
55+
/// <param name="name">The name of the trigger.</param>
56+
/// <returns>A builder that can be used to configure the database trigger.</returns>
57+
public virtual TriggerBuilder HasTrigger(string name)
58+
=> new(HasTrigger(Metadata, name, ConfigurationSource.Explicit));
59+
60+
private Trigger HasTrigger(IMutableEntityType entityType, string name, ConfigurationSource configurationSource)
61+
{
62+
Check.NotEmpty(name, nameof(name));
63+
64+
var trigger = (Trigger?)Trigger.FindTrigger(entityType, name);
65+
if (trigger != null)
66+
{
67+
trigger.UpdateConfigurationSource(configurationSource);
68+
return trigger;
69+
}
70+
71+
return Trigger.AddTrigger(entityType, name, configurationSource);
72+
}
73+
5174
#region Hidden System.Object members
5275

5376
/// <summary>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
5+
6+
/// <summary>
7+
/// Provides an API point for provider-specific extensions for configuring a <see cref="ISequence" />.
8+
/// </summary>
9+
/// <remarks>
10+
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
11+
/// </remarks>
12+
public class TriggerBuilder
13+
{
14+
/// <summary>
15+
/// Creates a new builder for the given <see cref="ISequence" />.
16+
/// </summary>
17+
/// <param name="trigger">The <see cref="IMutableSequence" /> to configure.</param>
18+
public TriggerBuilder(IMutableTrigger trigger)
19+
=> Metadata = trigger;
20+
21+
/// <summary>
22+
/// The trigger.
23+
/// </summary>
24+
public virtual IMutableTrigger Metadata { get; }
25+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata;
5+
6+
/// <summary>
7+
/// Represents a database sequence in the model.
8+
/// </summary>
9+
/// <remarks>
10+
/// See <see href="https://aka.ms/efcore-docs-sequences">Database sequences</see> for more information and examples.
11+
/// </remarks>
12+
public interface IConventionTrigger : IReadOnlyTrigger, IConventionAnnotatable
13+
{
14+
/// <summary>
15+
/// Gets the <see cref="IConventionEntityType" /> on which this trigger is defined.
16+
/// </summary>
17+
new IConventionEntityType EntityType { get; }
18+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata;
5+
6+
/// <summary>
7+
/// Represents a database trigger on a table.
8+
/// </summary>
9+
/// <remarks>
10+
/// <para>
11+
/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations.
12+
/// </para>
13+
/// <para>
14+
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
15+
/// </para>
16+
/// </remarks>
17+
public interface IMutableTrigger : IReadOnlyTrigger, IMutableAnnotatable
18+
{
19+
/// <summary>
20+
/// Gets the <see cref="IMutableEntityType" /> on which this trigger is defined.
21+
/// </summary>
22+
new IMutableEntityType EntityType { get; }
23+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata;
5+
6+
/// <summary>
7+
/// Represents a database trigger on a table.
8+
/// </summary>
9+
/// <remarks>
10+
/// <para>
11+
/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations.
12+
/// </para>
13+
/// <para>
14+
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
15+
/// </para>
16+
/// </remarks>
17+
public interface IReadOnlyTrigger : IReadOnlyAnnotatable
18+
{
19+
/// <summary>
20+
/// Gets the name of the trigger in the database.
21+
/// </summary>
22+
string Name { get; }
23+
24+
/// <summary>
25+
/// Gets the entity type on which this trigger is defined.
26+
/// </summary>
27+
IReadOnlyEntityType EntityType { get; }
28+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata;
5+
6+
/// <summary>
7+
/// Represents a database trigger on a table.
8+
/// </summary>
9+
/// <remarks>
10+
/// <para>
11+
/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations.
12+
/// </para>
13+
/// <para>
14+
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
15+
/// </para>
16+
/// </remarks>
17+
public interface ITrigger : IReadOnlyTrigger, IAnnotatable
18+
{
19+
/// <summary>
20+
/// Gets the entity type on which this trigger is defined.
21+
/// </summary>
22+
new IEntityType EntityType { get; }
23+
}

0 commit comments

Comments
 (0)