diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
index bf7f36a7b6b..11144e254e8 100644
--- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
+++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
@@ -923,7 +923,7 @@ private void GenerateTableMapping(
if (hasOverrides)
{
- GenerateOverrides("t", entityType, table!.Value, stringBuilder);
+ GeneratePropertyOverrides("t", entityType, table!.Value, stringBuilder);
}
}
@@ -959,7 +959,7 @@ private void GenerateSplitTableMapping(
using (stringBuilder.Indent())
{
GenerateTriggers("t", entityType, table.Name, table.Schema, stringBuilder);
- GenerateOverrides("t", entityType, table, stringBuilder);
+ GeneratePropertyOverrides("t", entityType, table, stringBuilder);
GenerateEntityTypeMappingFragmentAnnotations("t", fragment, stringBuilder);
}
@@ -1023,7 +1023,7 @@ private void GenerateViewMapping(string entityTypeBuilderName, IEntityType entit
using (stringBuilder.Indent())
{
- GenerateOverrides("v", entityType, view!.Value, stringBuilder);
+ GeneratePropertyOverrides("v", entityType, view!.Value, stringBuilder);
}
stringBuilder.Append("}");
@@ -1055,7 +1055,7 @@ private void GenerateSplitViewMapping(
using (stringBuilder.Indent())
{
- GenerateOverrides("v", entityType, fragment.StoreObject, stringBuilder);
+ GeneratePropertyOverrides("v", entityType, fragment.StoreObject, stringBuilder);
GenerateEntityTypeMappingFragmentAnnotations("v", fragment, stringBuilder);
}
@@ -1266,7 +1266,7 @@ protected virtual void GenerateTriggerAnnotations(
/// The entity type.
/// The store object identifier.
/// The builder code is added to.
- protected virtual void GenerateOverrides(
+ protected virtual void GeneratePropertyOverrides(
string tableBuilderName,
IEntityType entityType,
StoreObjectIdentifier storeObject,
@@ -1277,7 +1277,7 @@ protected virtual void GenerateOverrides(
var overrides = property.FindOverrides(storeObject);
if (overrides != null)
{
- GenerateOverride(tableBuilderName, overrides, stringBuilder);
+ GeneratePropertyOverride(tableBuilderName, overrides, stringBuilder);
}
}
}
@@ -1288,7 +1288,7 @@ protected virtual void GenerateOverrides(
/// The name of the table builder variable.
/// The overrides.
/// The builder code is added to.
- protected virtual void GenerateOverride(
+ protected virtual void GeneratePropertyOverride(
string tableBuilderName,
IRelationalPropertyOverrides overrides,
IndentedStringBuilder stringBuilder)
@@ -1301,7 +1301,7 @@ protected virtual void GenerateOverride(
// Note that GenerateAnnotations below does the corresponding decrement
stringBuilder.IncrementIndent();
- if (overrides.ColumnNameOverridden)
+ if (overrides.IsColumnNameOverridden)
{
stringBuilder
.AppendLine()
diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
index 42aaa626e22..b3b5753082a 100644
--- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
+++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
@@ -31,6 +31,9 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator
RelationalAnnotationNames.Triggers,
RelationalAnnotationNames.Sequences,
RelationalAnnotationNames.DbFunctions,
+ RelationalAnnotationNames.DeleteStoredProcedure,
+ RelationalAnnotationNames.InsertStoredProcedure,
+ RelationalAnnotationNames.UpdateStoredProcedure,
RelationalAnnotationNames.MappingFragments,
RelationalAnnotationNames.RelationalOverrides
};
diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
index 99f4681d8c7..1ea54b25765 100644
--- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
+++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
@@ -503,7 +503,7 @@ private void Create(
AppendLiteral(storeObject, mainBuilder, code);
mainBuilder.AppendLine(",")
- .Append(code.Literal(overrides.ColumnNameOverridden)).AppendLine(",")
+ .Append(code.Literal(overrides.IsColumnNameOverridden)).AppendLine(",")
.Append(code.Literal(overrides.ColumnName)).AppendLine(");").DecrementIndent();
CreateAnnotations(
diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
index 904163e6c40..d3ef709703b 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
@@ -97,7 +97,7 @@ private enum Id
DuplicateColumnOrders,
ForeignKeyTpcPrincipalWarning,
TpcStoreGeneratedIdentityWarning,
- KeyUnmappedProperties,
+ KeyPropertiesNotMappedToTable,
// Update events
BatchReadyForExecution = CoreEventId.RelationalBaseId + 700,
@@ -825,8 +825,8 @@ private static EventId MakeValidationId(Id id)
/// This event uses the payload when used with a .
///
///
- public static readonly EventId KeyUnmappedProperties =
- MakeValidationId(Id.KeyUnmappedProperties);
+ public static readonly EventId KeyPropertiesNotMappedToTable =
+ MakeValidationId(Id.KeyPropertiesNotMappedToTable);
///
/// A foreign key specifies properties which don't map to the related tables.
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
index 93bf91d2cb6..e01d2271d76 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
@@ -2775,15 +2775,15 @@ private static string NamedIndexPropertiesMappedToNonOverlappingTables(EventDefi
}
///
- /// Logs the event.
+ /// Logs the event.
///
/// The diagnostics logger to use.
/// The foreign key.
- public static void KeyUnmappedProperties(
+ public static void KeyPropertiesNotMappedToTable(
this IDiagnosticsLogger diagnostics,
IKey key)
{
- var definition = RelationalResources.LogKeyUnmappedProperties(diagnostics);
+ var definition = RelationalResources.LogKeyPropertiesNotMappedToTable(diagnostics);
if (diagnostics.ShouldLog(definition))
{
@@ -2798,14 +2798,14 @@ public static void KeyUnmappedProperties(
{
var eventData = new KeyEventData(
definition,
- KeyUnmappedProperties,
+ KeyPropertiesNotMappedToTable,
key);
diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
}
}
- private static string KeyUnmappedProperties(EventDefinitionBase definition, EventData payload)
+ private static string KeyPropertiesNotMappedToTable(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition)definition;
var p = (KeyEventData)payload;
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
index 56787f2dafe..5a928d4c42f 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
@@ -536,7 +536,7 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- public EventDefinitionBase? LogKeyUnmappedProperties;
+ public EventDefinitionBase? LogKeyPropertiesNotMappedToTable;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.UseSproc.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.UseSproc.cs
new file mode 100644
index 00000000000..b07449295a9
--- /dev/null
+++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.UseSproc.cs
@@ -0,0 +1,970 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore;
+
+///
+/// Relational database specific extension methods for .
+///
+///
+/// See Modeling entity types and relationships for more information and examples.
+///
+public static partial class RelationalEntityTypeBuilderExtensions
+{
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder UpdateUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ null,
+ null,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder UpdateUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ null,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder UpdateUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ string? schema,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ schema,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder UpdateUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ null,
+ null,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder UpdateUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ null,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder UpdateUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ string? schema,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ schema,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder UpdateUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ null,
+ null,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder UpdateUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ null,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder UpdateUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ string? schema,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ schema,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder UpdateUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ null,
+ null,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder UpdateUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ null,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder UpdateUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ string? schema,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ schema,
+ StoreObjectType.UpdateStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for updates when targeting a relational database.
+ ///
+ /// The builder for the entity type being configured.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The builder instance if the configuration was applied, otherwise.
+ ///
+ public static IConventionStoredProcedureBuilder? UpdateUsingStoredProcedure(
+ this IConventionEntityTypeBuilder entityTypeBuilder,
+ bool fromDataAnnotation = false)
+ => InternalStoredProcedureBuilder.HasStoredProcedure(
+ entityTypeBuilder.Metadata, StoreObjectType.UpdateStoredProcedure, fromDataAnnotation);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder DeleteUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ null,
+ null,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder DeleteUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ null,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder DeleteUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ string? schema,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ schema,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder DeleteUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ null,
+ null,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder DeleteUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ null,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder DeleteUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ string? schema,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ schema,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder DeleteUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ null,
+ null,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder DeleteUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ null,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder DeleteUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ string? schema,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ schema,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder DeleteUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ null,
+ null,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder DeleteUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ null,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder DeleteUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ string? schema,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ schema,
+ StoreObjectType.DeleteStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for deletes when targeting a relational database.
+ ///
+ /// The builder for the entity type being configured.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The builder instance if the configuration was applied, otherwise.
+ ///
+ public static IConventionStoredProcedureBuilder? DeleteUsingStoredProcedure(
+ this IConventionEntityTypeBuilder entityTypeBuilder,
+ bool fromDataAnnotation = false)
+ => InternalStoredProcedureBuilder.HasStoredProcedure(
+ entityTypeBuilder.Metadata, StoreObjectType.DeleteStoredProcedure, fromDataAnnotation);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder InsertUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ null,
+ null,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder InsertUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ null,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder InsertUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ string? schema,
+ Action buildAction)
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ schema,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder InsertUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ null,
+ null,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder InsertUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ null,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type being configured.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static EntityTypeBuilder InsertUsingStoredProcedure(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ string? schema,
+ Action> buildAction)
+ where TEntity : class
+ => UseStoredProcedure(
+ entityTypeBuilder,
+ name,
+ schema,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder InsertUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ null,
+ null,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder InsertUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ null,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder InsertUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ string? schema,
+ Action buildAction)
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ schema,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder InsertUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ null,
+ null,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder InsertUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ null,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The builder for the entity type being configured.
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// An action that performs configuration of the stored procedure.
+ /// The same builder instance so that multiple calls can be chained.
+ public static OwnedNavigationBuilder InsertUsingStoredProcedure(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ string? schema,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ => UseStoredProcedure(
+ ownedNavigationBuilder,
+ name,
+ schema,
+ StoreObjectType.InsertStoredProcedure,
+ buildAction);
+
+ ///
+ /// Configures the stored procedure that the entity type would use for inserts when targeting a relational database.
+ ///
+ /// The builder for the entity type being configured.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The builder instance if the configuration was applied, otherwise.
+ ///
+ public static IConventionStoredProcedureBuilder? InsertUsingStoredProcedure(
+ this IConventionEntityTypeBuilder entityTypeBuilder,
+ bool fromDataAnnotation = false)
+ => InternalStoredProcedureBuilder.HasStoredProcedure(
+ entityTypeBuilder.Metadata, StoreObjectType.InsertStoredProcedure, fromDataAnnotation);
+
+ private static EntityTypeBuilder UseStoredProcedure(
+ EntityTypeBuilder entityTypeBuilder,
+ string? name,
+ string? schema,
+ StoreObjectType sprocType,
+ Action buildAction)
+ {
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ var sprocBuilder = InternalStoredProcedureBuilder.HasStoredProcedure(
+ entityTypeBuilder.Metadata, sprocType, name, schema);
+ buildAction(new(sprocBuilder.Metadata, entityTypeBuilder));
+
+ return entityTypeBuilder;
+ }
+
+ private static EntityTypeBuilder UseStoredProcedure(
+ EntityTypeBuilder entityTypeBuilder,
+ string? name,
+ string? schema,
+ StoreObjectType sprocType,
+ Action> buildAction)
+ where TEntity : class
+ {
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ var sprocBuilder = InternalStoredProcedureBuilder.HasStoredProcedure(
+ entityTypeBuilder.Metadata, sprocType, name, schema);
+ buildAction(new(sprocBuilder.Metadata, entityTypeBuilder));
+
+ return entityTypeBuilder;
+ }
+
+ private static OwnedNavigationBuilder UseStoredProcedure(
+ OwnedNavigationBuilder ownedNavigationBuilder,
+ string? name,
+ string? schema,
+ StoreObjectType sprocType,
+ Action buildAction)
+ {
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ var sprocBuilder = InternalStoredProcedureBuilder.HasStoredProcedure(
+ ownedNavigationBuilder.OwnedEntityType, sprocType, name, schema);
+ buildAction(new(sprocBuilder.Metadata, ownedNavigationBuilder));
+
+ return ownedNavigationBuilder;
+ }
+
+ private static OwnedNavigationBuilder UseStoredProcedure(
+ OwnedNavigationBuilder ownedNavigationBuilder,
+ string? name,
+ string? schema,
+ StoreObjectType sprocType,
+ Action> buildAction)
+ where TOwnerEntity : class
+ where TDependentEntity : class
+ {
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ var sprocBuilder = InternalStoredProcedureBuilder.HasStoredProcedure(
+ ownedNavigationBuilder.OwnedEntityType, sprocType, name, schema);
+ buildAction(new(sprocBuilder.Metadata, ownedNavigationBuilder));
+
+ return ownedNavigationBuilder;
+ }
+}
diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
index c17b3127d68..c68b51e8a07 100644
--- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
@@ -241,8 +241,8 @@ public static void SetSchema(this IMutableEntityType entityType, string? value)
/// Returns the name of the view to which the entity type is mapped prepended by the schema
/// or if not mapped to a view.
///
- /// The entity type to get the table name for.
- /// The name of the table to which the entity type is mapped prepended by the schema.
+ /// The entity type to get the view name for.
+ /// The name of the view to which the entity type is mapped prepended by the schema.
public static string? GetSchemaQualifiedViewName(this IReadOnlyEntityType entityType)
{
var viewName = entityType.GetViewName();
@@ -506,8 +506,8 @@ public static void SetSqlQuery(this IMutableEntityType entityType, string? name)
///
/// Returns the SQL string mappings.
///
- /// The entity type to get the function mappings for.
- /// The functions to which the entity type is mapped.
+ /// The entity type to get the SQL string mappings for.
+ /// The SQL string to which the entity type is mapped.
public static IEnumerable GetSqlQueryMappings(this IEntityType entityType)
=> (IEnumerable?)entityType.FindRuntimeAnnotationValue(
RelationalAnnotationNames.SqlQueryMappings)
@@ -573,7 +573,248 @@ public static IEnumerable GetFunctionMappings(this IEntityType
RelationalAnnotationNames.FunctionMappings)
?? Enumerable.Empty();
- #endregion Function mapping
+ #endregion
+
+ #region SProc mapping
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for deletes
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IReadOnlyStoredProcedure? GetDeleteStoredProcedure(this IReadOnlyEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.DeleteStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for deletes
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IMutableStoredProcedure? GetDeleteStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.DeleteStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for deletes
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IConventionStoredProcedure? GetDeleteStoredProcedure(this IConventionEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.DeleteStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for deletes
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IStoredProcedure? GetDeleteStoredProcedure(this IEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.DeleteStoredProcedure);
+
+ ///
+ /// Maps the entity type to a stored procedure for deletes.
+ ///
+ /// The entity type.
+ /// The new stored procedure.
+ public static IMutableStoredProcedure SetDeleteStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.SetStoredProcedure(entityType, StoreObjectType.DeleteStoredProcedure);
+
+ ///
+ /// Maps the entity type to a stored procedure for deletes.
+ ///
+ /// The entity type.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The new stored procedure.
+ public static IConventionStoredProcedure? SetDeleteStoredProcedure(
+ this IConventionEntityType entityType,
+ bool fromDataAnnotation = false)
+ => StoredProcedure.SetStoredProcedure(entityType, StoreObjectType.DeleteStoredProcedure, fromDataAnnotation);
+
+ ///
+ /// Removes the mapped delete stored procedure for this entity type.
+ ///
+ /// The entity type.
+ /// The removed stored procedure.
+ public static IMutableStoredProcedure? RemoveDeleteStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.RemoveStoredProcedure(entityType, StoreObjectType.DeleteStoredProcedure);
+
+ ///
+ /// Removes the mapped delete stored procedure for this entity type.
+ ///
+ /// The entity type.
+ /// The removed stored procedure.
+ public static IConventionStoredProcedure? RemoveDeleteStoredProcedure(this IConventionEntityType entityType)
+ => StoredProcedure.RemoveStoredProcedure(entityType, StoreObjectType.DeleteStoredProcedure);
+
+ ///
+ /// Gets the for the delete stored procedure.
+ ///
+ /// The entity type to find configuration source for.
+ /// The for the delete stored procedure.
+ public static ConfigurationSource? GetDeleteStoredProcedureConfigurationSource(this IConventionEntityType entityType)
+ => StoredProcedure.GetStoredProcedureConfigurationSource(entityType, StoreObjectType.DeleteStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for inserts
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IReadOnlyStoredProcedure? GetInsertStoredProcedure(this IReadOnlyEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.InsertStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for inserts
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IMutableStoredProcedure? GetInsertStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.InsertStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for inserts
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IConventionStoredProcedure? GetInsertStoredProcedure(this IConventionEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.InsertStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for inserts
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IStoredProcedure? GetInsertStoredProcedure(this IEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.InsertStoredProcedure);
+
+ ///
+ /// Maps the entity type to a stored procedure for inserts.
+ ///
+ /// The entity type.
+ /// The new stored procedure.
+ public static IMutableStoredProcedure SetInsertStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.SetStoredProcedure(entityType, StoreObjectType.InsertStoredProcedure);
+
+ ///
+ /// Maps the entity type to a stored procedure for inserts.
+ ///
+ /// The entity type.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The new stored procedure.
+ public static IConventionStoredProcedure? SetInsertStoredProcedure(
+ this IConventionEntityType entityType,
+ bool fromDataAnnotation = false)
+ => StoredProcedure.SetStoredProcedure(entityType, StoreObjectType.InsertStoredProcedure, fromDataAnnotation);
+
+ ///
+ /// Removes the mapped insert stored procedure for this entity type.
+ ///
+ /// The entity type.
+ /// The removed stored procedure.
+ public static IMutableStoredProcedure? RemoveInsertStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.RemoveStoredProcedure(entityType, StoreObjectType.InsertStoredProcedure);
+
+ ///
+ /// Removes the mapped insert stored procedure for this entity type.
+ ///
+ /// The entity type.
+ /// The removed stored procedure.
+ public static IConventionStoredProcedure? RemoveInsertStoredProcedure(this IConventionEntityType entityType)
+ => StoredProcedure.RemoveStoredProcedure(entityType, StoreObjectType.InsertStoredProcedure);
+
+ ///
+ /// Gets the for the insert stored procedure.
+ ///
+ /// The entity type.
+ /// The for the insert stored procedure.
+ public static ConfigurationSource? GetInsertStoredProcedureConfigurationSource(this IConventionEntityType entityType)
+ => StoredProcedure.GetStoredProcedureConfigurationSource(entityType, StoreObjectType.InsertStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for updates
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IReadOnlyStoredProcedure? GetUpdateStoredProcedure(this IReadOnlyEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for updates
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IMutableStoredProcedure? GetUpdateStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for updates
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IConventionStoredProcedure? GetUpdateStoredProcedure(this IConventionEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ ///
+ /// Returns the stored procedure to which the entity type is mapped for updates
+ /// or if not mapped to a stored procedure.
+ ///
+ /// The entity type.
+ /// The stored procedure to which the entity type is mapped.
+ public static IStoredProcedure? GetUpdateStoredProcedure(this IEntityType entityType)
+ => StoredProcedure.FindStoredProcedure(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ ///
+ /// Maps the entity type to a stored procedure for updates.
+ ///
+ /// The entity type.
+ /// The new stored procedure.
+ public static IMutableStoredProcedure SetUpdateStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.SetStoredProcedure(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ ///
+ /// Maps the entity type to a stored procedure for updates.
+ ///
+ /// The entity type.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The new stored procedure.
+ public static IConventionStoredProcedure? SetUpdateStoredProcedure(
+ this IConventionEntityType entityType,
+ bool fromDataAnnotation = false)
+ => StoredProcedure.SetStoredProcedure(entityType, StoreObjectType.UpdateStoredProcedure, fromDataAnnotation);
+
+ ///
+ /// Removes the mapped update stored procedure for this entity type.
+ ///
+ /// The entity type.
+ /// The removed stored procedure.
+ public static IMutableStoredProcedure? RemoveUpdateStoredProcedure(this IMutableEntityType entityType)
+ => StoredProcedure.RemoveStoredProcedure(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ ///
+ /// Removes the mapped update stored procedure for this entity type.
+ ///
+ /// The entity type.
+ /// The removed stored procedure.
+ public static IConventionStoredProcedure? RemoveUpdateStoredProcedure(this IConventionEntityType entityType)
+ => StoredProcedure.RemoveStoredProcedure(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ ///
+ /// Gets the for the update stored procedure.
+ ///
+ /// The entity type to find configuration source for.
+ /// The for the update stored procedure.
+ public static ConfigurationSource? GetUpdateStoredProcedureConfigurationSource(this IConventionEntityType entityType)
+ => StoredProcedure.GetStoredProcedureConfigurationSource(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ #endregion
#region Check constraint
@@ -828,6 +1069,8 @@ public static void SetComment(this IMutableEntityType entityType, string? commen
#endregion Comment
+ #region Mapping Fragments
+
///
///
/// Returns all configured entity type mapping fragments.
@@ -1097,6 +1340,10 @@ public static IConventionEntityTypeMappingFragment GetOrCreateMappingFragment(
in StoreObjectIdentifier storeObject)
=> EntityTypeMappingFragment.Remove((IMutableEntityType)entityType, storeObject);
+ #endregion
+
+ #region Table sharing
+
///
/// Gets the foreign keys for the given entity type that point to other entity types
/// sharing the same table-like store object.
@@ -1169,6 +1416,8 @@ public static IEnumerable FindRowInternalForeignKeys(
// ReSharper disable once RedundantCast
=> ((IReadOnlyEntityType)entityType).FindRowInternalForeignKeys(storeObject).Cast();
+ #endregion
+
#region IsTableExcludedFromMigrations
///
diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
index d17904d8d4e..921b60e5cc7 100644
--- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
@@ -52,7 +52,7 @@ public static string GetColumnName(this IReadOnlyProperty property)
public static string? GetColumnName(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject)
{
var overrides = property.FindOverrides(storeObject);
- if (overrides?.ColumnNameOverridden == true)
+ if (overrides?.IsColumnNameOverridden == true)
{
return overrides.ColumnName;
}
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index d8bb74f0d5a..53b5bd3d93a 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -53,6 +53,7 @@ public override void Validate(IModel model, IDiagnosticsLogger
+ /// Validates the mapping/configuration of stored procedures in the model.
+ ///
+ /// The model to validate.
+ /// The logger to use.
+ protected virtual void ValidateStoredProcedures(
+ IModel model,
+ IDiagnosticsLogger logger)
+ {
+ var storedProcedures = new Dictionary>();
+ foreach (var entityType in model.GetEntityTypes())
+ {
+ var mappingStrategy = entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy;
+ var deleteStoredProcedure = entityType.GetDeleteStoredProcedure();
+ if (deleteStoredProcedure != null)
+ {
+ AddSproc(StoreObjectType.DeleteStoredProcedure, entityType, storedProcedures);
+ ValidateSproc(deleteStoredProcedure, mappingStrategy);
+ }
+
+ var insertStoredProcedure = entityType.GetInsertStoredProcedure();
+ if (insertStoredProcedure != null)
+ {
+ AddSproc(StoreObjectType.InsertStoredProcedure, entityType, storedProcedures);
+ ValidateSproc(insertStoredProcedure, mappingStrategy);
+ }
+
+ var updateStoredProcedure = entityType.GetUpdateStoredProcedure();
+ if (updateStoredProcedure != null)
+ {
+ AddSproc(StoreObjectType.UpdateStoredProcedure, entityType, storedProcedures);
+ ValidateSproc(updateStoredProcedure, mappingStrategy);
+ }
+ }
+
+ foreach (var (sproc, mappedTypes) in storedProcedures)
+ {
+ foreach (var mappedType in mappedTypes)
+ {
+ if (mappedTypes[0].GetRootType() != mappedType.GetRootType())
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureTableSharing(
+ mappedTypes[0].DisplayName(),
+ mappedType.DisplayName(),
+ sproc.DisplayName()));
+ }
+ }
+ }
+
+ static void AddSproc(
+ StoreObjectType storedProcedureType,
+ IEntityType entityType,
+ Dictionary> storedProcedures)
+ {
+ var sprocId = StoreObjectIdentifier.Create(entityType, storedProcedureType);
+ if (sprocId == null)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureNoName(
+ entityType.DisplayName(), storedProcedureType));
+ }
+
+ if (!storedProcedures.TryGetValue(sprocId.Value, out var mappedTypes))
+ {
+ mappedTypes = new List();
+ storedProcedures[sprocId.Value] = mappedTypes;
+ }
+
+ mappedTypes.Add(entityType);
+ }
+ }
+
+ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy)
+ {
+ var entityType = sproc.EntityType;
+ var storeObjectIdentifier = ((StoredProcedure)sproc).CreateIdentifier()!.Value;
+
+ var primaryKey = entityType.FindPrimaryKey();
+ if (primaryKey == null)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureKeyless(
+ entityType.DisplayName(), storeObjectIdentifier.DisplayName()));
+ }
+
+ var properties = entityType.GetDeclaredProperties().ToDictionary(p => p.Name);
+ if (mappingStrategy == RelationalAnnotationNames.TphMappingStrategy)
+ {
+ if (entityType.BaseType != null)
+ {
+ return;
+ }
+
+ foreach (var property in entityType.GetDerivedProperties())
+ {
+ properties.Add(property.Name, property);
+ }
+ }
+ else if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy)
+ {
+ if (entityType.BaseType != null)
+ {
+ foreach (var property in entityType.BaseType.GetProperties())
+ {
+ properties.Add(property.Name, property);
+ }
+ }
+ }
+ else if (mappingStrategy == RelationalAnnotationNames.TptMappingStrategy)
+ {
+ if (entityType.BaseType != null)
+ {
+ foreach (var property in primaryKey.Properties)
+ {
+ properties.Add(property.Name, property);
+ }
+ }
+ }
+
+ foreach (var resultColumn in sproc.ResultColumns)
+ {
+ if (!properties!.TryGetValue(resultColumn, out var property))
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureResultColumnNotFound(
+ resultColumn, entityType.DisplayName(), storeObjectIdentifier.DisplayName()));
+ }
+
+ switch (storeObjectIdentifier.StoreObjectType)
+ {
+ case StoreObjectType.InsertStoredProcedure:
+ if ((property.ValueGenerated & ValueGenerated.OnAdd) == 0)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureResultColumnNotGenerated(
+ entityType.DisplayName(), resultColumn, storeObjectIdentifier.DisplayName()));
+ }
+
+ break;
+ case StoreObjectType.DeleteStoredProcedure:
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureResultColumnDelete(
+ entityType.DisplayName(), resultColumn, storeObjectIdentifier.DisplayName()));
+ case StoreObjectType.UpdateStoredProcedure:
+ if ((property.ValueGenerated & ValueGenerated.OnUpdate) == 0)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureResultColumnNotGenerated(
+ entityType.DisplayName(), resultColumn, storeObjectIdentifier.DisplayName()));
+ }
+
+ break;
+ }
+ }
+
+ foreach (var parameter in sproc.Parameters)
+ {
+ if (!properties!.TryGetAndRemove(parameter, out IProperty property))
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureParameterNotFound(
+ parameter, entityType.DisplayName(), storeObjectIdentifier.DisplayName()));
+ }
+
+ if (storeObjectIdentifier.StoreObjectType == StoreObjectType.DeleteStoredProcedure
+ && !property.IsPrimaryKey()
+ && !property.IsConcurrencyToken)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureDeleteNonKeyProperty(
+ entityType.DisplayName(), parameter, storeObjectIdentifier.DisplayName()));
+ }
+ }
+
+ foreach (var resultColumn in sproc.ResultColumns)
+ {
+ properties!.Remove(resultColumn);
+ }
+
+ if (properties != null
+ && properties.Count > 0)
+ {
+ foreach (var property in properties.Values.ToList())
+ {
+ switch (storeObjectIdentifier.StoreObjectType)
+ {
+ case StoreObjectType.InsertStoredProcedure:
+ if ((property.ValueGenerated & ValueGenerated.OnAdd) == 0
+ && property.GetBeforeSaveBehavior() != PropertySaveBehavior.Save)
+ {
+ properties.Remove(property.Name);
+ }
+
+ break;
+ case StoreObjectType.DeleteStoredProcedure:
+ if (!property.IsPrimaryKey()
+ && !property.IsConcurrencyToken)
+ {
+ properties.Remove(property.Name);
+ }
+
+ break;
+ case StoreObjectType.UpdateStoredProcedure:
+ if (!property.IsPrimaryKey()
+ && !property.IsConcurrencyToken
+ && (property.ValueGenerated & ValueGenerated.OnUpdate) == 0
+ && property.GetAfterSaveBehavior() != PropertySaveBehavior.Save)
+ {
+ properties.Remove(property.Name);
+ }
+
+ break;
+ }
+ }
+
+ if (properties.Count > 0)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedurePropertiesNotMapped(
+ entityType.DisplayName(),
+ storeObjectIdentifier.DisplayName(),
+ properties.Values.Format(false)));
+ }
+ }
+ }
+
///
/// Validates the mapping/configuration of properties in the model.
///
@@ -1306,7 +1534,11 @@ protected override void ValidateInheritanceMapping(
ValidateMappingStrategy(entityType, mappingStrategy);
var storeObject = entityType.GetSchemaQualifiedTableName()
?? entityType.GetSchemaQualifiedViewName()
- ?? entityType.GetFunctionName();
+ ?? entityType.GetFunctionName()
+ ?? entityType.GetSqlQuery()
+ ?? entityType.GetInsertStoredProcedure()?.GetSchemaQualifiedName()
+ ?? entityType.GetDeleteStoredProcedure()?.GetSchemaQualifiedName()
+ ?? entityType.GetUpdateStoredProcedure()?.GetSchemaQualifiedName();
if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy
&& !entityType.ClrType.IsInstantiable()
&& storeObject != null)
@@ -1343,8 +1575,13 @@ protected override void ValidateInheritanceMapping(
RelationalStrings.NonTphMappingStrategy(mappingStrategy, entityType.DisplayName()));
}
- ValidateTphMapping(entityType, forTables: false);
- ValidateTphMapping(entityType, forTables: true);
+ ValidateTphMapping(entityType, StoreObjectType.Table);
+ ValidateTphMapping(entityType, StoreObjectType.View);
+ ValidateTphMapping(entityType, StoreObjectType.Function);
+ ValidateTphMapping(entityType, StoreObjectType.InsertStoredProcedure);
+ ValidateTphMapping(entityType, StoreObjectType.DeleteStoredProcedure);
+ ValidateTphMapping(entityType, StoreObjectType.UpdateStoredProcedure);
+
ValidateDiscriminatorValues(entityType);
}
else
@@ -1365,8 +1602,11 @@ protected override void ValidateInheritanceMapping(
RelationalStrings.KeylessMappingStrategy(mappingStrategy ?? RelationalAnnotationNames.TptMappingStrategy, entityType.DisplayName()));
}
- ValidateNonTphMapping(entityType, forTables: false);
- ValidateNonTphMapping(entityType, forTables: true);
+ ValidateNonTphMapping(entityType, StoreObjectType.Table);
+ ValidateNonTphMapping(entityType, StoreObjectType.View);
+ ValidateNonTphMapping(entityType, StoreObjectType.InsertStoredProcedure);
+ ValidateNonTphMapping(entityType, StoreObjectType.DeleteStoredProcedure);
+ ValidateNonTphMapping(entityType, StoreObjectType.UpdateStoredProcedure);
var derivedTypes = entityType.GetDerivedTypesInclusive().ToList();
var discriminatorValues = new Dictionary();
@@ -1376,6 +1616,7 @@ protected override void ValidateInheritanceMapping(
{
continue;
}
+
var discriminatorValue = derivedType.GetDiscriminatorValue();
if (discriminatorValue is not string valueString)
{
@@ -1414,32 +1655,41 @@ protected virtual void ValidateMappingStrategy(IEntityType entityType, string? m
};
}
- private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTables)
+ private static void ValidateNonTphMapping(IEntityType rootEntityType, StoreObjectType storeObjectType)
{
var isTpc = rootEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy;
- var derivedTypes = new Dictionary<(string, string?), IEntityType>();
+ var derivedTypes = new Dictionary();
foreach (var entityType in rootEntityType.GetDerivedTypesInclusive())
{
- var name = forTables ? entityType.GetTableName() : entityType.GetViewName();
- if (name == null)
+ var storeObject = StoreObjectIdentifier.Create(entityType, storeObjectType);
+ if (storeObject == null)
{
continue;
}
- var schema = forTables ? entityType.GetSchema() : entityType.GetViewSchema();
- if (derivedTypes.TryGetValue((name, schema), out var otherType))
+ if (derivedTypes.TryGetValue(storeObject.Value, out var otherType))
{
- throw new InvalidOperationException(
- forTables
- ? RelationalStrings.NonTphTableClash(
- entityType.DisplayName(), otherType.DisplayName(), entityType.GetSchemaQualifiedTableName())
- : RelationalStrings.NonTphViewClash(
- entityType.DisplayName(), otherType.DisplayName(), entityType.GetSchemaQualifiedViewName()));
+ switch (storeObjectType)
+ {
+ case StoreObjectType.Table:
+ throw new InvalidOperationException(
+ RelationalStrings.NonTphTableClash(
+ entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));
+ case StoreObjectType.View:
+ throw new InvalidOperationException(
+ RelationalStrings.NonTphViewClash(
+ entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));
+ case StoreObjectType.InsertStoredProcedure:
+ case StoreObjectType.DeleteStoredProcedure:
+ case StoreObjectType.UpdateStoredProcedure:
+ throw new InvalidOperationException(
+ RelationalStrings.NonTphStoredProcedureClash(
+ entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));
+ }
}
if (isTpc)
{
- var storeObject = StoreObjectIdentifier.Create(entityType, forTables ? StoreObjectType.Table : StoreObjectType.View)!;
var rowInternalFk = entityType.FindDeclaredReferencingRowInternalForeignKeys(storeObject.Value)
.FirstOrDefault();
if (rowInternalFk != null
@@ -1452,10 +1702,10 @@ private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTa
}
}
- derivedTypes[(name, schema)] = entityType;
+ derivedTypes[storeObject.Value] = entityType;
}
- var rootStoreObject = StoreObjectIdentifier.Create(rootEntityType, forTables ? StoreObjectType.Table : StoreObjectType.View);
+ var rootStoreObject = StoreObjectIdentifier.Create(rootEntityType, storeObjectType);
if (rootStoreObject == null)
{
return;
@@ -1466,47 +1716,69 @@ private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTa
&& rootEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
{
var derivedTypePair = derivedTypes.First(kv => kv.Value != rootEntityType);
- var (derivedName, derivedSchema) = derivedTypePair.Key;
throw new InvalidOperationException(RelationalStrings.TpcTableSharingDependent(
rootEntityType.DisplayName(),
rootStoreObject.Value.DisplayName(),
derivedTypePair.Value.DisplayName(),
- derivedSchema == null ? derivedName : $"{derivedSchema}.{derivedName}"));
+ derivedTypePair.Key.DisplayName()));
}
}
- private static void ValidateTphMapping(IEntityType rootEntityType, bool forTables)
+ private static void ValidateTphMapping(IEntityType rootEntityType, StoreObjectType storeObjectType)
{
- string? firstName = null;
- string? firstSchema = null;
- IEntityType? firstType = null;
- foreach (var entityType in rootEntityType.GetDerivedTypesInclusive())
+ var isSproc = storeObjectType == StoreObjectType.DeleteStoredProcedure
+ || storeObjectType == StoreObjectType.InsertStoredProcedure
+ || storeObjectType == StoreObjectType.UpdateStoredProcedure;
+ var rootSproc = isSproc ? StoredProcedure.GetDeclaredStoredProcedure(rootEntityType, storeObjectType) : null;
+ var rootId = StoreObjectIdentifier.Create(rootEntityType, storeObjectType);
+ foreach (var entityType in rootEntityType.GetDerivedTypes())
{
- var name = forTables ? entityType.GetTableName() : entityType.GetViewName();
- if (name == null)
+ var entityId = StoreObjectIdentifier.Create(entityType, storeObjectType);
+ if (entityId == null)
{
continue;
}
- if (firstType == null)
+ if (rootId == entityId)
{
- firstType = entityType;
- firstName = forTables ? firstType.GetTableName() : firstType.GetViewName();
- firstSchema = forTables ? firstType.GetSchema() : firstType.GetViewSchema();
+ if (rootSproc != null)
+ {
+ var sproc = StoredProcedure.GetDeclaredStoredProcedure(entityType, storeObjectType);
+ if (sproc != null
+ && sproc != rootSproc)
+ {
+ throw new InvalidOperationException(RelationalStrings.StoredProcedureTphDuplicate(
+ entityType.DisplayName(), rootEntityType.DisplayName(), rootId?.DisplayName()));
+ }
+ }
+
continue;
}
- var schema = forTables ? entityType.GetSchema() : entityType.GetViewSchema();
- if (name != firstName || schema != firstSchema)
+ switch (storeObjectType)
{
- throw new InvalidOperationException(
- forTables
- ? RelationalStrings.TphTableMismatch(
- entityType.DisplayName(), entityType.GetSchemaQualifiedTableName(),
- firstType.DisplayName(), firstType.GetSchemaQualifiedTableName())
- : RelationalStrings.TphViewMismatch(
- entityType.DisplayName(), entityType.GetSchemaQualifiedViewName(),
- firstType.DisplayName(), firstType.GetSchemaQualifiedViewName()));
+ case StoreObjectType.Table:
+ throw new InvalidOperationException(
+ RelationalStrings.TphTableMismatch(
+ entityType.DisplayName(), entityId.Value.DisplayName(),
+ rootEntityType.DisplayName(), rootId?.DisplayName()));
+ case StoreObjectType.View:
+ throw new InvalidOperationException(
+ RelationalStrings.TphViewMismatch(
+ entityType.DisplayName(), entityId.Value.DisplayName(),
+ rootEntityType.DisplayName(), rootId?.DisplayName()));
+ case StoreObjectType.Function:
+ throw new InvalidOperationException(
+ RelationalStrings.TphDbFunctionMismatch(
+ entityType.DisplayName(), entityId.Value.DisplayName(),
+ rootEntityType.DisplayName(), rootId?.DisplayName()));
+ case StoreObjectType.InsertStoredProcedure:
+ case StoreObjectType.DeleteStoredProcedure:
+ case StoreObjectType.UpdateStoredProcedure:
+ throw new InvalidOperationException(
+ RelationalStrings.TphStoredProcedureMismatch(
+ entityType.DisplayName(), entityId.Value.DisplayName(),
+ rootEntityType.DisplayName(), rootId?.DisplayName()));
}
}
}
@@ -1701,6 +1973,13 @@ protected virtual void ValidatePropertyOverrides(
RelationalStrings.FunctionOverrideMismatch(
entityType.DisplayName() + "." + property.Name,
storeObjectOverride.StoreObject.DisplayName()));
+ case StoreObjectType.InsertStoredProcedure:
+ case StoreObjectType.DeleteStoredProcedure:
+ case StoreObjectType.UpdateStoredProcedure:
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureOverrideMismatch(
+ entityType.DisplayName() + "." + property.Name,
+ storeObjectOverride.StoreObject.DisplayName()));
default:
throw new NotSupportedException(storeObject.StoreObjectType.ToString());
}
diff --git a/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
index 7a2c4097c23..8901db37e9f 100644
--- a/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
@@ -105,4 +105,34 @@ public virtual DbFunctionBuilder HasTranslation(Func
+ /// Returns an object that can be used to configure a parameter with the given name.
+ /// If no parameter with the given name exists, then a new parameter will be added.
+ ///
+ ///
+ /// See Database functions for more information and examples.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The builder to use for further parameter configuration.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual DbFunctionBuilder HasParameter(string name, Action buildAction)
+ => (DbFunctionBuilder)base.HasParameter(name, buildAction);
+
+ ///
+ /// Adds or updates an annotation on the database function. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual DbFunctionBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
}
diff --git a/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilderBase.cs b/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilderBase.cs
index 3e07be59518..ffaa6126514 100644
--- a/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilderBase.cs
+++ b/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilderBase.cs
@@ -107,6 +107,23 @@ public virtual DbFunctionBuilderBase IsBuiltIn(bool builtIn = true)
public virtual DbFunctionParameterBuilder HasParameter(string name)
=> new(Builder.HasParameter(name, ConfigurationSource.Explicit).Metadata);
+ ///
+ /// Returns an object that can be used to configure a parameter with the given name.
+ /// If no parameter with the given name exists, then a new parameter will be added.
+ ///
+ ///
+ /// See Database functions for more information and examples.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The builder to use for further parameter configuration.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual DbFunctionBuilderBase HasParameter(string name, Action buildAction)
+ {
+ buildAction(HasParameter(name));
+ return this;
+ }
+
#region Hidden System.Object members
///
diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs
new file mode 100644
index 00000000000..e120adc1c51
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs
@@ -0,0 +1,122 @@
+// 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;
+
+///
+/// Provides a simple API for configuring a .
+///
+///
+/// See Model building conventions for more information and examples.
+///
+public interface IConventionStoredProcedureBuilder : IConventionAnnotatableBuilder
+{
+ ///
+ /// The function being configured.
+ ///
+ new IConventionStoredProcedure Metadata { get; }
+
+ ///
+ /// Sets the name of the stored procedure.
+ ///
+ /// The name of the stored procedure in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ IConventionStoredProcedureBuilder? HasName(string? name, bool fromDataAnnotation = false);
+
+ ///
+ /// Sets the name and schema of the stored procedure.
+ ///
+ /// The name of the stored procedure in the database.
+ /// The schema of the stored procedure in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ IConventionStoredProcedureBuilder? HasName(string? name, string? schema, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given name can be set for the stored procedure.
+ ///
+ /// The name of the stored procedure in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the given name can be set for the stored procedure.
+ bool CanSetName(string? name, bool fromDataAnnotation = false);
+
+ ///
+ /// Sets the schema of the stored procedure.
+ ///
+ /// The schema of the stored procedure in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ IConventionStoredProcedureBuilder? HasSchema(string? schema, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given schema can be set for the stored procedure.
+ ///
+ /// The schema of the stored procedure in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the given schema can be set for the database function.
+ bool CanSetSchema(string? schema, bool fromDataAnnotation = false);
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property name.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ IConventionStoredProcedureBuilder? HasParameter(string propertyName, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether a parameter mapped to the given property can be used for stored procedure.
+ ///
+ /// The property name.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the parameter can be used for the stored procedure.
+ bool CanHaveParameter(string propertyName, bool fromDataAnnotation = false);
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ IConventionStoredProcedureBuilder? HasResultColumn(string propertyName, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether a column of the result mapped to the given property can be used for stored procedure.
+ ///
+ /// The property name.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the column of the result can be used for the stored procedure.
+ bool CanHaveResultColumn(string propertyName, bool fromDataAnnotation = false);
+
+ ///
+ /// Prevents automatically creating a transaction when executing this stored procedure.
+ ///
+ /// A value indicating whether the automatic transactions should be prevented.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ IConventionStoredProcedureBuilder? SuppressTransactions(bool suppress, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the transaction suppression can be set for stored procedure.
+ ///
+ /// A value indicating whether the automatic transactions should be prevented.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the column of the result can be used for the stored procedure.
+ bool CanSetSuppressTransactions(bool suppress, bool fromDataAnnotation = false);
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs
new file mode 100644
index 00000000000..9de2c4a730d
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs
@@ -0,0 +1,163 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a that an entity type is mapped to.
+///
+public class OwnedNavigationStoredProcedureBuilder :
+ IInfrastructure, IInfrastructure
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public OwnedNavigationStoredProcedureBuilder(
+ IMutableStoredProcedure sproc,
+ OwnedNavigationBuilder ownedNavigationBuilder)
+ {
+ Builder = ((StoredProcedure)sproc).Builder;
+ OwnedNavigationBuilder = ownedNavigationBuilder;
+ }
+
+ private OwnedNavigationBuilder OwnedNavigationBuilder { get; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual InternalStoredProcedureBuilder Builder { [DebuggerStepThrough] get; }
+
+ ///
+ IConventionStoredProcedureBuilder IInfrastructure.Instance
+ {
+ [DebuggerStepThrough]
+ get => Builder;
+ }
+
+ ///
+ /// The stored procedure being configured.
+ ///
+ public virtual IMutableStoredProcedure Metadata
+ => Builder.Metadata;
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasParameter(string propertyName)
+ {
+ Builder.HasParameter(propertyName, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasParameter(string propertyName, Action buildAction)
+ {
+ Builder.HasParameter(propertyName, ConfigurationSource.Explicit);
+ buildAction(new(((StoredProcedure)Metadata).CreateIdentifier()!.Value, CreatePropertyBuilder(propertyName)));
+ return this;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual PropertyBuilder CreatePropertyBuilder(string propertyName)
+ {
+ var entityType = OwnedNavigationBuilder.OwnedEntityType;
+ var property = entityType.FindProperty(propertyName);
+ if (property == null)
+ {
+ throw new InvalidOperationException(CoreStrings.PropertyNotFound(propertyName, entityType.DisplayName()));
+ }
+
+ return OwnedNavigationBuilder.Property(property.ClrType, propertyName);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual PropertyBuilder CreatePropertyBuilder(
+ Expression> propertyExpression)
+ {
+ var memberInfo = propertyExpression.GetMemberAccess();
+ return OwnedNavigationBuilder.Property(memberInfo.GetMemberType(), memberInfo.Name);
+ }
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasResultColumn(string propertyName)
+ {
+ Builder.HasResultColumn(propertyName, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// An action that performs configuration of the column.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasResultColumn(
+ string propertyName, Action buildAction)
+ {
+ Builder.HasResultColumn(propertyName, ConfigurationSource.Explicit);
+ buildAction(new(((StoredProcedure)Metadata).CreateIdentifier()!.Value, CreatePropertyBuilder(propertyName)));
+ return this;
+ }
+
+ ///
+ /// Prevents automatically creating a transaction when executing this stored procedure.
+ ///
+ /// A value indicating whether the automatic transactions should be prevented.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder SuppressTransactions(bool suppress = true)
+ {
+ Builder.SuppressTransactions(suppress, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Adds or updates an annotation on the stored procedure. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ OwnedNavigationBuilder IInfrastructure.Instance => OwnedNavigationBuilder;
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs
new file mode 100644
index 00000000000..1db167f39b9
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs
@@ -0,0 +1,159 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a that an entity type is mapped to.
+///
+/// The entity type owning the relationship.
+/// The dependent entity type of the relationship.
+public class OwnedNavigationStoredProcedureBuilder :
+ OwnedNavigationStoredProcedureBuilder, IInfrastructure>
+ where TOwnerEntity : class
+ where TDependentEntity : class
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public OwnedNavigationStoredProcedureBuilder(
+ IMutableStoredProcedure sproc,
+ OwnedNavigationBuilder ownedNavigationBuilder)
+ : base(sproc, ownedNavigationBuilder)
+ {
+ }
+
+ private OwnedNavigationBuilder OwnedNavigationBuilder
+ => (OwnedNavigationBuilder)((IInfrastructure)this)
+ .GetInfrastructure();
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationStoredProcedureBuilder HasParameter(string propertyName)
+ => (OwnedNavigationStoredProcedureBuilder)base.HasParameter(propertyName);
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationStoredProcedureBuilder HasParameter(
+ string propertyName, Action buildAction)
+ => (OwnedNavigationStoredProcedureBuilder)base.HasParameter(propertyName, buildAction);
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasParameter(
+ Expression> propertyExpression)
+ {
+ Builder.HasParameter(propertyExpression, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// An action that performs configuration of the parameter.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasParameter(
+ Expression> propertyExpression,
+ Action buildAction)
+ {
+ Builder.HasParameter(propertyExpression, ConfigurationSource.Explicit);
+ buildAction(new(((StoredProcedure)Metadata).CreateIdentifier()!.Value, CreatePropertyBuilder(propertyExpression)));
+ return this;
+ }
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationStoredProcedureBuilder HasResultColumn(string propertyName)
+ => (OwnedNavigationStoredProcedureBuilder)base.HasResultColumn(propertyName);
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// An action that performs configuration of the column.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationStoredProcedureBuilder HasResultColumn(
+ string propertyName, Action buildAction)
+ => (OwnedNavigationStoredProcedureBuilder)base.HasResultColumn(propertyName, buildAction);
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasResultColumn(
+ Expression> propertyExpression)
+ {
+ Builder.HasResultColumn(propertyExpression, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// An action that performs configuration of the column.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationStoredProcedureBuilder HasResultColumn(
+ Expression> propertyExpression,
+ Action buildAction)
+ {
+ Builder.HasResultColumn(propertyExpression, ConfigurationSource.Explicit);
+ buildAction(new(((StoredProcedure)Metadata).CreateIdentifier()!.Value, CreatePropertyBuilder(propertyExpression)));
+ return this;
+ }
+
+ ///
+ /// Prevents automatically creating a transaction when executing this stored procedure.
+ ///
+ /// A value indicating whether the automatic transactions should be prevented.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationStoredProcedureBuilder SuppressTransactions(bool suppress = true)
+ => (OwnedNavigationStoredProcedureBuilder)base.SuppressTransactions(suppress);
+
+ ///
+ /// Adds or updates an annotation on the stored procedure. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationStoredProcedureBuilder HasAnnotation(
+ string annotation, object? value)
+ => (OwnedNavigationStoredProcedureBuilder)base.HasAnnotation(annotation, value);
+
+ OwnedNavigationBuilder IInfrastructure>.Instance
+ => OwnedNavigationBuilder;
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder.cs
index 1dd69f117c4..d3da28e53db 100644
--- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder.cs
@@ -58,5 +58,35 @@ public OwnedNavigationTableValuedFunctionBuilder(
public new virtual OwnedNavigationTableValuedFunctionBuilder IsBuiltIn(bool builtIn = true)
=> (OwnedNavigationTableValuedFunctionBuilder)base.IsBuiltIn(builtIn);
+ ///
+ /// Returns an object that can be used to configure a parameter with the given name.
+ /// If no parameter with the given name exists, then a new parameter will be added.
+ ///
+ ///
+ /// See Database functions for more information and examples.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The builder to use for further parameter configuration.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationTableValuedFunctionBuilder HasParameter(string name, Action buildAction)
+ => (OwnedNavigationTableValuedFunctionBuilder)base.HasParameter(name, buildAction);
+
+ ///
+ /// Adds or updates an annotation on the database function. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual OwnedNavigationTableValuedFunctionBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
OwnedNavigationBuilder IInfrastructure.Instance => OwnedNavigationBuilder;
}
diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder``.cs
index 47a2041eb0a..1967edb424c 100644
--- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder``.cs
+++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder``.cs
@@ -65,6 +65,30 @@ private OwnedNavigationBuilder OwnedNavigationBu
public new virtual OwnedNavigationTableValuedFunctionBuilder IsBuiltIn(bool builtIn = true)
=> (OwnedNavigationTableValuedFunctionBuilder)base.IsBuiltIn(builtIn);
+ ///
+ /// Returns an object that can be used to configure a parameter with the given name.
+ /// If no parameter with the given name exists, then a new parameter will be added.
+ ///
+ ///
+ /// See Database functions for more information and examples.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The builder to use for further parameter configuration.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationTableValuedFunctionBuilder HasParameter(string name, Action buildAction)
+ => (OwnedNavigationTableValuedFunctionBuilder)base.HasParameter(name, buildAction);
+
+ ///
+ /// Adds or updates an annotation on the database function. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual OwnedNavigationTableValuedFunctionBuilder HasAnnotation(string annotation, object? value)
+ => (OwnedNavigationTableValuedFunctionBuilder)base.HasAnnotation(annotation, value);
+
OwnedNavigationBuilder IInfrastructure>.Instance
=> OwnedNavigationBuilder;
}
diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs
new file mode 100644
index 00000000000..880ad9aef4c
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs
@@ -0,0 +1,179 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a that an entity type is mapped to.
+///
+public class StoredProcedureBuilder : IInfrastructure, IInfrastructure
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public StoredProcedureBuilder(IMutableStoredProcedure sproc, EntityTypeBuilder entityTypeBuilder)
+ {
+ Builder = ((StoredProcedure)sproc).Builder;
+ EntityTypeBuilder = entityTypeBuilder;
+ }
+
+ private EntityTypeBuilder EntityTypeBuilder { get; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual InternalStoredProcedureBuilder Builder { [DebuggerStepThrough] get; }
+
+ ///
+ IConventionStoredProcedureBuilder IInfrastructure.Instance
+ {
+ [DebuggerStepThrough]
+ get => Builder;
+ }
+
+ ///
+ /// The stored procedure being configured.
+ ///
+ public virtual IMutableStoredProcedure Metadata
+ => Builder.Metadata;
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasParameter(string propertyName)
+ {
+ Builder.HasParameter(propertyName, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasParameter(
+ string propertyName, Action buildAction)
+ {
+ Builder.HasParameter(propertyName, ConfigurationSource.Explicit);
+ buildAction(new(((StoredProcedure)Metadata).CreateIdentifier()!.Value, CreatePropertyBuilder(propertyName)));
+ return this;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual PropertyBuilder CreatePropertyBuilder(string propertyName)
+ {
+ var entityType = EntityTypeBuilder.Metadata;
+ var property = entityType.FindProperty(propertyName);
+ if (property == null)
+ {
+ property = entityType.GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties())
+ .FirstOrDefault(p => p.Name == propertyName);
+ }
+
+ if (property == null)
+ {
+ throw new InvalidOperationException(CoreStrings.PropertyNotFound(propertyName, entityType.DisplayName()));
+ }
+
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ return new ModelBuilder(entityType.Model)
+#pragma warning restore EF1001 // Internal EF Core API usage.
+ .Entity(property.DeclaringEntityType.Name)
+ .Property(property.ClrType, propertyName);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual PropertyBuilder CreatePropertyBuilder(
+ Expression> propertyExpression)
+ where TDerivedEntity : class
+ {
+ var memberInfo = propertyExpression.GetMemberAccess();
+ var entityType = EntityTypeBuilder.Metadata;
+ var entityTypeBuilder = entityType.ClrType == typeof(TDerivedEntity)
+ ? EntityTypeBuilder
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ : new ModelBuilder(entityType.Model).Entity(typeof(TDerivedEntity));
+#pragma warning restore EF1001 // Internal EF Core API usage.
+
+ return entityTypeBuilder.Property(memberInfo.GetMemberType(), memberInfo.Name);
+ }
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasResultColumn(string propertyName)
+ {
+ Builder.HasResultColumn(propertyName, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// An action that performs configuration of the column.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasResultColumn(
+ string propertyName, Action buildAction)
+ {
+ Builder.HasResultColumn(propertyName, ConfigurationSource.Explicit);
+ buildAction(new(((StoredProcedure)Metadata).CreateIdentifier()!.Value, CreatePropertyBuilder(propertyName)));
+ return this;
+ }
+
+ ///
+ /// Prevents automatically creating a transaction when executing this stored procedure.
+ ///
+ /// A value indicating whether the automatic transactions should be prevented.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder SuppressTransactions(bool suppress = true)
+ {
+ Builder.SuppressTransactions(suppress, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Adds or updates an annotation on the stored procedure. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder;
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs
new file mode 100644
index 00000000000..c918f6ef75d
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs
@@ -0,0 +1,211 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+///
+/// Provides a simple API for configuring a that an entity type is mapped to.
+///
+/// The entity type being configured.
+public class StoredProcedureBuilder : StoredProcedureBuilder, IInfrastructure>
+ where TEntity : class
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public StoredProcedureBuilder(IMutableStoredProcedure sproc, EntityTypeBuilder entityTypeBuilder)
+ : base(sproc, entityTypeBuilder)
+ {
+ }
+
+ private EntityTypeBuilder EntityTypeBuilder
+ => (EntityTypeBuilder)((IInfrastructure)this).Instance;
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual StoredProcedureBuilder HasParameter(string propertyName)
+ => (StoredProcedureBuilder)base.HasParameter(propertyName);
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual StoredProcedureBuilder HasParameter(
+ string propertyName, Action buildAction)
+ => (StoredProcedureBuilder)base.HasParameter(propertyName, buildAction);
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasParameter(
+ Expression> propertyExpression)
+ => HasParameter(propertyExpression);
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The same or a derived entity type.
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasParameter(
+ Expression> propertyExpression)
+ where TDerivedEntity : class, TEntity
+ {
+ Builder.HasParameter(propertyExpression, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// An action that performs configuration of the parameter.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasParameter(
+ Expression> propertyExpression,
+ Action buildAction)
+ => HasParameter(propertyExpression, buildAction);
+
+ ///
+ /// Configures a new parameter if no parameter mapped to the given property exists.
+ ///
+ /// The same or a derived entity type.
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// An action that performs configuration of the parameter.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasParameter(
+ Expression> propertyExpression,
+ Action buildAction)
+ where TDerivedEntity : class, TEntity
+ {
+ Builder.HasParameter(propertyExpression, ConfigurationSource.Explicit);
+ buildAction(new(((StoredProcedure)Metadata).CreateIdentifier()!.Value, CreatePropertyBuilder(propertyExpression)));
+ return this;
+ }
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual StoredProcedureBuilder HasResultColumn(string propertyName)
+ => (StoredProcedureBuilder)base.HasResultColumn(propertyName);
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property name.
+ /// An action that performs configuration of the column.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual StoredProcedureBuilder HasResultColumn(
+ string propertyName, Action buildAction)
+ => (StoredProcedureBuilder)base.HasResultColumn(propertyName, buildAction);
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasResultColumn(
+ Expression> propertyExpression)
+ => HasResultColumn(propertyExpression);
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The same or a derived entity type.
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasResultColumn(
+ Expression> propertyExpression)
+ where TDerivedEntity : class, TEntity
+ {
+ Builder.HasResultColumn(propertyExpression, ConfigurationSource.Explicit);
+ return this;
+ }
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// An action that performs configuration of the column.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasResultColumn(
+ Expression> propertyExpression,
+ Action buildAction)
+ => HasResultColumn(propertyExpression, buildAction);
+
+ ///
+ /// Configures a new column of the result for this stored procedure. This is used for database generated columns.
+ ///
+ /// The same or a derived entity type.
+ /// The property type.
+ ///
+ /// A lambda expression representing the property to be configured (blog => blog.Url).
+ ///
+ /// An action that performs configuration of the column.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureBuilder HasResultColumn(
+ Expression> propertyExpression,
+ Action buildAction)
+ where TDerivedEntity : class, TEntity
+ {
+ Builder.HasResultColumn(propertyExpression, ConfigurationSource.Explicit);
+ buildAction(new(((StoredProcedure)Metadata).CreateIdentifier()!.Value, CreatePropertyBuilder(propertyExpression)));
+ return this;
+ }
+
+ ///
+ /// Prevents automatically creating a transaction when executing this stored procedure.
+ ///
+ /// A value indicating whether the automatic transactions should be prevented.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual StoredProcedureBuilder SuppressTransactions(bool suppress = true)
+ => (StoredProcedureBuilder)base.SuppressTransactions(suppress);
+
+ ///
+ /// Adds or updates an annotation on the stored procedure. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual StoredProcedureBuilder HasAnnotation(string annotation, object? value)
+ => (StoredProcedureBuilder)base.HasAnnotation(annotation, value);
+
+ EntityTypeBuilder IInfrastructure>.Instance => EntityTypeBuilder;
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs
new file mode 100644
index 00000000000..19c0702076a
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs
@@ -0,0 +1,120 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// 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;
+
+///
+/// Provides a simple API for configuring a parameter.
+///
+///
+/// Instances of this class are returned from methods when using the API
+/// and it is not designed to be directly constructed in your application code.
+///
+public class StoredProcedureParameterBuilder : IInfrastructure
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public StoredProcedureParameterBuilder(in StoreObjectIdentifier storeObject, PropertyBuilder propertyBuilder)
+ {
+ Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.InsertStoredProcedure
+ || storeObject.StoreObjectType == StoreObjectType.DeleteStoredProcedure
+ || storeObject.StoreObjectType == StoreObjectType.UpdateStoredProcedure,
+ "StoreObjectType should be StoredProcedure, not " + storeObject.StoreObjectType);
+
+ InternalOverrides = RelationalPropertyOverrides.GetOrCreate(
+ propertyBuilder.Metadata, storeObject, ConfigurationSource.Explicit);
+ PropertyBuilder = propertyBuilder;
+ }
+
+ ///
+ /// The stored procedure-specific overrides being configured.
+ ///
+ public virtual IMutableRelationalPropertyOverrides Overrides => InternalOverrides;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual RelationalPropertyOverrides InternalOverrides { get; }
+
+ private PropertyBuilder PropertyBuilder { get; }
+
+ ///
+ /// Sets the name of the stored procedure parameter.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The store type of the function parameter in the database.
+ /// The same builder instance so that further configuration calls can be chained.
+ public virtual StoredProcedureParameterBuilder HasName(string? name)
+ {
+ Check.NullButNotEmpty(name, nameof(name));
+
+ InternalOverrides.SetColumnName(name, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Adds or updates an annotation on the property for a specific stored procedure.
+ /// If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureParameterBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ InternalOverrides.Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ PropertyBuilder IInfrastructure.Instance => PropertyBuilder;
+
+ #region Hidden System.Object members
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString()
+ => base.ToString();
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// if the specified object is equal to the current object; otherwise, .
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectEqualsIsObjectEquals
+ public override bool Equals(object? obj)
+ => base.Equals(obj);
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ #endregion
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs
new file mode 100644
index 00000000000..b83fc3da30e
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs
@@ -0,0 +1,120 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// 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;
+
+///
+/// Provides a simple API for configuring a result column.
+///
+///
+/// Instances of this class are returned from methods when using the API
+/// and it is not designed to be directly constructed in your application code.
+///
+public class StoredProcedureResultColumnBuilder : IInfrastructure
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public StoredProcedureResultColumnBuilder(in StoreObjectIdentifier storeObject, PropertyBuilder propertyBuilder)
+ {
+ Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.InsertStoredProcedure
+ || storeObject.StoreObjectType == StoreObjectType.DeleteStoredProcedure
+ || storeObject.StoreObjectType == StoreObjectType.UpdateStoredProcedure,
+ "StoreObjectType should be StoredProcedure, not " + storeObject.StoreObjectType);
+
+ InternalOverrides = RelationalPropertyOverrides.GetOrCreate(
+ propertyBuilder.Metadata, storeObject, ConfigurationSource.Explicit);
+ PropertyBuilder = propertyBuilder;
+ }
+
+ ///
+ /// The stored procedure-specific overrides being configured.
+ ///
+ public virtual IMutableRelationalPropertyOverrides Overrides => InternalOverrides;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual RelationalPropertyOverrides InternalOverrides { get; }
+
+ private PropertyBuilder PropertyBuilder { get; }
+
+ ///
+ /// Sets the name of the stored procedure result column.
+ ///
+ ///
+ /// See Modeling entity types and relationships and
+ /// Saving data with EF Core for more information and examples.
+ ///
+ /// The store type of the function parameter in the database.
+ /// The same builder instance so that further configuration calls can be chained.
+ public virtual StoredProcedureResultColumnBuilder HasName(string? name)
+ {
+ Check.NullButNotEmpty(name, nameof(name));
+
+ InternalOverrides.SetColumnName(name, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ ///
+ /// Adds or updates an annotation on the property for a specific stored procedure.
+ /// If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual StoredProcedureResultColumnBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ InternalOverrides.Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ PropertyBuilder IInfrastructure.Instance => PropertyBuilder;
+
+ #region Hidden System.Object members
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString()
+ => base.ToString();
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// if the specified object is equal to the current object; otherwise, .
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectEqualsIsObjectEquals
+ public override bool Equals(object? obj)
+ => base.Equals(obj);
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ #endregion
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder.cs
index 502431374d7..b441ba64b50 100644
--- a/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder.cs
@@ -56,5 +56,35 @@ public TableValuedFunctionBuilder(IMutableDbFunction function, EntityTypeBuilder
public new virtual TableValuedFunctionBuilder IsBuiltIn(bool builtIn = true)
=> (TableValuedFunctionBuilder)base.IsBuiltIn(builtIn);
+ ///
+ /// Returns an object that can be used to configure a parameter with the given name.
+ /// If no parameter with the given name exists, then a new parameter will be added.
+ ///
+ ///
+ /// See Database functions for more information and examples.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The builder to use for further parameter configuration.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual TableValuedFunctionBuilder HasParameter(string name, Action buildAction)
+ => (TableValuedFunctionBuilder)base.HasParameter(name, buildAction);
+
+ ///
+ /// Adds or updates an annotation on the database function. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual TableValuedFunctionBuilder HasAnnotation(string annotation, object? value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder;
}
diff --git a/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder`.cs
index 013f8633fcb..22dd17ec6c3 100644
--- a/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder`.cs
+++ b/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder`.cs
@@ -58,5 +58,29 @@ private EntityTypeBuilder EntityTypeBuilder
public new virtual TableValuedFunctionBuilder IsBuiltIn(bool builtIn = true)
=> (TableValuedFunctionBuilder)base.IsBuiltIn(builtIn);
+ ///
+ /// Returns an object that can be used to configure a parameter with the given name.
+ /// If no parameter with the given name exists, then a new parameter will be added.
+ ///
+ ///
+ /// See Database functions for more information and examples.
+ ///
+ /// The parameter name.
+ /// An action that performs configuration of the parameter.
+ /// The builder to use for further parameter configuration.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual TableValuedFunctionBuilder HasParameter(string name, Action buildAction)
+ => (TableValuedFunctionBuilder)base.HasParameter(name, buildAction);
+
+ ///
+ /// Adds or updates an annotation on the database function. If an annotation with the key specified in
+ /// already exists, its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual TableValuedFunctionBuilder HasAnnotation(string annotation, object? value)
+ => (TableValuedFunctionBuilder)base.HasAnnotation(annotation, value);
+
EntityTypeBuilder IInfrastructure>.Instance => EntityTypeBuilder;
}
diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
index e3812030b50..267884d06b0 100644
--- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
@@ -75,6 +75,7 @@ public override ConventionSet CreateConventionSet()
conventionSet.EntityTypeAddedConventions.Add(entitySplittingConvention);
conventionSet.EntityTypeAddedConventions.Add(checkConstraintConvention);
conventionSet.EntityTypeAddedConventions.Add(triggerConvention);
+ conventionSet.EntityTypeAddedConventions.Add(new StoredProcedureConvention(Dependencies, RelationalDependencies));
ValueGenerationConvention valueGenerationConvention =
new RelationalValueGenerationConvention(Dependencies, RelationalDependencies);
diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs
index 2f59b82bf21..2f145d18fad 100644
--- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs
@@ -129,7 +129,7 @@ protected override void ProcessEntityTypeAnnotations(
annotations.Remove(RelationalAnnotationNames.CheckConstraints);
annotations.Remove(RelationalAnnotationNames.Comment);
annotations.Remove(RelationalAnnotationNames.IsTableExcludedFromMigrations);
-
+
// These need to be set explicitly to prevent default values from being generated
annotations[RelationalAnnotationNames.TableName] = entityType.GetTableName();
annotations[RelationalAnnotationNames.Schema] = entityType.GetSchema();
@@ -348,7 +348,7 @@ private static RuntimeRelationalPropertyOverrides Create(
=> new(
runtimeProperty,
propertyOverrides.StoreObject,
- propertyOverrides.ColumnNameOverridden,
+ propertyOverrides.IsColumnNameOverridden,
propertyOverrides.ColumnName);
///
diff --git a/src/EFCore.Relational/Metadata/Conventions/StoredProcedureConvention.cs b/src/EFCore.Relational/Metadata/Conventions/StoredProcedureConvention.cs
new file mode 100644
index 00000000000..46b913b9e3e
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Conventions/StoredProcedureConvention.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+///
+/// A convention that ensures that the entity type is current for the stored procedures.
+///
+///
+/// See Model building conventions and
+/// Entity type hierarchy mapping for more information and examples.
+///
+public class StoredProcedureConvention : IEntityTypeAddedConvention
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// Parameter object containing dependencies for this convention.
+ /// Parameter object containing relational dependencies for this convention.
+ public StoredProcedureConvention(
+ ProviderConventionSetBuilderDependencies dependencies,
+ RelationalConventionSetBuilderDependencies relationalDependencies)
+ {
+ Dependencies = dependencies;
+ RelationalDependencies = relationalDependencies;
+ }
+
+ ///
+ /// Dependencies for this service.
+ ///
+ protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; }
+
+ ///
+ /// Relational provider-specific dependencies for this service.
+ ///
+ protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
+
+ ///
+ /// Called after an entity type is added to the model.
+ ///
+ /// The builder for the entity type.
+ /// Additional information associated with convention execution.
+ public virtual void ProcessEntityTypeAdded(
+ IConventionEntityTypeBuilder entityTypeBuilder,
+ IConventionContext context)
+ {
+ var entityType = entityTypeBuilder.Metadata;
+ if (!entityType.HasSharedClrType)
+ {
+ return;
+ }
+
+ var sproc = (StoredProcedure?)entityType.GetDeleteStoredProcedure();
+ if (sproc != null
+ && sproc.EntityType != entityType)
+ {
+ sproc.EntityType = (IMutableEntityType)entityType;
+ }
+
+ sproc = (StoredProcedure?)entityType.GetInsertStoredProcedure();
+ if (sproc != null
+ && sproc.EntityType != entityType)
+ {
+ sproc.EntityType = (IMutableEntityType)entityType;
+ }
+
+ sproc = (StoredProcedure?)entityType.GetUpdateStoredProcedure();
+ if (sproc != null
+ && sproc.EntityType != entityType)
+ {
+ sproc.EntityType = (IMutableEntityType)entityType;
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/IConventionDbFunction.cs b/src/EFCore.Relational/Metadata/IConventionDbFunction.cs
index 592d137e0c3..07c8afaca66 100644
--- a/src/EFCore.Relational/Metadata/IConventionDbFunction.cs
+++ b/src/EFCore.Relational/Metadata/IConventionDbFunction.cs
@@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata;
public interface IConventionDbFunction : IReadOnlyDbFunction, IConventionAnnotatable
{
///
- /// Gets the in which this function is defined.
+ /// Gets the model in which this function is defined.
///
new IConventionModel Model { get; }
diff --git a/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs b/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs
new file mode 100644
index 00000000000..2c83f9883ac
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs
@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+
+namespace Microsoft.EntityFrameworkCore.Metadata;
+
+///
+/// Represents a relational database function in a model in
+/// the form that can be mutated while the model is being built.
+///
+public interface IConventionStoredProcedure : IReadOnlyStoredProcedure, IConventionAnnotatable
+{
+ ///
+ /// Gets the entity type in which this function is defined.
+ ///
+ new IConventionEntityType EntityType { get; }
+
+ ///
+ /// Gets the builder that can be used to configure this function.
+ ///
+ /// If the function has been removed from the model.
+ new IConventionStoredProcedureBuilder Builder { get; }
+
+ ///
+ /// Gets the configuration source for this function.
+ ///
+ /// The configuration source for this function.
+ ConfigurationSource GetConfigurationSource();
+
+ ///
+ /// Sets the name of the function in the database.
+ ///
+ /// The name of the function in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ string? SetName(string? name, bool fromDataAnnotation = false);
+
+ ///
+ /// Gets the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetNameConfigurationSource();
+
+ ///
+ /// Sets the schema of the function in the database.
+ ///
+ /// The schema of the function in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ string? SetSchema(string? schema, bool fromDataAnnotation = false);
+
+ ///
+ /// Gets the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetSchemaConfigurationSource();
+
+ ///
+ /// Adds a new parameter mapped to the property with the given name.
+ ///
+ /// The name of the corresponding property.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ string? AddParameter(string propertyName, bool fromDataAnnotation = false);
+
+ ///
+ /// Adds a new column of the result for this stored procedure mapped to the property with the given name
+ ///
+ /// The name of the corresponding property.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ string? AddResultColumn(string propertyName, bool fromDataAnnotation = false);
+
+ ///
+ /// Prevents automatically creating a transaction when executing this stored procedure.
+ ///
+ /// A value indicating whether the automatic transactions should be prevented.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ bool SetAreTransactionsSuppressed(bool areTransactionsSuppressed, bool fromDataAnnotation = false);
+
+ ///
+ /// Gets the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetAreTransactionsSuppressedConfigurationSource();
+}
diff --git a/src/EFCore.Relational/Metadata/IMutableDbFunction.cs b/src/EFCore.Relational/Metadata/IMutableDbFunction.cs
index ddacfc6da15..ef7f3258294 100644
--- a/src/EFCore.Relational/Metadata/IMutableDbFunction.cs
+++ b/src/EFCore.Relational/Metadata/IMutableDbFunction.cs
@@ -45,7 +45,7 @@ public interface IMutableDbFunction : IReadOnlyDbFunction, IMutableAnnotatable
new RelationalTypeMapping? TypeMapping { get; set; }
///
- /// Gets the in which this function is defined.
+ /// Gets the model in which this function is defined.
///
new IMutableModel Model { get; }
diff --git a/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs b/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs
new file mode 100644
index 00000000000..093d6f2a66b
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.EntityFrameworkCore.Metadata;
+
+///
+/// Represents a relational database function in an model in
+/// the form that can be mutated while the model is being built.
+///
+public interface IMutableStoredProcedure : IReadOnlyStoredProcedure, IMutableAnnotatable
+{
+ ///
+ /// Gets or sets the name of the function in the database.
+ ///
+ new string? Name { get; [param: NotNull] set; }
+
+ ///
+ /// Gets or sets the schema of the function in the database.
+ ///
+ new string? Schema { get; set; }
+
+ ///
+ /// Gets the entity type in which this function is defined.
+ ///
+ new IMutableEntityType EntityType { get; }
+
+ ///
+ /// Returns a value indicating whether automatic creation of transactions is disabled when executing this stored procedure.
+ ///
+ /// The configured value.
+ new bool AreTransactionsSuppressed { get; set; }
+
+ ///
+ /// Adds a new parameter mapped to the property with the given name.
+ ///
+ /// The name of the corresponding property.
+ /// if a parameter was added.
+ bool AddParameter(string propertyName);
+
+ ///
+ /// Adds a new column of the result for this stored procedure mapped to the property with the given name
+ ///
+ /// The name of the corresponding property.
+ /// if a column was added.
+ bool AddResultColumn(string propertyName);
+}
diff --git a/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs
index 5fe8987bf95..07dad4656a7 100644
--- a/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs
+++ b/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs
@@ -31,7 +31,7 @@ public interface IReadOnlyRelationalPropertyOverrides : IReadOnlyAnnotatable
///
/// Gets a value indicating whether the column name is overriden.
///
- bool ColumnNameOverridden { get; }
+ bool IsColumnNameOverridden { get; }
///
///
@@ -55,7 +55,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt
.Append("Override: ")
.Append(StoreObject.DisplayName());
- if (ColumnNameOverridden)
+ if (IsColumnNameOverridden)
{
builder.Append(" ColumnName: ")
.Append(ColumnName);
diff --git a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs
new file mode 100644
index 00000000000..23739096e18
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs
@@ -0,0 +1,125 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+
+namespace Microsoft.EntityFrameworkCore.Metadata;
+
+///
+/// Represents a stored procedure in a model.
+///
+public interface IReadOnlyStoredProcedure : IReadOnlyAnnotatable
+{
+ ///
+ /// Gets the name of the stored procedure in the database.
+ ///
+ string? Name { get; }
+
+ ///
+ /// Gets the schema of the stored procedure in the database.
+ ///
+ string? Schema { get; }
+
+ ///
+ /// Gets the entity type in which this stored procedure is defined.
+ ///
+ IReadOnlyEntityType EntityType { get; }
+
+ ///
+ /// Returns a value indicating whether automatic creation of transactions is disabled when executing this stored procedure.
+ ///
+ /// The configured value.
+ bool AreTransactionsSuppressed { get; }
+
+ ///
+ /// Gets the names of properties mapped to parameters for this stored procedure.
+ ///
+ IReadOnlyList Parameters { get; }
+
+ ///
+ /// Returns a value indicating whether there is a parameter corresponding to the given property.
+ ///
+ /// The name of a property.
+ /// if a parameter corresponding to the given property is found.
+ bool ContainsParameter(string propertyName);
+
+ ///
+ /// Gets the names of properties mapped to columns of the result for this stored procedure.
+ ///
+ IReadOnlyList ResultColumns { get; }
+
+ ///
+ /// Returns a value indicating whether there is a column of the result corresponding to the given property.
+ ///
+ /// The name of a property.
+ /// if a columns of the result corresponding to the given property is found.
+ bool ContainsResultColumn(string propertyName);
+
+ ///
+ /// Returns the name of the stored procedure prepended by the schema
+ /// or if not mapped.
+ ///
+ /// The name of the stored procedure prepended by the schema.
+ string? GetSchemaQualifiedName()
+ {
+ var name = Name;
+ if (name == null)
+ {
+ return null;
+ }
+
+ var schema = Schema;
+ return (string.IsNullOrEmpty(schema) ? "" : schema + ".") + name;
+ }
+
+ ///
+ ///
+ /// Creates a human-readable representation of the given metadata.
+ ///
+ ///
+ /// Warning: Do not rely on the format of the returned string.
+ /// It is designed for debugging only and may change arbitrarily between releases.
+ ///
+ ///
+ /// Options for generating the string.
+ /// The number of indent spaces to use before each new line.
+ /// A human-readable representation.
+ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0)
+ {
+ var builder = new StringBuilder();
+ var indentString = new string(' ', indent);
+
+ builder
+ .Append(indentString)
+ .Append("StoredProcedure: ");
+
+ if (Schema != null)
+ {
+ builder
+ .Append(Schema)
+ .Append('.');
+ }
+
+ builder.Append(Name);
+
+ if ((options & MetadataDebugStringOptions.SingleLine) == 0)
+ {
+ var parameters = Parameters.ToList();
+ if (parameters.Count != 0)
+ {
+ builder.AppendLine().Append(indentString).Append(" Parameters: ");
+ foreach (var parameter in parameters)
+ {
+ builder.AppendLine().Append(parameter);
+ }
+ }
+
+ if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0)
+ {
+ builder.Append(AnnotationsToDebugString(indent: indent + 2));
+ }
+ }
+
+ return builder.ToString();
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/IStoredProcedure.cs b/src/EFCore.Relational/Metadata/IStoredProcedure.cs
new file mode 100644
index 00000000000..2e571045add
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/IStoredProcedure.cs
@@ -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;
+
+///
+/// Represents a relational database function in a model.
+///
+///
+/// See Database functions for more information and examples.
+///
+public interface IStoredProcedure : IReadOnlyStoredProcedure, IAnnotatable
+{
+ ///
+ /// Gets the name of the stored procedure in the database.
+ ///
+ new string Name { get; }
+
+ ///
+ /// Gets the entity type in which this function is defined.
+ ///
+ new IEntityType EntityType { get; }
+
+ /////
+ ///// Gets the associated .
+ /////
+ //IStoreFunction StoreFunction { get; }
+}
diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
index 2c76de5e509..0e6f337e43c 100644
--- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
+++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
@@ -99,9 +99,9 @@ public DbFunction(
ReturnType = returnType;
Model = model;
_configurationSource = configurationSource;
- _builder = new InternalDbFunctionBuilder(this, ((IConventionModel)model).Builder);
+ _builder = new(this, ((IConventionModel)model).Builder);
_parameters = parameters == null
- ? new List()
+ ? new()
: parameters
.Select(p => new DbFunctionParameter(this, p.Name, p.Type))
.ToList();
@@ -633,6 +633,15 @@ public virtual IReadOnlyList Parameters
get => _parameters;
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual DbFunctionParameter? FindParameter(string name)
+ => Parameters.SingleOrDefault(p => p.Name == name);
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -719,15 +728,6 @@ IReadOnlyList IDbFunction.Parameters
get => _parameters;
}
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public virtual DbFunctionParameter? FindParameter(string name)
- => Parameters.SingleOrDefault(p => p.Name == name);
-
///
[DebuggerStepThrough]
string? IConventionDbFunction.SetName(string? name, bool fromDataAnnotation)
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs
new file mode 100644
index 00000000000..834f6d9f868
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs
@@ -0,0 +1,392 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class InternalStoredProcedureBuilder :
+ AnnotatableBuilder,
+ IConventionStoredProcedureBuilder
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public InternalStoredProcedureBuilder(StoredProcedure storedProcedure, IConventionModelBuilder modelBuilder)
+ : base(storedProcedure, modelBuilder)
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static InternalStoredProcedureBuilder HasStoredProcedure(
+ IMutableEntityType entityType,
+ StoreObjectType sprocType,
+ string? name = null,
+ string? schema = null)
+ {
+ var sproc = StoredProcedure.GetDeclaredStoredProcedure(entityType, sprocType);
+ if (sproc == null)
+ {
+ sproc = name == null
+ ? StoredProcedure.SetStoredProcedure(entityType, sprocType)
+ : StoredProcedure.SetStoredProcedure(entityType, sprocType, name, schema);
+ }
+ else
+ {
+ if (name != null)
+ {
+ sproc.SetName(name, schema, ConfigurationSource.Explicit);
+ }
+
+ sproc.UpdateConfigurationSource(ConfigurationSource.Explicit);
+ }
+
+ return sproc.Builder;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static InternalStoredProcedureBuilder? HasStoredProcedure(
+ IConventionEntityType entityType,
+ StoreObjectType sprocType,
+ bool fromDataAnnotation)
+ {
+ var sproc = StoredProcedure.GetDeclaredStoredProcedure(entityType, sprocType);
+ if (sproc == null)
+ {
+ sproc = StoredProcedure.SetStoredProcedure(entityType, sprocType, fromDataAnnotation);
+ }
+ else
+ {
+ sproc.UpdateConfigurationSource(fromDataAnnotation
+ ? ConfigurationSource.DataAnnotation
+ : ConfigurationSource.Convention);
+ }
+
+ return sproc?.Builder;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder? HasName(string? name, ConfigurationSource configurationSource)
+ {
+ if (CanSetName(name, configurationSource))
+ {
+ Metadata.SetName(name, configurationSource);
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder? HasName(string? name, string? schema, ConfigurationSource configurationSource)
+ {
+ if (CanSetName(name, configurationSource)
+ && CanSetSchema(schema, configurationSource))
+ {
+ Metadata.SetName(name, schema, configurationSource);
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetName(string? name, ConfigurationSource configurationSource)
+ => (name != "" || configurationSource == ConfigurationSource.Explicit)
+ && (configurationSource.Overrides(Metadata.GetNameConfigurationSource())
+ || Metadata.Name == name);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder? HasSchema(string? schema, ConfigurationSource configurationSource)
+ {
+ if (CanSetSchema(schema, configurationSource))
+ {
+ Metadata.SetSchema(schema, configurationSource);
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSetSchema(string? schema, ConfigurationSource configurationSource)
+ => configurationSource.Overrides(Metadata.GetSchemaConfigurationSource())
+ || Metadata.Schema == schema;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual PropertyBuilder CreatePropertyBuilder(EntityTypeBuilder entityTypeBuilder, string propertyName)
+ {
+ var entityType = entityTypeBuilder.Metadata;
+ var property = entityType.FindProperty(propertyName);
+ if (property == null)
+ {
+ property = entityType.GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties())
+ .FirstOrDefault(p => p.Name == propertyName);
+ }
+
+ if (property == null)
+ {
+ throw new InvalidOperationException(CoreStrings.PropertyNotFound(propertyName, entityType.DisplayName()));
+ }
+
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ return new ModelBuilder(entityType.Model)
+#pragma warning restore EF1001 // Internal EF Core API usage.
+ .Entity(property.DeclaringEntityType.Name)
+ .Property(property.ClrType, propertyName);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual PropertyBuilder CreatePropertyBuilder(
+ EntityTypeBuilder entityTypeBuilder,
+ Expression> propertyExpression)
+ where TDerivedEntity : class
+ {
+ var memberInfo = propertyExpression.GetMemberAccess();
+ var entityType = entityTypeBuilder.Metadata;
+ if (entityType.ClrType != typeof(TDerivedEntity))
+ {
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ entityTypeBuilder = new ModelBuilder(entityType.Model).Entity(typeof(TDerivedEntity));
+#pragma warning restore EF1001 // Internal EF Core API usage.
+ }
+
+ return entityTypeBuilder.Property(memberInfo.GetMemberType(), memberInfo.Name);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder? HasParameter(
+ string propertyName, ConfigurationSource configurationSource)
+ {
+ if (!Metadata.ContainsParameter(propertyName))
+ {
+ if (!configurationSource.Overrides(Metadata.GetConfigurationSource()))
+ {
+ return null;
+ }
+
+ Metadata.AddParameter(propertyName);
+ }
+
+ Metadata.UpdateConfigurationSource(configurationSource);
+ return this;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder? HasParameter(
+ Expression> propertyExpression,
+ ConfigurationSource configurationSource)
+ where TDerivedEntity : class
+ => HasParameter(propertyExpression.GetMemberAccess().Name, configurationSource);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanHaveParameter(string propertyName, ConfigurationSource configurationSource)
+ => Metadata.ContainsParameter(propertyName)
+ || configurationSource.Overrides(Metadata.GetConfigurationSource());
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder? HasResultColumn(
+ string propertyName, ConfigurationSource configurationSource)
+ {
+ if (!Metadata.ContainsResultColumn(propertyName))
+ {
+ if (!configurationSource.Overrides(Metadata.GetConfigurationSource()))
+ {
+ return null;
+ }
+
+ Metadata.AddResultColumn(propertyName);
+ }
+
+ Metadata.UpdateConfigurationSource(configurationSource);
+ return this;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder? HasResultColumn(
+ Expression> propertyExpression,
+ ConfigurationSource configurationSource)
+ where TDerivedEntity : class
+ => HasResultColumn(propertyExpression.GetMemberAccess().Name, configurationSource);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanHaveResultColumn(string propertyName, ConfigurationSource configurationSource)
+ => Metadata.ContainsResultColumn(propertyName)
+ || configurationSource.Overrides(Metadata.GetConfigurationSource());
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder? SuppressTransactions(bool suppress, ConfigurationSource configurationSource)
+ {
+ if (!CanSuppressTransactions(suppress, configurationSource))
+ {
+ return null;
+ }
+
+ Metadata.SetAreTransactionsSuppressed(suppress, configurationSource);
+ return this;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool CanSuppressTransactions(bool suppress, ConfigurationSource configurationSource)
+ => Metadata.AreTransactionsSuppressed == suppress
+ || configurationSource.Overrides(Metadata.GetAreTransactionsSuppressedConfigurationSource());
+
+ IConventionStoredProcedure IConventionStoredProcedureBuilder.Metadata
+ {
+ [DebuggerStepThrough]
+ get => Metadata;
+ }
+
+ ///
+ [DebuggerStepThrough]
+ IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasName(string? name, bool fromDataAnnotation)
+ => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasName(string? name, string? schema, bool fromDataAnnotation)
+
+ => HasName(name, schema, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ bool IConventionStoredProcedureBuilder.CanSetName(string? name, bool fromDataAnnotation)
+ => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasSchema(string? schema, bool fromDataAnnotation)
+ => HasSchema(schema, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ bool IConventionStoredProcedureBuilder.CanSetSchema(string? schema, bool fromDataAnnotation)
+ => CanSetSchema(schema, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasParameter(string propertyName, bool fromDataAnnotation)
+ => HasParameter(propertyName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ bool IConventionStoredProcedureBuilder.CanHaveParameter(string propertyName, bool fromDataAnnotation)
+ => CanHaveParameter(propertyName,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasResultColumn(string propertyName, bool fromDataAnnotation)
+ => HasResultColumn(propertyName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ bool IConventionStoredProcedureBuilder.CanHaveResultColumn(string propertyName, bool fromDataAnnotation)
+ => CanHaveResultColumn(propertyName,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.SuppressTransactions(bool suppress, bool fromDataAnnotation)
+ => SuppressTransactions(suppress, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ bool IConventionStoredProcedureBuilder.CanSetSuppressTransactions(bool suppress, bool fromDataAnnotation)
+ => CanSuppressTransactions(suppress,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+}
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalKeyExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalKeyExtensions.cs
index a50faacc0d4..f437487aaa7 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalKeyExtensions.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalKeyExtensions.cs
@@ -171,7 +171,7 @@ public static bool AreCompatible(
return null;
}
- logger.KeyUnmappedProperties((IKey)key);
+ logger.KeyPropertiesNotMappedToTable((IKey)key);
}
return null;
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs
index aeec0461e23..f0a5006c667 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs
@@ -101,10 +101,22 @@ public virtual void SetRemovedFromModel()
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public static void Attach(IConventionProperty property, IConventionRelationalPropertyOverrides detachedOverrides)
+ => Attach(property, detachedOverrides, detachedOverrides.StoreObject);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static void Attach(
+ IConventionProperty property,
+ IConventionRelationalPropertyOverrides detachedOverrides,
+ StoreObjectIdentifier newStoreObject)
{
var newOverrides = GetOrCreate(
(IMutableProperty)property,
- detachedOverrides.StoreObject,
+ newStoreObject,
detachedOverrides.GetConfigurationSource());
MergeInto(detachedOverrides, newOverrides);
@@ -184,12 +196,12 @@ public virtual string? ColumnName
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual bool ColumnNameOverridden
+ public virtual bool IsColumnNameOverridden
=> _columnNameConfigurationSource != null;
private bool RemoveColumnNameOverride(ConfigurationSource configurationSource)
{
- if (!ColumnNameOverridden)
+ if (!IsColumnNameOverridden)
{
return true;
}
diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs
new file mode 100644
index 00000000000..07c0ccb556f
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs
@@ -0,0 +1,665 @@
+// 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.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class StoredProcedure :
+ ConventionAnnotatable, IStoredProcedure, IMutableStoredProcedure, IConventionStoredProcedure
+{
+ private readonly List _parameters = new();
+ private readonly HashSet _parametersSet = new();
+ private readonly List _resultColumns = new();
+ private readonly HashSet _resultColumnsSet = new();
+ private string? _schema;
+ private string? _name;
+ private InternalStoredProcedureBuilder? _builder;
+ private bool _areTransactionsSuppressed;
+
+ private ConfigurationSource _configurationSource;
+ private ConfigurationSource? _schemaConfigurationSource;
+ private ConfigurationSource? _nameConfigurationSource;
+ private ConfigurationSource? _areTransactionsSuppressedConfigurationSource;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public StoredProcedure(
+ IMutableEntityType entityType,
+ ConfigurationSource configurationSource)
+ {
+ EntityType = entityType;
+ _configurationSource = configurationSource;
+ _builder = new(this, ((IConventionEntityType)entityType).Model.Builder);
+ }
+
+ ///
+ public virtual IMutableEntityType EntityType { get; set; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InternalStoredProcedureBuilder Builder
+ {
+ [DebuggerStepThrough]
+ get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool IsInModel
+ => _builder is not null;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual void SetRemovedFromModel()
+ => _builder = null;
+
+ ///
+ /// Indicates whether the function is read-only.
+ ///
+ public override bool IsReadOnly
+ => ((Annotatable)EntityType).IsReadOnly;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static StoredProcedure? FindStoredProcedure(
+ IReadOnlyEntityType entityType,
+ StoreObjectType sprocType)
+ {
+ var storedProcedure = GetDeclaredStoredProcedure(entityType, sprocType);
+ if (storedProcedure != null)
+ {
+ return storedProcedure;
+ }
+
+ if ((entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy)
+ == RelationalAnnotationNames.TphMappingStrategy
+ && entityType.BaseType != null)
+ {
+ return FindStoredProcedure(entityType.GetRootType(), sprocType);
+ }
+
+ return null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static StoredProcedure? GetDeclaredStoredProcedure(
+ IReadOnlyEntityType entityType,
+ StoreObjectType sprocType)
+ {
+ var sprocAnnotation = entityType.FindAnnotation(GetAnnotationName(sprocType));
+ return sprocAnnotation != null ? (StoredProcedure?)sprocAnnotation.Value : null;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static StoredProcedure SetStoredProcedure(
+ IMutableEntityType entityType,
+ StoreObjectType sprocType)
+ {
+ var oldId = GetDeclaredStoredProcedure(entityType, sprocType)?.CreateIdentifier();
+ var sproc = new StoredProcedure(entityType, ConfigurationSource.Explicit);
+ entityType.SetAnnotation(GetAnnotationName(sprocType), sproc);
+
+ if (oldId != null)
+ {
+ UpdateOverrides(oldId.Value, sproc.CreateIdentifier(), (IConventionEntityType)entityType);
+ }
+
+ return sproc;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static StoredProcedure SetStoredProcedure(
+ IMutableEntityType entityType,
+ StoreObjectType sprocType,
+ string name,
+ string? schema)
+ {
+ var oldId = GetDeclaredStoredProcedure(entityType, sprocType)?.CreateIdentifier();
+ var sproc = new StoredProcedure(entityType, ConfigurationSource.Explicit);
+ entityType.SetAnnotation(GetAnnotationName(sprocType), sproc);
+ sproc.SetName(name, schema, ConfigurationSource.Explicit, skipOverrides: true);
+
+ if (oldId != null)
+ {
+ UpdateOverrides(oldId.Value, sproc.CreateIdentifier(), (IConventionEntityType)entityType);
+ }
+
+ return sproc;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static StoredProcedure? SetStoredProcedure(
+ IConventionEntityType entityType,
+ StoreObjectType sprocType,
+ bool fromDataAnnotation)
+ {
+ var oldId = GetDeclaredStoredProcedure(entityType, sprocType)?.CreateIdentifier();
+ var sproc = new StoredProcedure(
+ (IMutableEntityType)entityType,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ sproc = (StoredProcedure?)entityType.SetAnnotation(GetAnnotationName(sprocType), sproc)?.Value;
+
+ if (oldId != null
+ && sproc != null)
+ {
+ UpdateOverrides(oldId.Value, sproc.CreateIdentifier(), entityType);
+ }
+
+ return sproc;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static StoredProcedure? SetStoredProcedure(
+ IConventionEntityType entityType,
+ StoreObjectType sprocType,
+ string name,
+ string? schema,
+ bool fromDataAnnotation)
+ {
+ var oldId = GetDeclaredStoredProcedure(entityType, sprocType)?.CreateIdentifier();
+ var configurationSource = fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention;
+ var sproc = new StoredProcedure((IMutableEntityType)entityType, configurationSource);
+ sproc = (StoredProcedure?)entityType.SetAnnotation(GetAnnotationName(sprocType), sproc)?.Value;
+
+ sproc?.SetName(name, schema, configurationSource, skipOverrides: true);
+
+ if (oldId != null
+ && sproc != null)
+ {
+ UpdateOverrides(oldId.Value, sproc.CreateIdentifier(), entityType);
+ }
+
+ return sproc;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static IMutableStoredProcedure? RemoveStoredProcedure(IMutableEntityType entityType, StoreObjectType sprocType)
+ {
+ var oldId = GetDeclaredStoredProcedure(entityType, sprocType)?.CreateIdentifier();
+ var sproc = (IMutableStoredProcedure?)entityType.RemoveAnnotation(GetAnnotationName(sprocType))?.Value;
+
+ if (oldId != null
+ && sproc != null)
+ {
+ UpdateOverrides(oldId.Value, null, (IConventionEntityType)entityType);
+ }
+
+ return sproc;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static IConventionStoredProcedure? RemoveStoredProcedure(IConventionEntityType entityType, StoreObjectType sprocType)
+ {
+ var oldId = GetDeclaredStoredProcedure(entityType, sprocType)?.CreateIdentifier();
+ var sproc = (IConventionStoredProcedure?)entityType.RemoveAnnotation(GetAnnotationName(sprocType))?.Value;
+
+ if (oldId != null
+ && sproc != null)
+ {
+ UpdateOverrides(oldId.Value, null, entityType);
+ }
+
+ return sproc;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static ConfigurationSource? GetStoredProcedureConfigurationSource(
+ IConventionEntityType entityType, StoreObjectType sprocType)
+ => entityType.FindAnnotation(GetAnnotationName(sprocType))
+ ?.GetConfigurationSource();
+
+ private static string GetAnnotationName(StoreObjectType sprocType)
+ => sprocType switch
+ {
+ StoreObjectType.InsertStoredProcedure => RelationalAnnotationNames.InsertStoredProcedure,
+ StoreObjectType.DeleteStoredProcedure => RelationalAnnotationNames.DeleteStoredProcedure,
+ StoreObjectType.UpdateStoredProcedure => RelationalAnnotationNames.UpdateStoredProcedure,
+ _ => throw new InvalidOperationException("Unsopported sproc type " + sprocType)
+ };
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual StoreObjectIdentifier? CreateIdentifier()
+ {
+ var name = Name;
+ if (name == null)
+ {
+ return null;
+ }
+
+ if (EntityType.GetInsertStoredProcedure() == this)
+ {
+ return StoreObjectIdentifier.InsertStoredProcedure(name, Schema);
+ }
+
+ if (EntityType.GetDeleteStoredProcedure() == this)
+ {
+ return StoreObjectIdentifier.DeleteStoredProcedure(name, Schema);
+ }
+
+ if (EntityType.GetUpdateStoredProcedure() == this)
+ {
+ return StoreObjectIdentifier.UpdateStoredProcedure(name, Schema);
+ }
+
+ return null;
+ }
+
+ ///
+ [DebuggerStepThrough]
+ public virtual ConfigurationSource GetConfigurationSource()
+ => _configurationSource;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [DebuggerStepThrough]
+ public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource)
+ => _configurationSource = configurationSource.Max(_configurationSource);
+
+ ///
+ public virtual string? Schema
+ {
+ get => _schema ?? EntityType.GetDefaultSchema();
+ set => SetSchema(value, ConfigurationSource.Explicit);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual string? SetSchema(string? schema, ConfigurationSource configurationSource)
+ {
+ EnsureMutable();
+
+ var oldId = CreateIdentifier();
+
+ _schema = schema;
+
+ _schemaConfigurationSource = configurationSource.Max(_schemaConfigurationSource);
+
+ if (oldId != null)
+ {
+ UpdateOverrides(oldId.Value, CreateIdentifier(), (IConventionEntityType)EntityType);
+ }
+
+ return schema;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual ConfigurationSource? GetSchemaConfigurationSource()
+ => _schemaConfigurationSource;
+
+ ///
+ public virtual string? Name
+ {
+ get => _name ?? GetDefaultName();
+ set => SetName(value, ConfigurationSource.Explicit);
+ }
+
+ private string? GetDefaultName()
+ {
+ string? suffix;
+ if (EntityType.GetInsertStoredProcedure() == this)
+ {
+ suffix = "_Insert";
+ }
+ else if (EntityType.GetDeleteStoredProcedure() == this)
+ {
+ suffix = "_Delete";
+ }
+ else if (EntityType.GetUpdateStoredProcedure() == this)
+ {
+ suffix = "_Update";
+ }
+ else
+ {
+ return null;
+ }
+
+ var tableName = EntityType.GetDefaultTableName();
+ if (tableName == null)
+ {
+ return null;
+ }
+
+ return tableName + suffix;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual string? SetName(string? name, ConfigurationSource configurationSource)
+ {
+ EnsureMutable();
+
+ var oldId = CreateIdentifier();
+
+ _name = name;
+
+ _nameConfigurationSource = name == null
+ ? null
+ : configurationSource.Max(_nameConfigurationSource);
+
+ if (oldId != null)
+ {
+ UpdateOverrides(oldId.Value, CreateIdentifier(), (IConventionEntityType)EntityType);
+ }
+
+ return name;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual void SetName(string? name, string? schema, ConfigurationSource configurationSource, bool skipOverrides = false)
+ {
+ EnsureMutable();
+
+ var oldId = CreateIdentifier();
+
+ _name = name;
+
+ _nameConfigurationSource = name == null
+ ? null
+ : configurationSource.Max(_nameConfigurationSource);
+
+ _schema = schema;
+
+ _schemaConfigurationSource = configurationSource.Max(_schemaConfigurationSource);
+
+ if (!skipOverrides
+ && oldId != null)
+ {
+ UpdateOverrides(oldId.Value, CreateIdentifier(), (IConventionEntityType)EntityType);
+ }
+ }
+
+ ///
+ public virtual ConfigurationSource? GetNameConfigurationSource()
+ => _nameConfigurationSource;
+
+ ///
+ public virtual bool AreTransactionsSuppressed
+ {
+ get => _areTransactionsSuppressed;
+ set => SetAreTransactionsSuppressed(value, ConfigurationSource.Explicit);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool SetAreTransactionsSuppressed(bool areTransactionsSuppressed, ConfigurationSource configurationSource)
+ {
+ EnsureMutable();
+
+ _areTransactionsSuppressed = areTransactionsSuppressed;
+
+ _areTransactionsSuppressedConfigurationSource = configurationSource.Max(_areTransactionsSuppressedConfigurationSource);
+
+ return areTransactionsSuppressed;
+ }
+
+ ///
+ public virtual ConfigurationSource? GetAreTransactionsSuppressedConfigurationSource()
+ => _areTransactionsSuppressedConfigurationSource;
+
+ private static void UpdateOverrides(
+ StoreObjectIdentifier oldId,
+ StoreObjectIdentifier? newId,
+ IConventionEntityType entityType)
+ {
+ if (oldId == newId)
+ {
+ return;
+ }
+
+ var properties = (entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy)
+ == RelationalAnnotationNames.TphMappingStrategy
+ ? entityType.GetProperties().Concat(entityType.GetDerivedProperties())
+ : entityType.GetProperties();
+
+ foreach (var property in properties)
+ {
+ var removedOverrides = property.RemoveOverrides(oldId);
+ if (removedOverrides != null
+ && newId != null)
+ {
+ RelationalPropertyOverrides.Attach(property, removedOverrides, newId.Value);
+ }
+ }
+ }
+
+ ///
+ public virtual IReadOnlyList Parameters
+ {
+ [DebuggerStepThrough]
+ get => _parameters;
+ }
+
+ ///
+ public virtual bool ContainsParameter(string propertyName)
+ => _parametersSet.Contains(propertyName);
+
+ ///
+ public virtual bool AddParameter(string propertyName)
+ {
+ if (!_parametersSet.Contains(propertyName))
+ {
+ _parameters.Add(propertyName);
+ _parametersSet.Add(propertyName);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ public virtual IReadOnlyList ResultColumns
+ {
+ [DebuggerStepThrough]
+ get => _resultColumns;
+ }
+
+ ///
+ public virtual bool ContainsResultColumn(string propertyName)
+ => _resultColumnsSet.Contains(propertyName);
+
+ ///
+ public virtual bool AddResultColumn(string propertyName)
+ {
+ if (!_resultColumnsSet.Contains(propertyName))
+ {
+ _resultColumns.Add(propertyName);
+ _resultColumnsSet.Add(propertyName);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /////
+ ///// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ ///// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ ///// any release. You should only use it directly in your code with extreme caution and knowing that
+ ///// doing so can result in application failures when updating to a new Entity Framework Core release.
+ /////
+ //[DisallowNull]
+ //public virtual IStoreFunction? StoreFunction { get; set; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public override string ToString()
+ => ((IStoredProcedure)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public virtual DebugView DebugView
+ => new(
+ () => ((IStoredProcedure)this).ToDebugString(),
+ () => ((IStoredProcedure)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
+
+ ///
+ IConventionStoredProcedureBuilder IConventionStoredProcedure.Builder
+ {
+ [DebuggerStepThrough]
+ get => Builder;
+ }
+
+ ///
+ string IStoredProcedure.Name
+ {
+ [DebuggerStepThrough]
+ get => Name!;
+ }
+
+ ///
+ IReadOnlyEntityType IReadOnlyStoredProcedure.EntityType
+ {
+ [DebuggerStepThrough]
+ get => (IConventionEntityType)EntityType;
+ }
+
+ ///
+ IConventionEntityType IConventionStoredProcedure.EntityType
+ {
+ [DebuggerStepThrough]
+ get => (IConventionEntityType)EntityType;
+ }
+
+ ///
+ IEntityType IStoredProcedure.EntityType
+ {
+ [DebuggerStepThrough]
+ get => (IEntityType)EntityType;
+ }
+
+ /////
+ //IStoreFunction IDbFunction.StoreFunction
+ // => StoreFunction!; // Relational model creation ensures StoreFunction is populated
+
+ ///
+ [DebuggerStepThrough]
+ string? IConventionStoredProcedure.SetName(string? name, bool fromDataAnnotation)
+ => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ string? IConventionStoredProcedure.SetSchema(string? schema, bool fromDataAnnotation)
+ => SetSchema(schema, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ string? IConventionStoredProcedure.AddParameter(string propertyName, bool fromDataAnnotation)
+ => AddParameter(propertyName) ? propertyName : null;
+
+ ///
+ [DebuggerStepThrough]
+ string? IConventionStoredProcedure.AddResultColumn(string propertyName, bool fromDataAnnotation)
+ => AddResultColumn(propertyName) ? propertyName : null;
+
+ ///
+ [DebuggerStepThrough]
+ bool IConventionStoredProcedure.SetAreTransactionsSuppressed(bool areTransactionsSuppressed, bool fromDataAnnotation)
+ => SetAreTransactionsSuppressed(
+ areTransactionsSuppressed, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+}
diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
index e6cd3da5c2f..c060f520d0c 100644
--- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
+++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
@@ -77,6 +77,21 @@ public static class RelationalAnnotationNames
///
public const string FunctionName = Prefix + "FunctionName";
+ ///
+ /// The name for mapped delete stored procedure annotations.
+ ///
+ public const string DeleteStoredProcedure = Prefix + "DeleteStoredProcedure";
+
+ ///
+ /// The name for mapped insert stored procedure annotations.
+ ///
+ public const string InsertStoredProcedure = Prefix + "InsertStoredProcedure";
+
+ ///
+ /// The name for mapped update stored procedure annotations.
+ ///
+ public const string UpdateStoredProcedure = Prefix + "UpdateStoredProcedure";
+
///
/// The name for mapped sql query annotations.
///
diff --git a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs
index 43f8fb11100..bee9445fcf0 100644
--- a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs
+++ b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs
@@ -78,7 +78,7 @@ IReadOnlyProperty IReadOnlyRelationalPropertyOverrides.Property
}
///
- bool IReadOnlyRelationalPropertyOverrides.ColumnNameOverridden
+ bool IReadOnlyRelationalPropertyOverrides.IsColumnNameOverridden
{
[DebuggerStepThrough]
get => FindAnnotation(RelationalAnnotationNames.ColumnName) != null;
diff --git a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs
index a8f555f16a8..070aa49b81c 100644
--- a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs
+++ b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs
@@ -42,6 +42,21 @@ private StoreObjectIdentifier(StoreObjectType storeObjectType, string name, stri
case StoreObjectType.Function:
var functionName = entityType.GetFunctionName();
return functionName == null ? null : DbFunction(functionName);
+ case StoreObjectType.InsertStoredProcedure:
+ var insertStoredProcedure = entityType.GetInsertStoredProcedure();
+ return insertStoredProcedure == null || insertStoredProcedure.Name == null
+ ? null
+ : InsertStoredProcedure(insertStoredProcedure.Name, insertStoredProcedure.Schema);
+ case StoreObjectType.DeleteStoredProcedure:
+ var deleteStoredProcedure = entityType.GetDeleteStoredProcedure();
+ return deleteStoredProcedure == null || deleteStoredProcedure.Name == null
+ ? null
+ : DeleteStoredProcedure(deleteStoredProcedure.Name, deleteStoredProcedure.Schema);
+ case StoreObjectType.UpdateStoredProcedure:
+ var updateStoredProcedure = entityType.GetUpdateStoredProcedure();
+ return updateStoredProcedure == null || updateStoredProcedure.Name == null
+ ? null
+ : UpdateStoredProcedure(updateStoredProcedure.Name, updateStoredProcedure.Schema);
default:
return null;
}
@@ -109,6 +124,45 @@ public static StoreObjectIdentifier DbFunction(string modelName)
return new StoreObjectIdentifier(StoreObjectType.Function, modelName);
}
+ ///
+ /// Creates an insert stored procedure id.
+ ///
+ /// The stored procedure name.
+ /// The stored procedure schema.
+ /// The stored procedure id.
+ public static StoreObjectIdentifier InsertStoredProcedure(string name, string? schema = null)
+ {
+ Check.NotNull(name, nameof(name));
+
+ return new StoreObjectIdentifier(StoreObjectType.InsertStoredProcedure, name, schema);
+ }
+
+ ///
+ /// Creates a delete stored procedure id.
+ ///
+ /// The stored procedure name.
+ /// The stored procedure schema.
+ /// The stored procedure id.
+ public static StoreObjectIdentifier DeleteStoredProcedure(string name, string? schema = null)
+ {
+ Check.NotNull(name, nameof(name));
+
+ return new StoreObjectIdentifier(StoreObjectType.DeleteStoredProcedure, name, schema);
+ }
+
+ ///
+ /// Creates an update stored procedure id.
+ ///
+ /// The stored procedure name.
+ /// The stored procedure schema.
+ /// The stored procedure id.
+ public static StoreObjectIdentifier UpdateStoredProcedure(string name, string? schema = null)
+ {
+ Check.NotNull(name, nameof(name));
+
+ return new StoreObjectIdentifier(StoreObjectType.UpdateStoredProcedure, name, schema);
+ }
+
///
/// Gets the table-like store object type.
///
diff --git a/src/EFCore.Relational/Metadata/StoreObjectType.cs b/src/EFCore.Relational/Metadata/StoreObjectType.cs
index b268a35b848..b10ea103509 100644
--- a/src/EFCore.Relational/Metadata/StoreObjectType.cs
+++ b/src/EFCore.Relational/Metadata/StoreObjectType.cs
@@ -29,5 +29,20 @@ public enum StoreObjectType
///
/// A table-valued function.
///
- Function
+ Function,
+
+ ///
+ /// An insert stored procedure.
+ ///
+ InsertStoredProcedure,
+
+ ///
+ /// A delete stored procedure.
+ ///
+ DeleteStoredProcedure,
+
+ ///
+ /// An update stored procedure.
+ ///
+ UpdateStoredProcedure,
}
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index edd1c6549b5..eed94e6bbd5 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -1091,6 +1091,14 @@ public static string NonTphMappingStrategy(object? mappingStrategy, object? enti
GetString("NonTphMappingStrategy", nameof(mappingStrategy), nameof(entityType)),
mappingStrategy, entityType);
+ ///
+ /// Both '{entityType}' and '{otherEntityType}' are mapped to the stored procedure '{sproc}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different stored procedures. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+ ///
+ public static string NonTphStoredProcedureClash(object? entityType, object? otherEntityType, object? sproc)
+ => string.Format(
+ GetString("NonTphStoredProcedureClash", nameof(entityType), nameof(otherEntityType), nameof(sproc)),
+ entityType, otherEntityType, sproc);
+
///
/// Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
///
@@ -1221,6 +1229,94 @@ public static string SqlQueryOverrideMismatch(object? propertySpecification, obj
GetString("SqlQueryOverrideMismatch", nameof(propertySpecification), nameof(query)),
propertySpecification, query);
+ ///
+ /// The non-key property '{entityType}.{property}' is mapped to a parameter of the stored procedure '{sproc}', but only key properties are supported for Delete stored procedures.
+ ///
+ public static string StoredProcedureDeleteNonKeyProperty(object? entityType, object? property, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureDeleteNonKeyProperty", nameof(entityType), nameof(property), nameof(sproc)),
+ entityType, property, sproc);
+
+ ///
+ /// The keyless entity type '{entityType}' was configured to use '{sproc}'. An entity type requires a primary key to be able to be mapped to a stored procedure.
+ ///
+ public static string StoredProcedureKeyless(object? entityType, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureKeyless", nameof(entityType), nameof(sproc)),
+ entityType, sproc);
+
+ ///
+ /// The entity type '{entityType}' was configured to use '{sproc}', but the store name was not specified. Configure the stored procedure name explicitly.
+ ///
+ public static string StoredProcedureNoName(object? entityType, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureNoName", nameof(entityType), nameof(sproc)),
+ entityType, sproc);
+
+ ///
+ /// The property '{propertySpecification}' has specific configuration for the stored procedure '{sproc}', but it isn't mapped to a parameter or a result column on that stored procedure. Remove the specific configuration, or map an entity type that contains this property to '{sproc}'.
+ ///
+ public static string StoredProcedureOverrideMismatch(object? propertySpecification, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureOverrideMismatch", nameof(propertySpecification), nameof(sproc)),
+ propertySpecification, sproc);
+
+ ///
+ /// No property named '{property}' found on the entity type '{entityType}' corresponding to the parameter on the stored procedure '{sproc}'
+ ///
+ public static string StoredProcedureParameterNotFound(object? property, object? entityType, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureParameterNotFound", nameof(property), nameof(entityType), nameof(sproc)),
+ property, entityType, sproc);
+
+ ///
+ /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the properties {properties} are not mapped to any parameter or result column.
+ ///
+ public static string StoredProcedurePropertiesNotMapped(object? entityType, object? sproc, object? properties)
+ => string.Format(
+ GetString("StoredProcedurePropertiesNotMapped", nameof(entityType), nameof(sproc), nameof(properties)),
+ entityType, sproc, properties);
+
+ ///
+ /// The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but store-generated values are not supported for Delete stored procedures.
+ ///
+ public static string StoredProcedureResultColumnDelete(object? entityType, object? property, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureResultColumnDelete", nameof(entityType), nameof(property), nameof(sproc)),
+ entityType, property, sproc);
+
+ ///
+ /// No property named '{property}' found on the entity type '{entityType}' corresponding to the result column on the stored procedure '{sproc}'
+ ///
+ public static string StoredProcedureResultColumnNotFound(object? property, object? entityType, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureResultColumnNotFound", nameof(property), nameof(entityType), nameof(sproc)),
+ property, entityType, sproc);
+
+ ///
+ /// The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is not configured as store-generated.
+ ///
+ public static string StoredProcedureResultColumnNotGenerated(object? entityType, object? property, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureResultColumnNotGenerated", nameof(entityType), nameof(property), nameof(sproc)),
+ entityType, property, sproc);
+
+ ///
+ /// Both entity type '{entityType1}' and '{entityType2}' were configured to use '{sproc}', stored procedure sharing is not supported. Specify different names for the corresponding stored procedures.
+ ///
+ public static string StoredProcedureTableSharing(object? entityType1, object? entityType2, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureTableSharing", nameof(entityType1), nameof(entityType2), nameof(sproc)),
+ entityType1, entityType2, sproc);
+
+ ///
+ /// Both '{entityType}' and '{otherEntityType}' are explicitly mapped to the stored procedure '{sproc}' using the 'TPH' mapping strategy. Configure the stored procedure mapping on the root entity type, including all parameters for the derived types. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+ ///
+ public static string StoredProcedureTphDuplicate(object? entityType, object? otherEntityType, object? sproc)
+ => string.Format(
+ GetString("StoredProcedureTphDuplicate", nameof(entityType), nameof(otherEntityType), nameof(sproc)),
+ entityType, otherEntityType, sproc);
+
///
/// The entity type '{entityType}' is not mapped to the store object '{table}'.
///
@@ -1238,7 +1334,7 @@ public static string TableOverrideMismatch(object? propertySpecification, object
propertySpecification, table);
///
- /// The element type of the result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property.
+ /// The element type of the result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy but does not contain a discriminator property. Only TPH hierarchies can be mapped to a TVF.
///
public static string TableValuedFunctionNonTph(object? dbFunction, object? entityType)
=> string.Format(
@@ -1270,7 +1366,7 @@ public static string TooFewReaderFields(object? expected, object? actual)
expected, actual);
///
- /// The entity type '{dependentType}' is mapped to '{storeObject}'. However the principal entity type '{principalEntityType}' is also mapped to '{storeObject}' and it's using the TPC mapping strategy. Only leaf entity types in a TPC hierarchy can use table-sharing.
+ /// The entity type '{dependentType}' is mapped to '{storeObject}'. However the principal entity type '{principalEntityType}' is also mapped to '{storeObject}' and it's using the TPC mapping strategy. Entity types in a TPC hierarchy can use table-sharing only if they have no derived types.
///
public static string TpcTableSharing(object? dependentType, object? storeObject, object? principalEntityType)
=> string.Format(
@@ -1285,6 +1381,22 @@ public static string TpcTableSharingDependent(object? dependentType, object? sto
GetString("TpcTableSharingDependent", nameof(dependentType), nameof(storeObject), nameof(derivedType), nameof(otherStoreObject)),
dependentType, storeObject, derivedType, otherStoreObject);
+ ///
+ /// '{entityType}' is mapped to the database function '{function}' while '{otherEntityType}' is mapped to the database function '{otherFunction}'. Map all the entity types in the hierarchy to the same database function. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+ ///
+ public static string TphDbFunctionMismatch(object? entityType, object? function, object? otherEntityType, object? otherFunction)
+ => string.Format(
+ GetString("TphDbFunctionMismatch", nameof(entityType), nameof(function), nameof(otherEntityType), nameof(otherFunction)),
+ entityType, function, otherEntityType, otherFunction);
+
+ ///
+ /// '{entityType}' is mapped to the stored procedure '{sproc}' while '{otherEntityType}' is mapped to the stored procedure '{otherSproc}'. Map all the entity types in the hierarchy to the same stored procedure, or remove the discriminator and map them all to different stored procedures. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+ ///
+ public static string TphStoredProcedureMismatch(object? entityType, object? sproc, object? otherEntityType, object? otherSproc)
+ => string.Format(
+ GetString("TphStoredProcedureMismatch", nameof(entityType), nameof(sproc), nameof(otherEntityType), nameof(otherSproc)),
+ entityType, sproc, otherEntityType, otherSproc);
+
///
/// '{entityType}' is mapped to the table '{table}' while '{otherEntityType}' is mapped to the table '{otherTable}'. Map all the entity types in the hierarchy to the same table, or remove the discriminator and map them all to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
///
@@ -2506,23 +2618,23 @@ public static EventDefinition LogKeyHasDefaultValue(IDiagnostics
///
/// The key {keyProperties} on the entity type '{entityType}' cannot be represented in the database. Either all or some of the properties aren't mapped to table '{table}'. All key properties must be mapped to a single table for the unique constraint to be created.
///
- public static EventDefinition LogKeyUnmappedProperties(IDiagnosticsLogger logger)
+ public static EventDefinition LogKeyPropertiesNotMappedToTable(IDiagnosticsLogger logger)
{
- var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogKeyUnmappedProperties;
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogKeyPropertiesNotMappedToTable;
if (definition == null)
{
definition = NonCapturingLazyInitializer.EnsureInitialized(
- ref ((RelationalLoggingDefinitions)logger.Definitions).LogKeyUnmappedProperties,
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogKeyPropertiesNotMappedToTable,
logger,
static logger => new EventDefinition(
logger.Options,
- RelationalEventId.KeyUnmappedProperties,
+ RelationalEventId.KeyPropertiesNotMappedToTable,
LogLevel.Error,
- "RelationalEventId.KeyUnmappedProperties",
+ "RelationalEventId.KeyPropertiesNotMappedToTable",
level => LoggerMessage.Define(
level,
- RelationalEventId.KeyUnmappedProperties,
- _resourceManager.GetString("LogKeyUnmappedProperties")!)));
+ RelationalEventId.KeyPropertiesNotMappedToTable,
+ _resourceManager.GetString("LogKeyPropertiesNotMappedToTable")!)));
}
return (EventDefinition)definition;
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 4d202036ce2..5a38d8f7d7a 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -623,9 +623,9 @@
Property '{property}' on entity type '{entityType}' is part of a primary or alternate key, but has a constant default value set. Constant default values are not useful for primary or alternate keys since these properties must always have non-null unique values.
Warning RelationalEventId.ModelValidationKeyDefaultValueWarning string string
-
+
The key {keyProperties} on the entity type '{entityType}' cannot be represented in the database. Either all or some of the properties aren't mapped to table '{table}'. All key properties must be mapped to a single table for the unique constraint to be created.
- Error RelationalEventId.KeyUnmappedProperties string string string
+ Error RelationalEventId.KeyPropertiesNotMappedToTable string string string
Migrating using database '{database}' on server '{dataSource}'.
@@ -804,6 +804,9 @@
The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported for entity types with a discriminator.
+
+ Both '{entityType}' and '{otherEntityType}' are mapped to the stored procedure '{sproc}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different stored procedures. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+
Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
@@ -858,6 +861,39 @@
The property '{propertySpecification}' has specific configuration for the SQL query '{query}', but isn't mapped to a column on that query. Remove the specific configuration, or map an entity type that contains this property to '{query}'.
+
+ The non-key property '{entityType}.{property}' is mapped to a parameter of the stored procedure '{sproc}', but only key properties are supported for Delete stored procedures.
+
+
+ The keyless entity type '{entityType}' was configured to use '{sproc}'. An entity type requires a primary key to be able to be mapped to a stored procedure.
+
+
+ The entity type '{entityType}' was configured to use '{sproc}', but the store name was not specified. Configure the stored procedure name explicitly.
+
+
+ The property '{propertySpecification}' has specific configuration for the stored procedure '{sproc}', but it isn't mapped to a parameter or a result column on that stored procedure. Remove the specific configuration, or map an entity type that contains this property to '{sproc}'.
+
+
+ No property named '{property}' found on the entity type '{entityType}' corresponding to the parameter on the stored procedure '{sproc}'
+
+
+ The entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the properties {properties} are not mapped to any parameter or result column.
+
+
+ The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but store-generated values are not supported for Delete stored procedures.
+
+
+ No property named '{property}' found on the entity type '{entityType}' corresponding to the result column on the stored procedure '{sproc}'
+
+
+ The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is not configured as store-generated.
+
+
+ Both entity type '{entityType1}' and '{entityType2}' were configured to use '{sproc}', stored procedure sharing is not supported. Specify different names for the corresponding stored procedures.
+
+
+ Both '{entityType}' and '{otherEntityType}' are explicitly mapped to the stored procedure '{sproc}' using the 'TPH' mapping strategy. Configure the stored procedure mapping on the root entity type, including all parameters for the derived types. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+
The entity type '{entityType}' is not mapped to the store object '{table}'.
@@ -865,7 +901,7 @@
The property '{propertySpecification}' has specific configuration for the table '{table}', but isn't mapped to a column on that table. Remove the specific configuration, or map an entity type that contains this property to '{table}'.
- The element type of the result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property.
+ The element type of the result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy but does not contain a discriminator property. Only TPH hierarchies can be mapped to a TVF.
Timeout must be less than or equal to Int32.MaxValue (2147483647) seconds. Provided timeout: {seconds} seconds.
@@ -877,11 +913,17 @@
The underlying reader doesn't have as many fields as expected. Expected: {expected}, actual: {actual}.
- The entity type '{dependentType}' is mapped to '{storeObject}'. However the principal entity type '{principalEntityType}' is also mapped to '{storeObject}' and it's using the TPC mapping strategy. Only leaf entity types in a TPC hierarchy can use table-sharing.
+ The entity type '{dependentType}' is mapped to '{storeObject}'. However the principal entity type '{principalEntityType}' is also mapped to '{storeObject}' and it's using the TPC mapping strategy. Entity types in a TPC hierarchy can use table-sharing only if they have no derived types.
The entity type '{dependentType}' is mapped to '{storeObject}'. However one of its derived types '{derivedType}' is mapped to '{otherStoreObject}'. Hierarchies using table-sharing cannot be mapped using the TPC mapping strategy.
+
+ '{entityType}' is mapped to the database function '{function}' while '{otherEntityType}' is mapped to the database function '{otherFunction}'. Map all the entity types in the hierarchy to the same database function. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+
+
+ '{entityType}' is mapped to the stored procedure '{sproc}' while '{otherEntityType}' is mapped to the stored procedure '{otherSproc}'. Map all the entity types in the hierarchy to the same stored procedure, or remove the discriminator and map them all to different stored procedures. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+
'{entityType}' is mapped to the table '{table}' while '{otherEntityType}' is mapped to the table '{otherTable}'. Map all the entity types in the hierarchy to the same table, or remove the discriminator and map them all to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
@@ -960,4 +1002,4 @@
'VisitChildren' must be overridden in the class deriving from 'SqlExpression'.
-
+
\ No newline at end of file
diff --git a/src/EFCore/Infrastructure/ConventionAnnotatable.cs b/src/EFCore/Infrastructure/ConventionAnnotatable.cs
index 188128ad3c7..b48bd3cf6ba 100644
--- a/src/EFCore/Infrastructure/ConventionAnnotatable.cs
+++ b/src/EFCore/Infrastructure/ConventionAnnotatable.cs
@@ -133,21 +133,6 @@ public override void SetAnnotation(string name, object? value)
public new virtual ConventionAnnotation? FindAnnotation(string name)
=> (ConventionAnnotation?)base.FindAnnotation(name);
- ///
- /// Removes the given annotation from this object.
- ///
- /// The annotation to remove.
- /// The configuration source of the annotation to be removed.
- /// The annotation that was removed.
- public virtual ConventionAnnotation? RemoveAnnotation(string name, ConfigurationSource configurationSource)
- {
- var annotation = FindAnnotation(name);
- return annotation == null
- || !configurationSource.Overrides(annotation.GetConfigurationSource())
- ? null
- : (ConventionAnnotation?)base.RemoveAnnotation(name);
- }
-
///
protected override Annotation CreateAnnotation(string name, object? value)
=> CreateAnnotation(name, value, ConfigurationSource.Explicit);
@@ -212,8 +197,8 @@ IConventionAnnotation IConventionAnnotatable.AddAnnotation(string name, object?
///
[DebuggerStepThrough]
- IConventionAnnotation? IConventionAnnotatable.RemoveAnnotation(string name, bool fromDataAnnotation)
- => RemoveAnnotation(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ IConventionAnnotation? IConventionAnnotatable.RemoveAnnotation(string name)
+ => (IConventionAnnotation?)RemoveAnnotation(name);
///
[DebuggerStepThrough]
diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs
index 08e42a2cc90..4902eac65d0 100644
--- a/src/EFCore/Infrastructure/ModelValidator.cs
+++ b/src/EFCore/Infrastructure/ModelValidator.cs
@@ -604,7 +604,8 @@ protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType)
if (!discriminatorProperty.ClrType.IsInstanceOfType(discriminatorValue))
{
throw new InvalidOperationException(
- CoreStrings.DiscriminatorValueIncompatible(discriminatorValue, discriminatorProperty.Name, discriminatorProperty.ClrType));
+ CoreStrings.DiscriminatorValueIncompatible(
+ discriminatorValue, derivedType.DisplayName(), discriminatorProperty.ClrType.DisplayName()));
}
if (discriminatorValues.TryGetValue(discriminatorValue, out var duplicateEntityType))
diff --git a/src/EFCore/Metadata/IConventionAnnotatable.cs b/src/EFCore/Metadata/IConventionAnnotatable.cs
index 52c89ffd1b5..71c8243bda6 100644
--- a/src/EFCore/Metadata/IConventionAnnotatable.cs
+++ b/src/EFCore/Metadata/IConventionAnnotatable.cs
@@ -66,9 +66,8 @@ public interface IConventionAnnotatable : IReadOnlyAnnotatable
/// Removes the annotation with the given name from this object.
///
/// The name of the annotation to remove.
- /// Indicates whether the configuration was specified using a data annotation.
/// The annotation that was removed.
- IConventionAnnotation? RemoveAnnotation(string name, bool fromDataAnnotation = false);
+ IConventionAnnotation? RemoveAnnotation(string name);
///
/// Gets the annotation with the given name, throwing if it does not exist.
diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs
index 396cf991faa..4279dccbd3b 100644
--- a/src/EFCore/Metadata/IConventionEntityType.cs
+++ b/src/EFCore/Metadata/IConventionEntityType.cs
@@ -126,10 +126,9 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas
///
/// Removes the discriminator value for this entity type.
///
- /// Indicates whether the configuration was specified using a data annotation.
/// The removed discriminator value.
- object? RemoveDiscriminatorValue(bool fromDataAnnotation = false)
- => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue, fromDataAnnotation)?.Value;
+ object? RemoveDiscriminatorValue()
+ => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue)?.Value;
///
/// Gets the for the discriminator value.
diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs
index b0ef33240dc..73db23e0735 100644
--- a/src/EFCore/Metadata/Internal/EntityType.cs
+++ b/src/EFCore/Metadata/Internal/EntityType.cs
@@ -3310,35 +3310,10 @@ public virtual ChangeTrackingStrategy GetChangeTrackingStrategy()
{
CheckDiscriminatorProperty(property);
- if (((property == null && BaseType == null)
- || (property != null && !property.ClrType.IsInstanceOfType(((IReadOnlyEntityType)this).GetDiscriminatorValue()))))
- {
- RemoveDiscriminatorValue(this, configurationSource);
- if (BaseType == null)
- {
- foreach (var derivedType in GetDerivedTypes())
- {
- RemoveDiscriminatorValue(derivedType, configurationSource);
- }
- }
- }
-
return ((string?)SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, property?.Name, configurationSource)?.Value)
== property?.Name
? property
: (Property?)((IReadOnlyEntityType)this).FindDiscriminatorProperty();
-
- static void RemoveDiscriminatorValue(IReadOnlyEntityType entityType, ConfigurationSource configurationSource)
- {
- if (configurationSource is ConfigurationSource.Convention or ConfigurationSource.DataAnnotation)
- {
- ((IConventionEntityType)entityType).RemoveDiscriminatorValue(configurationSource == ConfigurationSource.DataAnnotation);
- }
- else
- {
- ((IMutableEntityType)entityType).RemoveDiscriminatorValue();
- }
- }
}
private void CheckDiscriminatorProperty(Property? property)
diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
index e62abe7e9fe..5978cfcd461 100644
--- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
@@ -4854,6 +4854,9 @@ public virtual bool CanSetServiceOnlyConstructorBinding(
RemoveUnusedDiscriminatorProperty(discriminatorProperty, configurationSource);
rootTypeBuilder.Metadata.SetDiscriminatorProperty(discriminatorProperty, configurationSource);
+
+ RemoveIncompatibleDiscriminatorValues(Metadata, discriminatorProperty, configurationSource);
+
discriminatorPropertyBuilder.IsRequired(true, ConfigurationSource.Convention);
discriminatorPropertyBuilder.HasValueGeneratorFactory(
typeof(DiscriminatorValueGeneratorFactory), ConfigurationSource.Convention);
@@ -4861,6 +4864,35 @@ public virtual bool CanSetServiceOnlyConstructorBinding(
return new DiscriminatorBuilder(Metadata);
}
+ private void RemoveIncompatibleDiscriminatorValues(
+ EntityType entityType,
+ Property? newDiscriminatorProperty,
+ ConfigurationSource configurationSource)
+ {
+ if ((newDiscriminatorProperty != null || entityType.BaseType != null)
+ && (newDiscriminatorProperty == null
+ || newDiscriminatorProperty.ClrType.IsInstanceOfType(((IReadOnlyEntityType)entityType).GetDiscriminatorValue())))
+ {
+ return;
+ }
+
+ if (configurationSource.Overrides(((IConventionEntityType)entityType).GetDiscriminatorValueConfigurationSource()))
+ {
+ ((IMutableEntityType)entityType).RemoveDiscriminatorValue();
+ }
+
+ if (entityType.BaseType == null)
+ {
+ foreach (var derivedType in entityType.GetDerivedTypes())
+ {
+ if (configurationSource.Overrides(((IConventionEntityType)derivedType).GetDiscriminatorValueConfigurationSource()))
+ {
+ ((IMutableEntityType)derivedType).RemoveDiscriminatorValue();
+ }
+ }
+ }
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -4881,6 +4913,8 @@ public virtual bool CanSetServiceOnlyConstructorBinding(
}
Metadata.SetDiscriminatorProperty(null, configurationSource);
+
+ RemoveIncompatibleDiscriminatorValues(Metadata, null, configurationSource);
if (configurationSource == ConfigurationSource.Explicit)
{
diff --git a/src/EFCore/ModelBuilder.cs b/src/EFCore/ModelBuilder.cs
index 0c7562029c3..f1207ee42cd 100644
--- a/src/EFCore/ModelBuilder.cs
+++ b/src/EFCore/ModelBuilder.cs
@@ -88,6 +88,18 @@ public ModelBuilder()
_builder = new Model().Builder;
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public ModelBuilder(IMutableModel model)
+ {
+ _builder = ((Model)model).Builder;
+ }
+
///
/// The model being configured.
///
diff --git a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs
index 085d5e6b97d..8b422d97c29 100644
--- a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs
@@ -13,11 +13,6 @@ public CustomConvertersCosmosTest(CustomConvertersCosmosFixture fixture)
Fixture.TestSqlLoggerFactory.Clear();
}
- public override void Can_perform_query_with_max_length()
- {
- // Over the 2Mb document limit
- }
-
[ConditionalTheory(Skip = "Issue #17246 No Explicit Convert")]
public override Task Can_filter_projection_with_inline_enum_variable(bool async)
=> base.Can_filter_projection_with_inline_enum_variable(async);
diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
index 5cba542dca9..f816e4b7468 100644
--- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
@@ -1094,7 +1094,8 @@ public void Unmapped_entity_types_are_stored_in_the_model_snapshot()
builder =>
{
builder.HasDefaultSchema("default");
- builder.Entity().Ignore(e => e.EntityWithTwoProperties).ToTable((string)null);
+ builder.Entity().Ignore(e => e.EntityWithTwoProperties).ToTable((string)null)
+ .UpdateUsingStoredProcedure("Update", "sproc", p => p.HasParameter(e => e.Id));
},
AddBoilerPlate(
@"
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
index 99afb33690f..574fb3bdb5c 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
@@ -797,7 +797,7 @@ public void Detects_unnamed_key_properties_mapped_to_different_fragments_in_enti
modelBuilder.Entity().HasAlternateKey(nameof(Animal.Name), nameof(Cat.Identity));
var definition = RelationalResources
- .LogKeyUnmappedProperties(new TestLogger());
+ .LogKeyPropertiesNotMappedToTable(new TestLogger());
VerifyWarning(
definition.GenerateMessage(
"{'Name', 'Identity'}",
@@ -1795,6 +1795,18 @@ public virtual void Passes_for_missing_discriminator_value_for_abstract_class()
Validate(modelBuilder);
}
+ [ConditionalFact]
+ public virtual void Detects_derived_entity_type_mapped_to_a_different_SQL_query()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity().ToSqlQuery("sql");
+ modelBuilder.Entity().ToSqlQuery("sql2");
+
+ VerifyError(
+ RelationalStrings.InvalidMappedSqlQueryDerivedType(nameof(Cat), nameof(Animal)),
+ modelBuilder);
+ }
+
[ConditionalFact]
public virtual void Passes_for_TPT()
{
@@ -2031,7 +2043,6 @@ public virtual void Detects_ToTable_for_abstract_class_TPC()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToTable("Abstract", "dbo").UseTpcMappingStrategy();
- modelBuilder.Entity();
modelBuilder.Entity>();
VerifyError(
@@ -2044,7 +2055,6 @@ public virtual void Detects_ToView_for_abstract_class_TPC()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToView("Abstract").UseTpcMappingStrategy();
- modelBuilder.Entity();
modelBuilder.Entity>();
VerifyError(
@@ -2057,7 +2067,6 @@ public virtual void Detects_ToFunction_for_abstract_class_TPC()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToFunction("Abstract").UseTpcMappingStrategy();
- modelBuilder.Entity();
modelBuilder.Entity>();
VerifyError(
@@ -2065,6 +2074,19 @@ public virtual void Detects_ToFunction_for_abstract_class_TPC()
modelBuilder);
}
+ [ConditionalFact]
+ public virtual void Detects_UsingStoredProcedure_for_abstract_class_TPC()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity().InsertUsingStoredProcedure("Insert", s => s.HasParameter(s => s.Id))
+ .UseTpcMappingStrategy();
+ modelBuilder.Entity>();
+
+ VerifyError(
+ RelationalStrings.AbstractTpc(nameof(Abstract), "Insert"),
+ modelBuilder);
+ }
+
[ConditionalFact]
public virtual void Detects_clashing_entity_types_in_views_TPC()
{
@@ -2334,6 +2356,18 @@ public virtual void Detects_invalid_sql_query_overrides()
RelationalStrings.SqlQueryOverrideMismatch("Animal.Name", "Dog"),
modelBuilder);
}
+
+ [ConditionalFact]
+ public virtual void Detects_invalid_stored_procedure_overrides()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ var property = modelBuilder.Entity().Property(a => a.Name).GetInfrastructure();
+ property.HasColumnName("DogName", StoreObjectIdentifier.InsertStoredProcedure("Dog"));
+
+ VerifyError(
+ RelationalStrings.StoredProcedureOverrideMismatch("Animal.Name", "Dog"),
+ modelBuilder);
+ }
[ConditionalFact]
public virtual void Detects_invalid_function_overrides()
@@ -2506,19 +2540,366 @@ public void Detects_derived_entity_type_mapped_to_a_function()
{
var modelBuilder = CreateConventionModelBuilder();
- var function = modelBuilder.HasDbFunction(TestMethods.MethodAMi).Metadata;
+ var function = modelBuilder.HasDbFunction(BaseTestMethods.MethodAMi).Metadata;
- modelBuilder.Entity().HasNoKey();
+ modelBuilder.Entity().ToFunction(function.ModelName).HasNoKey();
modelBuilder.Entity().ToFunction(function.ModelName);
VerifyError(
RelationalStrings.InvalidMappedFunctionDerivedType(
nameof(TestMethods),
- "Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidatorTest+TestMethods.MethodA()",
+ "Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidatorTest+BaseTestMethods.MethodA()",
nameof(BaseTestMethods)),
modelBuilder);
}
+ [ConditionalFact]
+ public void Detects_derived_entity_type_mapped_to_a_different_function()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ var function = modelBuilder.HasDbFunction(BaseTestMethods.MethodAMi).Metadata;
+ var function2 = modelBuilder.HasDbFunction(TestMethods.MethodAMi).Metadata;
+
+ modelBuilder.Entity().ToFunction(function.ModelName).HasNoKey();
+ modelBuilder.Entity().ToFunction(function2.ModelName);
+
+ VerifyError(
+ RelationalStrings.TphDbFunctionMismatch(
+ nameof(TestMethods),
+ "Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidatorTest+TestMethods.MethodA()",
+ nameof(BaseTestMethods),
+ "Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidatorTest+BaseTestMethods.MethodA()"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public void Detects_multiple_entity_types_mapped_to_the_same_stored_procedure()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder.Entity(
+ db =>
+ {
+ db.HasBaseType((string)null);
+ db.OwnsOne(d => d.SomeTestMethods).DeleteUsingStoredProcedure("Delete",
+ s => s.HasParameter("DerivedTestMethodsId"));
+ db.OwnsOne(d => d.OtherTestMethods).DeleteUsingStoredProcedure("Delete",
+ s => s.HasParameter("DerivedTestMethodsId"));
+ });
+
+ VerifyError(
+ RelationalStrings.StoredProcedureTableSharing(
+ "DerivedTestMethods.OtherTestMethods#TestMethods",
+ "DerivedTestMethods.SomeTestMethods#TestMethods",
+ "Delete"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_keyless_entity_type_mapped_to_a_stored_procedure()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .Ignore(a => a.FavoritePerson)
+ .HasNoKey()
+ .InsertUsingStoredProcedure(s => s
+ .HasParameter(c => c.Id)
+ .HasParameter(c => c.Name));
+
+ VerifyError(
+ RelationalStrings.StoredProcedureKeyless(nameof(Animal), "Animal_Insert"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_derived_entity_type_mapped_to_a_stored_procedure_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity().HasDiscriminator("Discriminator");
+ modelBuilder.Entity().UpdateUsingStoredProcedure("Update", s => s.HasParameter(c => c.Breed));
+
+ VerifyError(
+ RelationalStrings.TphStoredProcedureMismatch(nameof(Cat), "Update", nameof(Animal), ""),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_derived_entity_type_mapped_to_a_different_stored_procedure_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity().UpdateUsingStoredProcedure("Update", s => s.HasParameter(c => c.Id))
+ .HasDiscriminator("Discriminator");
+ modelBuilder.Entity().UpdateUsingStoredProcedure("Update2", s => s.HasParameter(c => c.Breed));
+
+ VerifyError(
+ RelationalStrings.TphStoredProcedureMismatch(nameof(Cat), "Update2", nameof(Animal), "Update"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_derived_entity_type_mapped_to_a_different_stored_procedure_instance_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity().UpdateUsingStoredProcedure("Update", s => s.HasParameter(c => c.Id));
+ modelBuilder.Entity().UpdateUsingStoredProcedure("Update", s => s.HasParameter(c => c.Breed));
+
+ VerifyError(
+ RelationalStrings.StoredProcedureTphDuplicate(nameof(Cat), nameof(Animal), "Update"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_missing_stored_procedure_parameters_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UpdateUsingStoredProcedure("Update", s => s
+ .HasParameter(a => a.Id, p => p.HasName("MyId"))
+ .HasParameter(a => a.Name)
+ .HasParameter((Cat c) => c.Breed)
+ .HasResultColumn(a => a.Name))
+ .Property(a => a.Name).ValueGeneratedOnUpdate();
+ modelBuilder.Entity();
+
+ VerifyError(
+ RelationalStrings.StoredProcedurePropertiesNotMapped(nameof(Animal), "Update", "{'FavoritePersonId', 'Identity'}"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_unmatched_stored_procedure_parameters_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UpdateUsingStoredProcedure("Update", s => s.HasParameter("Missing"))
+ .Property(a => a.Name).ValueGeneratedOnUpdate();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureParameterNotFound("Missing", nameof(Animal), "Update"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_non_key_delete_stored_procedure_params_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .DeleteUsingStoredProcedure(s => s.HasParameter(a => a.Id)
+ .HasParameter(a => a.Name))
+ .Property(a => a.Name).ValueGeneratedOnUpdate();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureDeleteNonKeyProperty(nameof(Animal), nameof(Animal.Name), "Animal_Delete"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_unmatched_stored_procedure_result_columns_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UpdateUsingStoredProcedure("Update", "dbo", s => s.HasResultColumn("Missing"));
+
+ VerifyError(
+ RelationalStrings.StoredProcedureResultColumnNotFound("Missing", nameof(Animal), "dbo.Update"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_non_generated_insert_stored_procedure_result_columns_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .InsertUsingStoredProcedure(s => s.HasResultColumn(a => a.Name));
+ modelBuilder.Entity();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureResultColumnNotGenerated(nameof(Animal), nameof(Animal.Name), "Animal_Insert"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_non_generated_update_stored_procedure_result_columns_in_TPT()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .Ignore(a => a.FavoritePerson)
+ .UpdateUsingStoredProcedure(s => s.HasParameter(a => a.Id).HasParameter(a => a.Name))
+ .UseTptMappingStrategy();
+ modelBuilder.Entity()
+ .UpdateUsingStoredProcedure("Update", s => s.HasResultColumn(c => c.Breed));
+
+ VerifyError(
+ RelationalStrings.StoredProcedureResultColumnNotGenerated(nameof(Cat), nameof(Cat.Breed), "Update"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_delete_stored_procedure_result_columns_in_TPH()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .DeleteUsingStoredProcedure("Delete", s => s.HasResultColumn(a => a.Name))
+ .Property(a => a.Name).ValueGeneratedOnUpdate();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureResultColumnDelete(nameof(Animal), nameof(Animal.Name), "Delete"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Passes_on_valid_UsingDeleteStoredProcedure_in_TPT()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UseTptMappingStrategy()
+ .DeleteUsingStoredProcedure("Delete", s => s.HasParameter(a => a.Id))
+ .Property(a => a.Name).ValueGeneratedOnUpdate();
+ modelBuilder.Entity()
+ .DeleteUsingStoredProcedure(s => s.HasParameter(a => a.Id));
+
+ Validate(modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Passes_on_derived_entity_type_mapped_to_a_stored_procedure_in_TPT()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity().UseTptMappingStrategy();
+ modelBuilder.Entity().UpdateUsingStoredProcedure("Update", s => s
+ .HasParameter(c => c.Id)
+ .HasParameter(c => c.Breed)
+ .HasParameter(c => c.Identity));
+
+ Validate(modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Passes_on_derived_entity_type_not_mapped_to_a_stored_procedure_in_TPT()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UseTptMappingStrategy()
+ .UpdateUsingStoredProcedure("Update", s => s
+ .HasParameter(a => a.Id, p => p.HasName("MyId"))
+ .HasParameter(a => a.Name)
+ .HasParameter("FavoritePersonId")
+ .HasResultColumn(a => a.Name))
+ .Property(a => a.Name).ValueGeneratedOnUpdate();
+ modelBuilder.Entity();
+
+ Validate(modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_missing_stored_procedure_parameters_in_TPT()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UseTptMappingStrategy()
+ .UpdateUsingStoredProcedure("Update", s => s
+ .HasParameter(a => a.Id, p => p.HasName("MyId"))
+ .HasParameter(a => a.Name)
+ .HasParameter("FavoritePersonId")
+ .HasResultColumn(a => a.Name))
+ .Property(a => a.Name).ValueGeneratedOnUpdate();
+ modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(s => s.HasParameter(c => c.Breed));
+
+ VerifyError(
+ RelationalStrings.StoredProcedurePropertiesNotMapped(nameof(Cat), "Cat_Update", "{'Identity', 'Id'}"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_unmatched_stored_procedure_parameters_in_TPT()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UseTptMappingStrategy()
+ .UpdateUsingStoredProcedure("Update", s => s.HasParameter((Cat c) => c.Breed));
+ modelBuilder.Entity();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureParameterNotFound(nameof(Cat.Breed), nameof(Animal), "Update"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_unmatched_stored_procedure_result_columns_in_TPT()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UpdateUsingStoredProcedure("Update", "dbo", s => s.HasResultColumn((Cat c) => c.Breed))
+ .UseTptMappingStrategy();
+ modelBuilder.Entity();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureResultColumnNotFound(nameof(Cat.Breed), nameof(Animal), "dbo.Update"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_InsertUsingStoredProcedure_without_a_name()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity().InsertUsingStoredProcedure(s => { }).UseTpcMappingStrategy();
+ modelBuilder.Entity>();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureNoName(nameof(Abstract), "InsertStoredProcedure"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_missing_stored_procedure_parameters_in_TPC()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UseTpcMappingStrategy()
+ .UpdateUsingStoredProcedure("Update", s => s
+ .HasParameter(a => a.Id, p => p.HasName("MyId"))
+ .HasParameter(a => a.Name)
+ .HasParameter("FavoritePersonId")
+ .HasResultColumn(a => a.Name))
+ .Property(a => a.Name).ValueGeneratedOnUpdate();
+ modelBuilder.Entity()
+ .UpdateUsingStoredProcedure(s => s.HasParameter(c => c.Breed).HasParameter(a => a.Name));
+
+ VerifyError(
+ RelationalStrings.StoredProcedurePropertiesNotMapped(nameof(Cat), "Cat_Update", "{'Identity', 'Id', 'FavoritePersonId'}"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_unmatched_stored_procedure_parameters_in_TPC()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UseTpcMappingStrategy()
+ .UpdateUsingStoredProcedure("Update", s => s.HasParameter((Cat c) => c.Breed));
+ modelBuilder.Entity();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureParameterNotFound(nameof(Cat.Breed), nameof(Animal), "Update"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Detects_unmatched_stored_procedure_result_columns_in_TPC()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .UpdateUsingStoredProcedure("Update", "dbo", s => s.HasResultColumn((Cat c) => c.Breed))
+ .UseTpcMappingStrategy();
+ modelBuilder.Entity();
+
+ VerifyError(
+ RelationalStrings.StoredProcedureResultColumnNotFound(nameof(Cat.Breed), nameof(Animal), "dbo.Update"),
+ modelBuilder);
+ }
+
[ConditionalFact]
public void Passes_for_unnamed_index_with_all_properties_not_mapped_to_any_table()
{
@@ -2832,6 +3213,10 @@ public TestDecimalToDecimalConverter()
private class BaseTestMethods
{
+ public static readonly MethodInfo MethodAMi = typeof(BaseTestMethods).GetTypeInfo().GetDeclaredMethod(nameof(MethodA));
+
+ public static IQueryable MethodA()
+ => throw new NotImplementedException();
}
private class DerivedTestMethods : TestMethods
@@ -2843,14 +3228,14 @@ private class DerivedTestMethods : TestMethods
private class TestMethods : BaseTestMethods
{
- public static readonly MethodInfo MethodAMi = typeof(TestMethods).GetTypeInfo().GetDeclaredMethod(nameof(MethodA));
+ public static readonly new MethodInfo MethodAMi = typeof(TestMethods).GetTypeInfo().GetDeclaredMethod(nameof(MethodA));
public static readonly MethodInfo MethodBMi = typeof(TestMethods).GetTypeInfo().GetDeclaredMethod(nameof(MethodB));
public static readonly MethodInfo MethodCMi = typeof(TestMethods).GetTypeInfo().GetDeclaredMethod(nameof(MethodC));
public static readonly MethodInfo MethodDMi = typeof(TestMethods).GetTypeInfo().GetDeclaredMethod(nameof(MethodD));
public static readonly MethodInfo MethodEMi = typeof(TestMethods).GetTypeInfo().GetDeclaredMethod(nameof(MethodE));
public static readonly MethodInfo MethodFMi = typeof(TestMethods).GetTypeInfo().GetDeclaredMethod(nameof(MethodF));
- public static IQueryable MethodA()
+ public static new IQueryable MethodA()
=> throw new NotImplementedException();
public static IQueryable MethodB(int id)
diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
index b9c4b25fcc1..df861550b26 100644
--- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
+++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs
@@ -14,23 +14,24 @@ public abstract class RelationalNonRelationshipTestBase : NonRelationshipTestBas
public virtual void Can_use_table_splitting()
{
var modelBuilder = CreateModelBuilder();
- modelBuilder.Entity().SplitToTable("OrderDetails", s =>
- {
- s.ExcludeFromMigrations();
- var propertyBuilder = s.Property(o => o.CustomerId);
- var columnBuilder = propertyBuilder.HasColumnName("id");
- if (columnBuilder is IInfrastructure> genericBuilder)
+ modelBuilder.Entity().SplitToTable(
+ "OrderDetails", s =>
{
- Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
- Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
- }
- else
- {
- var nonGenericBuilder = (IInfrastructure)columnBuilder;
- Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
- Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
- }
- });
+ s.ExcludeFromMigrations();
+ var propertyBuilder = s.Property(o => o.CustomerId);
+ var columnBuilder = propertyBuilder.HasColumnName("id");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
modelBuilder.Ignore();
modelBuilder.Ignore();
@@ -55,9 +56,10 @@ public virtual void Can_use_table_splitting_with_schema()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity().ToTable("Order", "dbo")
- .SplitToTable("OrderDetails", "sch", s =>
- s.ExcludeFromMigrations()
- .Property(o => o.CustomerId).HasColumnName("id"));
+ .SplitToTable(
+ "OrderDetails", "sch", s =>
+ s.ExcludeFromMigrations()
+ .Property(o => o.CustomerId).HasColumnName("id"));
modelBuilder.Ignore();
modelBuilder.Ignore();
@@ -68,9 +70,12 @@ public virtual void Can_use_table_splitting_with_schema()
Assert.False(entity.IsTableExcludedFromMigrations());
Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo")));
Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "sch")));
- Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "sch")));
- Assert.Equal(RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"),
- Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order"))).Message);
+ Assert.Same(
+ entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "sch")));
+ Assert.Equal(
+ RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"),
+ Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order")))
+ .Message);
var customerId = entity.FindProperty(nameof(Order.CustomerId))!;
Assert.Equal("CustomerId", customerId.GetColumnName());
@@ -85,22 +90,23 @@ public virtual void Can_use_view_splitting()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity().ToView("Order")
- .SplitToView("OrderDetails", s =>
- {
- var propertyBuilder = s.Property(o => o.CustomerId);
- var columnBuilder = propertyBuilder.HasColumnName("id");
- if (columnBuilder is IInfrastructure> genericBuilder)
- {
- Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
- Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
- }
- else
- {
- var nonGenericBuilder = (IInfrastructure)columnBuilder;
- Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
- Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
- }
- });
+ .SplitToView(
+ "OrderDetails", s =>
+ {
+ var propertyBuilder = s.Property(o => o.CustomerId);
+ var columnBuilder = propertyBuilder.HasColumnName("id");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
modelBuilder.Ignore();
modelBuilder.Ignore();
@@ -122,8 +128,9 @@ public virtual void Can_use_view_splitting_with_schema()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity().ToView("Order", "dbo")
- .SplitToView("OrderDetails", "sch", s =>
- s.Property(o => o.CustomerId).HasColumnName("id"));
+ .SplitToView(
+ "OrderDetails", "sch", s =>
+ s.Property(o => o.CustomerId).HasColumnName("id"));
modelBuilder.Ignore();
modelBuilder.Ignore();
@@ -131,9 +138,12 @@ public virtual void Can_use_view_splitting_with_schema()
var entity = model.FindEntityType(typeof(Order))!;
- Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails", "sch")));
- Assert.Equal(RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"),
- Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.View("Order"))).Message);
+ Assert.Same(
+ entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails", "sch")));
+ Assert.Equal(
+ RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"),
+ Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.View("Order")))
+ .Message);
var customerId = entity.FindProperty(nameof(Order.CustomerId))!;
Assert.Equal("CustomerId", customerId.GetColumnName());
@@ -146,6 +156,180 @@ public virtual void Can_use_view_splitting_with_schema()
public abstract class RelationalInheritanceTestBase : InheritanceTestBase
{
+ [ConditionalFact]
+ public virtual void Can_use_table_splitting()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.Entity().SplitToTable(
+ "OrderDetails", s =>
+ {
+ s.ExcludeFromMigrations();
+ var propertyBuilder = s.Property(o => o.CustomerId);
+ var columnBuilder = propertyBuilder.HasColumnName("id");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Order))!;
+
+ Assert.False(entity.IsTableExcludedFromMigrations());
+ Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order")));
+ Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails")));
+ Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails")));
+
+ var customerId = entity.FindProperty(nameof(Order.CustomerId))!;
+ Assert.Equal("CustomerId", customerId.GetColumnName());
+ Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order")));
+ Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails")));
+ Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails")));
+ }
+
+ [ConditionalFact]
+ public virtual void Sproc_overrides_update_when_renamed_in_TPH()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ modelBuilder.Entity()
+ .Ignore(s => s.SpecialBookLabel)
+ .InsertUsingStoredProcedure(
+ s => s.SuppressTransactions().HasAnnotation("foo", "bar1")
+ .HasParameter(b => b.BookId)
+ .HasParameter("Discriminator")
+ .HasResultColumn(
+ b => b.Id, p =>
+ {
+ var resultColumnBuilder = p.HasName("InsertId");
+ var nonGenericBuilder = (IInfrastructure)resultColumnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ }))
+ .UpdateUsingStoredProcedure(
+ s => s.SuppressTransactions().HasAnnotation("foo", "bar2")
+ .HasParameter(
+ b => b.Id, p =>
+ {
+ var parameterBuilder = p.HasName("UpdateId");
+ var nonGenericBuilder = (IInfrastructure)parameterBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ })
+ .HasParameter(b => b.BookId))
+ .DeleteUsingStoredProcedure(
+ s => s.SuppressTransactions().HasAnnotation("foo", "bar3")
+ .HasParameter(b => b.Id, p => p.HasName("DeleteId")));
+
+ modelBuilder.Entity()
+ .Ignore(s => s.BookLabel);
+ modelBuilder.Entity();
+
+ modelBuilder.Entity()
+ .InsertUsingStoredProcedure("Insert", s => { })
+ .UpdateUsingStoredProcedure("Update", "dbo", s => { })
+ .DeleteUsingStoredProcedure("BookLabel_Delete", s => { });
+
+ var model = modelBuilder.FinalizeModel();
+
+ var bookLabel = model.FindEntityType(typeof(BookLabel))!;
+ var insertSproc = bookLabel.GetInsertStoredProcedure()!;
+ Assert.Equal("Insert", insertSproc.Name);
+ Assert.Null(insertSproc.Schema);
+ Assert.Equal(new[] { "BookId", "Discriminator" }, insertSproc.Parameters);
+ Assert.Equal(new[] { "Id" }, insertSproc.ResultColumns);
+ Assert.True(insertSproc.AreTransactionsSuppressed);
+ Assert.Equal("bar1", insertSproc["foo"]);
+ Assert.Same(bookLabel, insertSproc.EntityType);
+
+ var updateSproc = bookLabel.GetUpdateStoredProcedure()!;
+ Assert.Equal("Update", updateSproc.Name);
+ Assert.Equal("dbo", updateSproc.Schema);
+ Assert.Equal(new[] { "Id", "BookId" }, updateSproc.Parameters);
+ Assert.Empty(updateSproc.ResultColumns);
+ Assert.True(updateSproc.AreTransactionsSuppressed);
+ Assert.Equal("bar2", updateSproc["foo"]);
+ Assert.Same(bookLabel, updateSproc.EntityType);
+
+ var deleteSproc = bookLabel.GetDeleteStoredProcedure()!;
+ Assert.Equal("BookLabel_Delete", deleteSproc.Name);
+ Assert.Null(deleteSproc.Schema);
+ Assert.Equal(new[] { "Id" }, deleteSproc.Parameters);
+ Assert.Empty(deleteSproc.ResultColumns);
+ Assert.True(deleteSproc.AreTransactionsSuppressed);
+ Assert.Equal("bar3", deleteSproc["foo"]);
+ Assert.Same(bookLabel, deleteSproc.EntityType);
+
+ var id = bookLabel.FindProperty(nameof(BookLabel.Id))!;
+ Assert.Equal(3, id.GetOverrides().Count());
+ Assert.Equal(
+ "InsertId",
+ id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.InsertStoredProcedure)!.Value));
+ Assert.Equal(
+ "UpdateId",
+ id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.UpdateStoredProcedure)!.Value));
+ Assert.Equal(
+ "DeleteId",
+ id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.DeleteStoredProcedure)!.Value));
+
+ var specialBookLabel = model.FindEntityType(typeof(SpecialBookLabel))!;
+ Assert.Same(insertSproc, specialBookLabel.GetInsertStoredProcedure());
+ Assert.Same(updateSproc, specialBookLabel.GetUpdateStoredProcedure());
+ Assert.Same(deleteSproc, specialBookLabel.GetDeleteStoredProcedure());
+
+ var extraSpecialBookLabel = model.FindEntityType(typeof(ExtraSpecialBookLabel))!;
+ Assert.Same(insertSproc, extraSpecialBookLabel.GetInsertStoredProcedure());
+ Assert.Same(updateSproc, extraSpecialBookLabel.GetUpdateStoredProcedure());
+ Assert.Same(deleteSproc, extraSpecialBookLabel.GetDeleteStoredProcedure());
+ }
+
+ [ConditionalFact]
+ public virtual void Sproc_overrides_are_removed_with_sproc()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ modelBuilder.Entity()
+ .Ignore(s => s.SpecialBookLabel)
+ .InsertUsingStoredProcedure(
+ s => s.HasParameter(b => b.BookId)
+ .HasResultColumn(b => b.Id, p => p.HasName("InsertId")))
+ .UpdateUsingStoredProcedure(
+ s => s.HasParameter(b => b.Id, p => p.HasName("UpdateId"))
+ .HasParameter(b => b.BookId))
+ .DeleteUsingStoredProcedure(
+ s => s.HasParameter(b => b.Id, p => p.HasName("DeleteId")));
+
+ var bookLabelEntityType = modelBuilder.Entity().Metadata;
+
+ bookLabelEntityType.RemoveInsertStoredProcedure();
+ bookLabelEntityType.RemoveUpdateStoredProcedure();
+ bookLabelEntityType.RemoveDeleteStoredProcedure();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var bookLabel = model.FindEntityType(typeof(BookLabel))!;
+ Assert.Null(bookLabel.GetInsertStoredProcedure());
+ Assert.Null(bookLabel.GetUpdateStoredProcedure());
+ Assert.Null(bookLabel.GetDeleteStoredProcedure());
+
+ var id = bookLabel.FindProperty(nameof(BookLabel.Id))!;
+ Assert.Empty(id.GetOverrides());
+ }
}
public abstract class RelationalOneToManyTestBase : OneToManyTestBase
@@ -180,23 +364,24 @@ public virtual void Can_use_table_splitting_with_owned_reference()
{
lb.Ignore(l => l.Book);
lb.Property("ShadowProp");
-
- lb.SplitToTable("BookLabelDetails", s =>
- {
- var propertyBuilder = s.Property(o => o.Id);
- var columnBuilder = propertyBuilder.HasColumnName("bid");
- if (columnBuilder is IInfrastructure> genericBuilder)
- {
- Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
- Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
- }
- else
+
+ lb.SplitToTable(
+ "BookLabelDetails", s =>
{
- var nonGenericBuilder = (IInfrastructure)columnBuilder;
- Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
- Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
- }
- });
+ var propertyBuilder = s.Property(o => o.Id);
+ var columnBuilder = propertyBuilder.HasColumnName("bid");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
});
modelBuilder.Entity()
.OwnsOne(b => b.AlternateLabel);
@@ -250,22 +435,23 @@ public virtual void Can_use_view_splitting_with_owned_collection()
r.Property("ShadowProp");
r.ToView("Order");
- r.SplitToView("OrderDetails", s =>
- {
- var propertyBuilder = s.Property(o => o.AnotherCustomerId);
- var columnBuilder = propertyBuilder.HasColumnName("cid");
- if (columnBuilder is IInfrastructure> genericBuilder)
- {
- Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
- Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
- }
- else
+ r.SplitToView(
+ "OrderDetails", s =>
{
- var nonGenericBuilder = (IInfrastructure)columnBuilder;
- Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
- Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
- }
- });
+ var propertyBuilder = s.Property(o => o.AnotherCustomerId);
+ var columnBuilder = propertyBuilder.HasColumnName("cid");
+ if (columnBuilder is IInfrastructure> genericBuilder)
+ {
+ Assert.IsType>(genericBuilder.Instance.GetInfrastructure>());
+ Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides);
+ }
+ else
+ {
+ var nonGenericBuilder = (IInfrastructure)columnBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides);
+ }
+ });
});
var model = modelBuilder.FinalizeModel();
@@ -292,6 +478,111 @@ public virtual void Can_use_view_splitting_with_owned_collection()
Assert.True(((IConventionRelationalPropertyOverrides)overrides).IsInModel);
Assert.Same(anotherCustomerId, overrides.Property);
}
+
+ [ConditionalFact]
+ public virtual void Can_use_sproc_mapping_with_owned_reference()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ modelBuilder.Entity().OwnsOne(
+ b => b.Label, lb =>
+ {
+ lb.Ignore(l => l.Book);
+ lb.Ignore(s => s.SpecialBookLabel);
+
+ lb.Property(l => l.Id).ValueGeneratedOnUpdate();
+
+ lb.InsertUsingStoredProcedure(
+ s => s.SuppressTransactions().HasAnnotation("foo", "bar1")
+ .HasParameter(b => b.Id)
+ .HasParameter(b => b.BookId, p => p.HasName("InsertId")))
+ .UpdateUsingStoredProcedure(
+ s => s.SuppressTransactions().HasAnnotation("foo", "bar2")
+ .HasParameter(b => b.Id)
+ .HasParameter(b => b.BookId, p =>
+ {
+ var parameterBuilder = p.HasName("UpdateId");
+ var nonGenericBuilder = (IInfrastructure)parameterBuilder;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ })
+ .HasResultColumn(
+ b => b.Id, p =>
+ {
+ var nonGenericBuilder = (IInfrastructure)p;
+ Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure());
+ }))
+ .DeleteUsingStoredProcedure(
+ s => s.SuppressTransactions().HasAnnotation("foo", "bar3")
+ .HasParameter(b => b.BookId, p => p.HasName("DeleteId")));
+ });
+ modelBuilder.Entity()
+ .OwnsOne(b => b.AlternateLabel);
+
+ modelBuilder.Entity().OwnsOne(
+ b => b.Label, lb =>
+ {
+ lb.InsertUsingStoredProcedure("Insert", s => { });
+ lb.UpdateUsingStoredProcedure("Update", "dbo", s => { });
+ lb.DeleteUsingStoredProcedure("BookLabel_Delete", s => { });
+ });
+
+ var model = modelBuilder.FinalizeModel();
+
+ Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel)));
+ Assert.Equal(3, model.GetEntityTypes().Count());
+
+ var book = model.FindEntityType(typeof(Book))!;
+ var bookOwnership1 = book.FindNavigation(nameof(Book.Label))!.ForeignKey;
+ var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel))!.ForeignKey;
+ Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType);
+
+ var insertSproc = bookOwnership1.DeclaringEntityType.GetInsertStoredProcedure()!;
+ Assert.Equal("Insert", insertSproc.Name);
+ Assert.Null(insertSproc.Schema);
+ Assert.Equal(new[] { "Id", "BookId" }, insertSproc.Parameters);
+ Assert.Empty(insertSproc.ResultColumns);
+ Assert.True(insertSproc.AreTransactionsSuppressed);
+ Assert.Equal("bar1", insertSproc["foo"]);
+ Assert.Same(bookOwnership1.DeclaringEntityType, insertSproc.EntityType);
+
+ var updateSproc = bookOwnership1.DeclaringEntityType.GetUpdateStoredProcedure()!;
+ Assert.Equal("Update", updateSproc.Name);
+ Assert.Equal("dbo", updateSproc.Schema);
+ Assert.Equal(new[] { "Id", "BookId" }, updateSproc.Parameters);
+ Assert.Equal(new[] { "Id" }, updateSproc.ResultColumns);
+ Assert.True(updateSproc.AreTransactionsSuppressed);
+ Assert.Equal("bar2", updateSproc["foo"]);
+ Assert.Same(bookOwnership1.DeclaringEntityType, updateSproc.EntityType);
+
+ var deleteSproc = bookOwnership1.DeclaringEntityType.GetDeleteStoredProcedure()!;
+ Assert.Equal("BookLabel_Delete", deleteSproc.Name);
+ Assert.Null(deleteSproc.Schema);
+ Assert.Equal(new[] { "BookId" }, deleteSproc.Parameters);
+ Assert.Empty(deleteSproc.ResultColumns);
+ Assert.True(deleteSproc.AreTransactionsSuppressed);
+ Assert.Equal("bar3", deleteSproc["foo"]);
+ Assert.Same(bookOwnership1.DeclaringEntityType, deleteSproc.EntityType);
+
+ var bookId = bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.BookId))!;
+ Assert.Equal(3, bookId.GetOverrides().Count());
+ Assert.Equal(
+ "InsertId",
+ bookId.GetColumnName(StoreObjectIdentifier.Create(bookOwnership1.DeclaringEntityType, StoreObjectType.InsertStoredProcedure)!.Value));
+ Assert.Equal(
+ "UpdateId",
+ bookId.GetColumnName(StoreObjectIdentifier.Create(bookOwnership1.DeclaringEntityType, StoreObjectType.UpdateStoredProcedure)!.Value));
+ Assert.Equal(
+ "DeleteId",
+ bookId.GetColumnName(StoreObjectIdentifier.Create(bookOwnership1.DeclaringEntityType, StoreObjectType.DeleteStoredProcedure)!.Value));
+
+ Assert.Null(bookOwnership2.DeclaringEntityType.GetInsertStoredProcedure());
+ Assert.Null(bookOwnership2.DeclaringEntityType.GetUpdateStoredProcedure());
+ Assert.Null(bookOwnership2.DeclaringEntityType.GetDeleteStoredProcedure());
+ }
}
public abstract class TestTableBuilder
@@ -414,7 +705,8 @@ public override string? Name
public override string? Schema
=> TableBuilder.Schema;
- OwnedNavigationTableBuilder IInfrastructure>.Instance
+ OwnedNavigationTableBuilder
+ IInfrastructure>.Instance
=> TableBuilder;
protected virtual TestOwnedNavigationTableBuilder Wrap(
@@ -432,7 +724,8 @@ public override TestColumnBuilder Property(Expression :
- TestOwnedNavigationTableBuilder, IInfrastructure
+ TestOwnedNavigationTableBuilder,
+ IInfrastructure
where TOwnerEntity : class
where TDependentEntity : class
{
@@ -585,7 +878,8 @@ public override string? Name
public override string? Schema
=> TableBuilder.Schema;
- OwnedNavigationSplitTableBuilder IInfrastructure>.Instance
+ OwnedNavigationSplitTableBuilder
+ IInfrastructure>.Instance
=> TableBuilder;
protected virtual TestOwnedNavigationSplitTableBuilder Wrap(
@@ -603,7 +897,8 @@ public override TestColumnBuilder Property(Expression :
- TestOwnedNavigationSplitTableBuilder, IInfrastructure
+ TestOwnedNavigationSplitTableBuilder,
+ IInfrastructure
where TOwnerEntity : class
where TDependentEntity : class
{
@@ -623,7 +918,8 @@ public override string? Schema
OwnedNavigationSplitTableBuilder IInfrastructure.Instance
=> TableBuilder;
- protected virtual TestOwnedNavigationSplitTableBuilder Wrap(OwnedNavigationSplitTableBuilder tableBuilder)
+ protected virtual TestOwnedNavigationSplitTableBuilder Wrap(
+ OwnedNavigationSplitTableBuilder tableBuilder)
=> new NonGenericTestOwnedNavigationSplitTableBuilder(tableBuilder);
public override TestOwnedNavigationSplitTableBuilder ExcludeFromMigrations(bool excluded = true)
@@ -759,7 +1055,8 @@ public abstract class TestOwnedNavigationViewBuilder Property(string propertyName);
- public abstract TestViewColumnBuilder Property(Expression> propertyExpression);
+ public abstract TestViewColumnBuilder Property(
+ Expression> propertyExpression);
}
public class GenericTestOwnedNavigationViewBuilder :
@@ -781,7 +1078,8 @@ public override string? Name
public override string? Schema
=> ViewBuilder.Schema;
- OwnedNavigationViewBuilder IInfrastructure>.Instance
+ OwnedNavigationViewBuilder
+ IInfrastructure>.Instance
=> ViewBuilder;
protected virtual TestOwnedNavigationViewBuilder Wrap(
@@ -791,12 +1089,14 @@ protected virtual TestOwnedNavigationViewBuilder
public override TestViewColumnBuilder Property(string propertyName)
=> new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName));
- public override TestViewColumnBuilder Property(Expression> propertyExpression)
+ public override TestViewColumnBuilder Property(
+ Expression> propertyExpression)
=> new GenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression.GetPropertyAccess().Name));
}
public class NonGenericTestOwnedNavigationViewBuilder :
- TestOwnedNavigationViewBuilder, IInfrastructure
+ TestOwnedNavigationViewBuilder,
+ IInfrastructure
where TOwnerEntity : class
where TDependentEntity : class
{
@@ -822,7 +1122,8 @@ protected virtual TestOwnedNavigationViewBuilder
public override TestViewColumnBuilder Property(string propertyName)
=> new NonGenericTestViewColumnBuilder