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

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

Fixes dotnet#27372
Fixes dotnet#27503
  • Loading branch information
roji committed Mar 4, 2022
1 parent 49ea987 commit 466fe62
Show file tree
Hide file tree
Showing 43 changed files with 2,604 additions and 813 deletions.
2 changes: 1 addition & 1 deletion src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public sealed class InternalUsageDiagnosticAnalyzer : DiagnosticAnalyzer
private static readonly int EFLen = "EntityFrameworkCore".Length;

private static readonly DiagnosticDescriptor Descriptor
= new(
= new DiagnosticDescriptor(
Id,
title: AnalyzerStrings.InternalUsageTitle,
messageFormat: AnalyzerStrings.InternalUsageMessageFormat,
Expand Down
134 changes: 134 additions & 0 deletions src/EFCore.Relational/Extensions/RelationalModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore;
/// </remarks>
public static class RelationalModelExtensions
{
#region Default schema

/// <summary>
/// Returns the default schema to use for the model, or <see langword="null" /> if none has been set.
/// </summary>
Expand Down Expand Up @@ -58,6 +60,8 @@ public static void SetDefaultSchema(this IMutableModel model, string? value)
public static ConfigurationSource? GetDefaultSchemaConfigurationSource(this IConventionModel model)
=> model.FindAnnotation(RelationalAnnotationNames.DefaultSchema)?.GetConfigurationSource();

#endregion Default schema

/// <summary>
/// Returns the database model.
/// </summary>
Expand All @@ -74,6 +78,8 @@ public static IRelationalModel GetRelationalModel(this IModel model)
return databaseModel;
}

#region Max identifier length

/// <summary>
/// Returns the maximum length allowed for store identifiers.
/// </summary>
Expand Down Expand Up @@ -112,6 +118,10 @@ public static void SetMaxIdentifierLength(this IMutableModel model, int? length)
public static ConfigurationSource? GetMaxIdentifierLengthConfigurationSource(this IConventionModel model)
=> model.FindAnnotation(RelationalAnnotationNames.MaxIdentifierLength)?.GetConfigurationSource();

#endregion Max identifier length

#region Sequence

/// <summary>
/// Finds a sequence with the given name.
/// </summary>
Expand Down Expand Up @@ -269,6 +279,10 @@ public static IEnumerable<IConventionSequence> GetSequences(this IConventionMode
public static IEnumerable<IReadOnlySequence> GetSequences(this IReadOnlyModel model)
=> Sequence.GetSequences(model);

#endregion Sequence

#region DbFunction

/// <summary>
/// Finds a function that is mapped to the method represented by the given <see cref="MethodInfo" />.
/// </summary>
Expand Down Expand Up @@ -467,6 +481,10 @@ public static IEnumerable<IConventionDbFunction> GetDbFunctions(this IConvention
public static IEnumerable<IDbFunction> GetDbFunctions(this IModel model)
=> DbFunction.GetDbFunctions(model);

#endregion DbFunction

#region Collation

/// <summary>
/// Returns the database collation.
/// </summary>
Expand Down Expand Up @@ -509,4 +527,120 @@ public static void SetCollation(this IMutableModel model, string? value)
/// <returns>The configuration source for the collation.</returns>
public static ConfigurationSource? GetCollationConfigurationSource(this IConventionModel model)
=> model.FindAnnotation(RelationalAnnotationNames.Collation)?.GetConfigurationSource();

#endregion Collation

#region Trigger

/// <summary>
/// Finds a trigger with the given name.
/// </summary>
/// <param name="entityType">The entity type to find the sequence on.</param>
/// <param name="name">The trigger name.</param>
/// <returns>The trigger or <see langword="null" /> if no trigger with the given name was found.</returns>
public static IReadOnlyTrigger? FindTrigger(this IReadOnlyEntityType entityType, string name)
=> Trigger.FindTrigger(entityType, Check.NotEmpty(name, nameof(name)));

/// <summary>
/// Finds a trigger with the given name.
/// </summary>
/// <param name="entityType">The entity type to find the sequence on.</param>
/// <param name="name">The trigger name.</param>
/// <returns>The trigger or <see langword="null" /> if no trigger with the given name was found.</returns>
public static IMutableTrigger? FindTrigger(this IMutableEntityType entityType, string name)
=> (IMutableTrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name);

/// <summary>
/// Finds a trigger with the given name.
/// </summary>
/// <param name="entityType">The entity type to find the sequence on.</param>
/// <param name="name">The trigger name.</param>
/// <returns>The trigger or <see langword="null" /> if no trigger with the given name was found.</returns>
public static IConventionTrigger? FindTrigger(this IConventionEntityType entityType, string name)
=> (IConventionTrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name);

/// <summary>
/// Finds a trigger with the given name.
/// </summary>
/// <param name="entityType">The entity type to find the sequence on.</param>
/// <param name="name">The trigger name.</param>
/// <returns>The trigger or <see langword="null" /> if no trigger with the given name was found.</returns>
public static ITrigger? FindTrigger(this IEntityType entityType, string name)
=> (ITrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name);

/// <summary>
/// Either returns the existing <see cref="IMutableTrigger" /> with the given name or creates a new trigger with the given name.
/// </summary>
/// <param name="entityType">The entity type to add the trigger to.</param>
/// <param name="name">The trigger name.</param>
/// <returns>The trigger.</returns>
public static IMutableTrigger AddTrigger(this IMutableEntityType entityType, string name)
=> Trigger.AddTrigger(entityType, name, ConfigurationSource.Explicit);

/// <summary>
/// Either returns the existing <see cref="IMutableTrigger" /> with the given name or creates a new trigger with the given name.
/// </summary>
/// <param name="entityType">The entityType to add the trigger to.</param>
/// <param name="name">The trigger name.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>The trigger.</returns>
public static IConventionTrigger? AddTrigger(
this IConventionEntityType entityType,
string name,
bool fromDataAnnotation = false)
=> Trigger.AddTrigger(
(IMutableEntityType)entityType, name,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <summary>
/// Removes the <see cref="IMutableTrigger" /> with the given name.
/// </summary>
/// <param name="entityType">The entityType to find the trigger in.</param>
/// <param name="name">The trigger name.</param>
/// <returns>
/// The removed <see cref="IMutableTrigger" /> or <see langword="null" /> if no trigger with the given name was found.
/// </returns>
public static IMutableTrigger? RemoveTrigger(this IMutableEntityType entityType, string name)
=> Trigger.RemoveTrigger(entityType, name);

/// <summary>
/// Removes the <see cref="IConventionTrigger" /> with the given name.
/// </summary>
/// <param name="entityType">The entityType to find the trigger in.</param>
/// <param name="name">The trigger name.</param>
/// <returns>
/// The removed <see cref="IMutableTrigger" /> or <see langword="null" /> if no trigger with the given name was found.
/// </returns>
public static IConventionTrigger? RemoveTrigger(this IConventionEntityType entityType, string name)
=> Trigger.RemoveTrigger((IMutableEntityType)entityType, name);

/// <summary>
/// Returns all triggers on the entity type.
/// </summary>
/// <param name="entityType">The entity type to get the triggers on.</param>
public static IEnumerable<ITrigger> GetTriggers(this IEntityType entityType)
=> Trigger.GetTriggers(entityType);

/// <summary>
/// Returns all triggers on the entity type.
/// </summary>
/// <param name="entityType">The entity type to get the triggers on.</param>
public static IEnumerable<IMutableTrigger> GetTriggers(this IMutableEntityType entityType)
=> Trigger.GetTriggers(entityType).Cast<IMutableTrigger>();

/// <summary>
/// Returns all triggers on the entity type.
/// </summary>
/// <param name="entityType">The entity type to get the triggers on.</param>
public static IEnumerable<IConventionTrigger> GetTriggers(this IConventionEntityType entityType)
=> Trigger.GetTriggers(entityType).Cast<IConventionTrigger>();

/// <summary>
/// Returns all triggers on the entity type.
/// </summary>
/// <param name="entityType">The entity type to get the triggers on.</param>
public static IEnumerable<IReadOnlyTrigger> GetTriggers(this IReadOnlyEntityType entityType)
=> Trigger.GetTriggers(entityType);

#endregion Trigger
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Metadata.Builders;

/// <summary>
/// Provides an API point for provider-specific extensions for configuring a <see cref="IConventionSequence" />.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-conventions">Model building conventions</see> for more information and examples.
/// </remarks>
public interface IConventionTriggerBuilder : IConventionAnnotatableBuilder
{
/// <summary>
/// The trigger being configured.
/// </summary>
new IConventionTrigger Metadata { get; }
}
23 changes: 23 additions & 0 deletions src/EFCore.Relational/Metadata/Builders/TableBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders;

Expand Down Expand Up @@ -48,6 +49,28 @@ public virtual TableBuilder ExcludeFromMigrations(bool excluded = true)
return this;
}

/// <summary>
/// Configures a database trigger on the table.
/// </summary>
/// <param name="name">The name of the trigger.</param>
/// <returns>A builder that can be used to configure the database trigger.</returns>
public virtual TriggerBuilder HasTrigger(string name)
=> new(HasTrigger(Metadata, name, ConfigurationSource.Explicit));

private Trigger HasTrigger(IMutableEntityType entityType, string name, ConfigurationSource configurationSource)
{
Check.NotEmpty(name, nameof(name));

var trigger = (Trigger?)Trigger.FindTrigger(entityType, name);
if (trigger != null)
{
trigger.UpdateConfigurationSource(configurationSource);
return trigger;
}

return Trigger.AddTrigger(entityType, name, configurationSource);
}

#region Hidden System.Object members

/// <summary>
Expand Down
25 changes: 25 additions & 0 deletions src/EFCore.Relational/Metadata/Builders/TriggerBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Metadata.Builders;

/// <summary>
/// Provides an API point for provider-specific extensions for configuring a <see cref="ISequence" />.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
/// </remarks>
public class TriggerBuilder
{
/// <summary>
/// Creates a new builder for the given <see cref="ISequence" />.
/// </summary>
/// <param name="trigger">The <see cref="IMutableSequence" /> to configure.</param>
public TriggerBuilder(IMutableTrigger trigger)
=> Metadata = trigger;

/// <summary>
/// The trigger.
/// </summary>
public virtual IMutableTrigger Metadata { get; }
}
18 changes: 18 additions & 0 deletions src/EFCore.Relational/Metadata/IConventionTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Metadata;

/// <summary>
/// Represents a database sequence in the model.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-sequences">Database sequences</see> for more information and examples.
/// </remarks>
public interface IConventionTrigger : IReadOnlyTrigger, IConventionAnnotatable
{
/// <summary>
/// Gets the <see cref="IConventionEntityType" /> on which this trigger is defined.
/// </summary>
new IConventionEntityType EntityType { get; }
}
23 changes: 23 additions & 0 deletions src/EFCore.Relational/Metadata/IMutableTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Metadata;

/// <summary>
/// Represents a database trigger on a table.
/// </summary>
/// <remarks>
/// <para>
/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
/// </para>
/// </remarks>
public interface IMutableTrigger : IReadOnlyTrigger, IMutableAnnotatable
{
/// <summary>
/// Gets the <see cref="IMutableEntityType" /> on which this trigger is defined.
/// </summary>
new IMutableEntityType EntityType { get; }
}
28 changes: 28 additions & 0 deletions src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Metadata;

/// <summary>
/// Represents a database trigger on a table.
/// </summary>
/// <remarks>
/// <para>
/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
/// </para>
/// </remarks>
public interface IReadOnlyTrigger : IReadOnlyAnnotatable
{
/// <summary>
/// Gets the name of the trigger in the database.
/// </summary>
string Name { get; }

/// <summary>
/// Gets the entity type on which this trigger is defined.
/// </summary>
IReadOnlyEntityType EntityType { get; }
}
23 changes: 23 additions & 0 deletions src/EFCore.Relational/Metadata/ITrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Metadata;

/// <summary>
/// Represents a database trigger on a table.
/// </summary>
/// <remarks>
/// <para>
/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-triggers">Database triggers</see> for more information and examples.
/// </para>
/// </remarks>
public interface ITrigger : IReadOnlyTrigger, IAnnotatable
{
/// <summary>
/// Gets the entity type on which this trigger is defined.
/// </summary>
new IEntityType EntityType { get; }
}
Loading

0 comments on commit 466fe62

Please sign in to comment.