From 9f6b6cafa4bda3607b5cac70e5d28e9d4f48955a Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 2 Aug 2022 00:41:18 -0700 Subject: [PATCH 1/8] Add metadata for optimistic concurrency support in sprocs Add table mapping to sproc mapping Part of #28435 --- .../Design/AnnotationCodeGenerator.cs | 3 +- ...nalCSharpRuntimeAnnotationCodeGenerator.cs | 81 ++- .../RelationalPropertyBuilderExtensions.cs | 48 -- .../RelationalPropertyExtensions.cs | 48 -- .../RelationalModelRuntimeInitializer.cs | 6 +- .../RelationalModelValidator.cs | 35 +- .../IConventionStoredProcedureBuilder.cs | 61 ++- ...nventionStoredProcedureParameterBuilder.cs | 64 +++ ...ntionStoredProcedureResultColumnBuilder.cs | 43 ++ .../OwnedNavigationStoredProcedureBuilder.cs | 97 +++- ...OwnedNavigationStoredProcedureBuilder``.cs | 104 +++- .../Builders/StoredProcedureBuilder.cs | 171 +++++-- .../Builders/StoredProcedureBuilder`.cs | 133 ++++- .../StoredProcedureParameterBuilder.cs | 38 +- .../StoredProcedureResultColumnBuilder.cs | 30 +- .../RelationalRuntimeModelConvention.cs | 78 ++- .../RelationalValueGenerationConvention.cs | 80 +-- .../IConventionDbFunctionParameter.cs | 6 +- .../IConventionRelationalPropertyOverrides.cs | 16 - .../Metadata/IConventionStoredProcedure.cs | 93 +++- .../IConventionStoredProcedureParameter.cs | 49 ++ .../IConventionStoredProcedureResultColumn.cs | 34 ++ .../Metadata/IMutableDbFunctionParameter.cs | 4 +- .../IMutableRelationalPropertyOverrides.cs | 9 - .../Metadata/IMutableStoredProcedure.cs | 79 ++- .../IMutableStoredProcedureParameter.cs | 27 + .../IMutableStoredProcedureResultColumn.cs | 20 + .../Metadata/IReadOnlyDbFunctionParameter.cs | 2 +- .../IReadOnlyRelationalPropertyOverrides.cs | 6 - .../Metadata/IReadOnlyStoredProcedure.cs | 49 +- .../IReadOnlyStoredProcedureParameter.cs | 93 ++++ .../IReadOnlyStoredProcedureResultColumn.cs | 66 +++ .../Metadata/IStoredProcedure.cs | 48 +- .../Metadata/IStoredProcedureMapping.cs | 5 + .../Metadata/IStoredProcedureParameter.cs | 20 + .../IStoredProcedureParameterMapping.cs | 7 +- .../Metadata/IStoredProcedureResultColumn.cs | 20 + .../IStoredProcedureResultColumnMapping.cs | 7 +- ...ntValuePropertyStoredProcedureParameter.cs | 282 +++++++++++ .../Metadata/Internal/DbFunction.cs | 16 +- .../Metadata/Internal/DbFunctionParameter.cs | 45 +- ...InternalStoredProcedureParameterBuilder.cs | 52 ++ ...ernalStoredProcedureResultColumnBuilder.cs | 40 ++ .../IRuntimeStoredProcedureParameter.cs | 21 + .../IRuntimeStoredProcedureResultColumn.cs | 21 + ...PropertyStoredProcedureParameterBuilder.cs | 148 ++++++ ...PropertyStoredProcedureParameterBuilder.cs | 148 ++++++ ...AffectedStoredProcedureParameterBuilder.cs | 146 ++++++ ...ectedStoredProcedureResultColumnBuilder.cs | 92 ++++ .../InternalStoredProcedureBuilder.cs | 229 ++++++++- ...ernalStoredProcedureResultColumnBuilder.cs | 96 ++++ ...alValuePropertyStoredProcedureParameter.cs | 285 +++++++++++ .../PropertyStoredProcedureResultColumn.cs | 213 ++++++++ .../Metadata/Internal/RelationalModel.cs | 158 ++++-- .../RowsAffectedStoredProcedureParameter.cs | 238 +++++++++ ...RowsAffectedStoredProcedureResultColumn.cs | 203 ++++++++ .../Metadata/Internal/StoreStoredProcedure.cs | 14 +- .../Internal/StoreStoredProcedureParameter.cs | 12 + .../StoreStoredProcedureResultColumn.cs | 17 +- .../Metadata/Internal/StoredProcedure.cs | 474 ++++++++++++++++-- .../Internal/StoredProcedureMapping.cs | 17 + .../StoredProcedureParameterMapping.cs | 23 +- .../StoredProcedureResultColumnMapping.cs | 25 +- .../Metadata/RelationalAnnotationNames.cs | 5 - .../Metadata/RuntimeStoredProcedure.cs | 115 ++++- .../RuntimeStoredProcedureParameter.cs | 132 +++++ .../RuntimeStoredProcedureResultColumn.cs | 112 +++++ .../Metadata/RuntimeTrigger.cs | 16 +- .../Properties/RelationalStrings.Designer.cs | 80 +++ .../Properties/RelationalStrings.resx | 30 ++ .../SqlServerOnDeleteConvention.cs | 2 - .../Metadata/Internal/EntityTypeExtensions.cs | 3 +- .../Design/CSharpMigrationsGeneratorTest.cs | 1 - .../CSharpRuntimeModelCodeGeneratorTest.cs | 97 ++-- .../Query/SimpleQueryRelationalTestBase.cs | 1 - .../RelationalModelValidatorTest.cs | 3 +- .../Metadata/RelationalModelTest.cs | 84 ++-- .../RelationalModelBuilderTest.cs | 278 ++++++++-- .../RelationalApiConsistencyTest.cs | 46 +- 79 files changed, 5213 insertions(+), 587 deletions(-) create mode 100644 src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/IConventionStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/IConventionStoredProcedureResultColumn.cs create mode 100644 src/EFCore.Relational/Metadata/IMutableStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/IMutableStoredProcedureResultColumn.cs create mode 100644 src/EFCore.Relational/Metadata/IReadOnlyStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/IReadOnlyStoredProcedureResultColumn.cs create mode 100644 src/EFCore.Relational/Metadata/IStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/IStoredProcedureResultColumn.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureParameterBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureResultColumnBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/IRuntimeStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/IRuntimeStoredProcedureResultColumn.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/InternalCurrentValuePropertyStoredProcedureParameterBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/InternalOriginalValuePropertyStoredProcedureParameterBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureParameterBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureResultColumnBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/OriginalValuePropertyStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/PropertyStoredProcedureResultColumn.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureResultColumn.cs create mode 100644 src/EFCore.Relational/Metadata/RuntimeStoredProcedureParameter.cs create mode 100644 src/EFCore.Relational/Metadata/RuntimeStoredProcedureResultColumn.cs diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 07409031d2f..63c0d25dcbc 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -35,8 +35,7 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator RelationalAnnotationNames.InsertStoredProcedure, RelationalAnnotationNames.UpdateStoredProcedure, RelationalAnnotationNames.MappingFragments, - RelationalAnnotationNames.RelationalOverrides, - RelationalAnnotationNames.ParameterDirection + RelationalAnnotationNames.RelationalOverrides }; #region MethodInfos diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index fc24006ed32..b2b7d5e5618 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Data; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Design.Internal; @@ -166,16 +167,16 @@ private void Create( mainBuilder.AppendLine(");").DecrementIndent() .AppendLine(); - var parameterParameters = parameters with { TargetName = functionVariable }; + parameters = parameters with { TargetName = functionVariable }; foreach (var parameter in function.Parameters) { - Create(parameter, parameterParameters); + Create(parameter, parameters); } CreateAnnotations( function, Generate, - parameters with { TargetName = functionVariable }); + parameters); mainBuilder .Append(functionsVariable).Append("[").Append(code.Literal(function.ModelName)).Append("] = ").Append(functionVariable) @@ -481,6 +482,8 @@ public virtual void Generate(ITrigger trigger, CSharpRuntimeAnnotationCodeGenera private void Create(IStoredProcedure storedProcedure, string sprocVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { AddNamespace(typeof(RuntimeStoredProcedure), parameters.Namespaces); + AddNamespace(typeof(ParameterDirection), parameters.Namespaces); + var code = Dependencies.CSharpHelper; var mainBuilder = parameters.MainBuilder; mainBuilder @@ -488,29 +491,27 @@ private void Create(IStoredProcedure storedProcedure, string sprocVariable, CSha .Append(parameters.TargetName).AppendLine(",") .Append(code.Literal(storedProcedure.Name)).AppendLine(",") .Append(code.Literal(storedProcedure.Schema)).AppendLine(",") + .Append(code.Literal(storedProcedure.AreRowsAffectedReturned)).AppendLine(",") .Append(code.Literal(storedProcedure.AreTransactionsSuppressed)) .AppendLine(");") .DecrementIndent() .AppendLine(); - + + parameters = parameters with { TargetName = sprocVariable }; foreach (var parameter in storedProcedure.Parameters) { - mainBuilder.Append(sprocVariable).Append(".AddParameter(") - .Append(code.Literal(parameter)) - .AppendLine(");"); + Create(parameter, parameters); } foreach (var resultColumn in storedProcedure.ResultColumns) { - mainBuilder.Append(sprocVariable).Append(".AddResultColumn(") - .Append(code.Literal(resultColumn)) - .AppendLine(");"); + Create(resultColumn, parameters); } CreateAnnotations( storedProcedure, Generate, - parameters with { TargetName = sprocVariable }); + parameters); } /// @@ -521,6 +522,64 @@ private void Create(IStoredProcedure storedProcedure, string sprocVariable, CSha public virtual void Generate(IStoredProcedure storedProcedure, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) => GenerateSimpleAnnotations(parameters); + private void Create(IStoredProcedureParameter parameter, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var code = Dependencies.CSharpHelper; + var mainBuilder = parameters.MainBuilder; + var parameterVariable = code.Identifier(parameter.Name, parameters.ScopeVariables, capitalize: false); + + mainBuilder + .Append("var ").Append(parameterVariable).Append(" = ") + .Append(parameters.TargetName).AppendLine(".AddParameter(").IncrementIndent() + .Append(code.Literal(parameter.Name)).Append(", ") + .Append(code.Literal(parameter.Direction)).Append(", ") + .Append(code.Literal(parameter.ForRowsAffected)).Append(", ") + .Append(code.Literal(parameter.PropertyName!)).Append(", ") + .Append(code.Literal(parameter.ForOriginalValue)) + .AppendLine(");").DecrementIndent(); + + CreateAnnotations( + parameter, + Generate, + parameters with { TargetName = parameterVariable }); + } + + /// + /// Generates code to create the given annotations. + /// + /// The stored procedure to which the annotations are applied. + /// Additional parameters used during code generation. + public virtual void Generate(IStoredProcedureParameter storedProcedure, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + => GenerateSimpleAnnotations(parameters); + + private void Create(IStoredProcedureResultColumn resultColumn, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var code = Dependencies.CSharpHelper; + var mainBuilder = parameters.MainBuilder; + var resultColumnVariable = code.Identifier(resultColumn.Name, parameters.ScopeVariables, capitalize: false); + + mainBuilder + .Append("var ").Append(resultColumnVariable).Append(" = ") + .Append(parameters.TargetName).AppendLine(".AddResultColumn(").IncrementIndent() + .Append(code.Literal(resultColumn.Name)).Append(", ") + .Append(code.Literal(resultColumn.ForRowsAffected)).Append(", ") + .Append(code.Literal(resultColumn.PropertyName!)) + .AppendLine(");").DecrementIndent(); + + CreateAnnotations( + resultColumn, + Generate, + parameters with { TargetName = resultColumnVariable }); + } + + /// + /// Generates code to create the given annotations. + /// + /// The stored procedure to which the annotations are applied. + /// Additional parameters used during code generation. + public virtual void Generate(IStoredProcedureResultColumn storedProcedure, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + => GenerateSimpleAnnotations(parameters); + /// /// Generates code to create the given annotations. /// diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs index 0aa7b023fe9..1547bc19942 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs @@ -357,54 +357,6 @@ public static bool CanSetIsFixedLength( bool? fixedLength, bool fromDataAnnotation = false) => propertyBuilder.CanSetAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength, fromDataAnnotation); - - /// - /// Sets the direction of the stored procedure parameter. - /// - /// The builder for the property being configured. - /// The direction. - /// The identifier of the stored procedure. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - public static IConventionPropertyBuilder? HasDirection( - this IConventionPropertyBuilder propertyBuilder, - ParameterDirection direction, - in StoreObjectIdentifier storeObject, - bool fromDataAnnotation = false) - { - if (!propertyBuilder.CanSetDirection(direction, storeObject, fromDataAnnotation)) - { - return null; - } - - propertyBuilder.Metadata.SetDirection(direction, storeObject, fromDataAnnotation); - return propertyBuilder; - } - - /// - /// Returns a value indicating whether the given direction can be configured on the corresponding stored procedure parameter. - /// - /// The builder for the property being configured. - /// The direction. - /// The identifier of the stored procedure. - /// Indicates whether the configuration was specified using a data annotation. - /// if the property can be mapped to the given column. - public static bool CanSetDirection( - this IConventionPropertyBuilder propertyBuilder, - ParameterDirection direction, - in StoreObjectIdentifier storeObject, - bool fromDataAnnotation = false) - { - var overrides = (IConventionRelationalPropertyOverrides?)RelationalPropertyOverrides.Find( - propertyBuilder.Metadata, storeObject); - return overrides == null - || (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) - .Overrides(overrides.GetDirectionConfigurationSource()) - || overrides.Direction == direction; - } /// /// Configures the default value expression for the column that the property maps to when targeting a diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 1467e0e1479..ac1baec11b5 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Text; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; // ReSharper disable once CheckNamespace @@ -1170,53 +1169,6 @@ private static bool IsOptionalSharingDependent( return optional ?? (entityType.BaseType != null && entityType.FindDiscriminatorProperty() != null); } - - /// - /// Gets the direction of the corresponding stored procedure parameter. - /// - /// The property. - /// The identifier of the stored procedure containing the parameter. - /// - /// The direction of the corresponding stored procedure parameter. - /// - public static System.Data.ParameterDirection GetDirection(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) - => property.FindOverrides(storeObject)?.Direction ?? System.Data.ParameterDirection.Input; - - /// - /// Sets the direction of the corresponding stored procedure parameter. - /// - /// The property. - /// The direction to set. - /// The identifier of the stored procedure containing the parameter. - public static void SetDirection( - this IMutableProperty property, - System.Data.ParameterDirection? direction, - in StoreObjectIdentifier storeObject) - => property.GetOrCreateOverrides(storeObject).Direction = direction; - - /// - /// Sets the direction of the corresponding stored procedure parameter. - /// - /// The property. - /// The direction to set. - /// The identifier of the stored procedure containing the parameter. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - public static System.Data.ParameterDirection? SetDirection( - this IConventionProperty property, - System.Data.ParameterDirection? direction, - in StoreObjectIdentifier storeObject, - bool fromDataAnnotation = false) - => property.GetOrCreateOverrides(storeObject, fromDataAnnotation).SetDirection(direction, fromDataAnnotation); - - /// - /// Gets the for the stored procedure parameter direction. - /// - /// The property. - /// The identifier of the stored procedure containing the parameter. - /// The for the stored procedure parameter direction. - public static ConfigurationSource? GetDirectionConfigurationSource(this IConventionProperty property, in StoreObjectIdentifier storeObject) - => property.FindOverrides(storeObject)?.GetDirectionConfigurationSource(); /// /// Returns the comment for the column this property is mapped to. diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs b/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs index 773eb861576..d83b18b92fd 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs @@ -62,7 +62,11 @@ protected override void InitializeModel(IModel model, bool designTime, bool prev } else { - RelationalModel.Add(model, RelationalDependencies.RelationalAnnotationProvider, designTime); + RelationalModel.Add( + model, + RelationalDependencies.RelationalAnnotationProvider, + (IRelationalTypeMappingSource)Dependencies.ModelDependencies.TypeMappingSource, + designTime); } } } diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index b1da78717b9..12b6426a166 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -348,11 +348,16 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy foreach (var resultColumn in sproc.ResultColumns) { - if (!properties.TryGetValue(resultColumn, out var property)) + if (resultColumn.PropertyName == null) + { + continue; + } + + if (!properties.TryGetValue(resultColumn.PropertyName, out var property)) { throw new InvalidOperationException( RelationalStrings.StoredProcedureResultColumnNotFound( - resultColumn, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); + resultColumn.PropertyName, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); } switch (storeObjectIdentifier.StoreObjectType) @@ -363,14 +368,14 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy { throw new InvalidOperationException( RelationalStrings.StoredProcedureResultColumnNotGenerated( - entityType.DisplayName(), resultColumn, storeObjectIdentifier.DisplayName())); + entityType.DisplayName(), resultColumn.PropertyName, storeObjectIdentifier.DisplayName())); } break; case StoreObjectType.DeleteStoredProcedure: throw new InvalidOperationException( RelationalStrings.StoredProcedureResultColumnDelete( - entityType.DisplayName(), resultColumn, storeObjectIdentifier.DisplayName())); + entityType.DisplayName(), resultColumn.PropertyName, storeObjectIdentifier.DisplayName())); default: Check.DebugFail("Unexpected stored procedure type: " + storeObjectIdentifier.StoreObjectType); break; @@ -379,23 +384,28 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy foreach (var parameter in sproc.Parameters) { - if (!properties.TryGetAndRemove(parameter, out IProperty property)) + if (parameter.PropertyName == null) + { + continue; + } + + if (!properties.TryGetAndRemove(parameter.PropertyName, out IProperty property)) { throw new InvalidOperationException( RelationalStrings.StoredProcedureParameterNotFound( - parameter, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); + parameter.PropertyName, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); } switch (storeObjectIdentifier.StoreObjectType) { case StoreObjectType.InsertStoredProcedure: case StoreObjectType.UpdateStoredProcedure: - if (property.GetDirection(storeObjectIdentifier) != ParameterDirection.Input + if (parameter.Direction != ParameterDirection.Input && !storeGeneratedProperties.Remove(property.Name)) { throw new InvalidOperationException( RelationalStrings.StoredProcedureOutputParameterNotGenerated( - entityType.DisplayName(), parameter, storeObjectIdentifier.DisplayName())); + entityType.DisplayName(), parameter.PropertyName, storeObjectIdentifier.DisplayName())); } break; @@ -405,7 +415,7 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy { throw new InvalidOperationException( RelationalStrings.StoredProcedureDeleteNonKeyProperty( - entityType.DisplayName(), parameter, storeObjectIdentifier.DisplayName())); + entityType.DisplayName(), parameter.PropertyName, storeObjectIdentifier.DisplayName())); } break; @@ -426,7 +436,12 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy foreach (var resultColumn in sproc.ResultColumns) { - properties.Remove(resultColumn); + if (resultColumn.PropertyName == null) + { + continue; + } + + properties.Remove(resultColumn.PropertyName); } if (properties.Count > 0) diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs index e120adc1c51..f335504e7db 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs @@ -75,7 +75,7 @@ public interface IConventionStoredProcedureBuilder : IConventionAnnotatableBuild /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionStoredProcedureBuilder? HasParameter(string propertyName, bool fromDataAnnotation = false); + IConventionStoredProcedureParameterBuilder? HasParameter(string propertyName, bool fromDataAnnotation = false); /// /// Returns a value indicating whether a parameter mapped to the given property can be used for stored procedure. @@ -85,16 +85,51 @@ public interface IConventionStoredProcedureBuilder : IConventionAnnotatableBuild /// if the parameter can be used for the stored procedure. bool CanHaveParameter(string propertyName, bool fromDataAnnotation = false); + /// + /// Configures a new parameter that holds the original value of the property with the given name + /// if no parameter mapped to the given property exists. + /// + /// The property name. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The builder instance if the configuration was applied, otherwise. + /// + IConventionStoredProcedureParameterBuilder? HasOriginalValueParameter(string propertyName, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether a parameter holds the original value of the mapped 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 CanHaveOriginalValueParameter(string propertyName, bool fromDataAnnotation = false); + + /// + /// Configures a new parameter that returns the rows affected if no such parameter exists. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The builder instance if the configuration was applied, otherwise. + /// + IConventionStoredProcedureParameterBuilder? HasRowsAffectedParameter(bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether a parameter that returns the rows affected can be used for stored procedure. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the parameter can be used for the stored procedure. + bool CanHaveRowsAffectedParameter(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. + /// The builder instance if the configuration was applied, otherwise. /// - IConventionStoredProcedureBuilder? HasResultColumn(string propertyName, bool fromDataAnnotation = false); + IConventionStoredProcedureResultColumnBuilder? 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. @@ -104,6 +139,24 @@ public interface IConventionStoredProcedureBuilder : IConventionAnnotatableBuild /// if the column of the result can be used for the stored procedure. bool CanHaveResultColumn(string propertyName, bool fromDataAnnotation = false); + /// + /// Configures a new column that contains the rows affected for this stored procedure if no such column exists. + /// + /// The property name. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The builder instance if the configuration was applied, otherwise. + /// + IConventionStoredProcedureResultColumnBuilder? HasRowsAffectedResultColumn(string propertyName, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether a column that contains the rows affected 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 CanHaveRowsAffectedResultColumn(string propertyName, bool fromDataAnnotation = false); + /// /// Prevents automatically creating a transaction when executing this stored procedure. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs new file mode 100644 index 00000000000..978d7c778a6 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a . +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionStoredProcedureParameterBuilder : IConventionAnnotatableBuilder +{ + /// + /// The stored procedure parameter metadata that is being built. + /// + new IConventionStoredProcedureParameter Metadata { get; } + + /// + /// Configures the parameter name. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The name of the parameter. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionStoredProcedureParameterBuilder? HasName(string name, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given parameter name can be set. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The name of the parameter. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given parameter name can be set. + bool CanSetName(string? name, bool fromDataAnnotation = false); + + /// + /// Sets the direction of the stored procedure parameter. + /// + /// The direction. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionStoredProcedureParameterBuilder? HasDirection(ParameterDirection direction, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given direction can be configured on the corresponding stored procedure parameter. + /// + /// The direction. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given direction can be configured. + bool CanSetDirection(ParameterDirection direction, bool fromDataAnnotation = false); +} diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs new file mode 100644 index 00000000000..3fb0d22c6e2 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs @@ -0,0 +1,43 @@ +// 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 IConventionStoredProcedureResultColumnBuilder : IConventionAnnotatableBuilder +{ + /// + /// The stored procedure result column metadata that is being built. + /// + new IConventionStoredProcedureResultColumn Metadata { get; } + + /// + /// Configures the result column name. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The name of the result column. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionStoredProcedureResultColumnBuilder? HasName(string name, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given result column name can be set. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The name of the result column. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given result column name can be set. + bool CanSetName(string? name, bool fromDataAnnotation = false); +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs index 52f8d56ef16..f049e38ec37 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs @@ -67,10 +67,59 @@ public virtual OwnedNavigationStoredProcedureBuilder HasParameter(string propert /// 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) + public virtual OwnedNavigationStoredProcedureBuilder HasParameter( + string propertyName, Action buildAction) { - Builder.HasParameter(propertyName, ConfigurationSource.Explicit); - buildAction(new(Metadata.GetStoreIdentifier()!.Value, CreatePropertyBuilder(propertyName))); + var parameterBuilder = Builder.HasParameter(propertyName, ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, CreatePropertyBuilder(propertyName))); + return this; + } + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter(string propertyName) + { + Builder.HasOriginalValueParameter(propertyName, ConfigurationSource.Explicit); + return this; + } + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + string propertyName, Action buildAction) + { + var parameterBuilder = Builder.HasOriginalValueParameter(propertyName, ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, CreatePropertyBuilder(propertyName))); + return this; + } + + /// + /// Configures a new parameter that returns the rows affected if no such parameter exists. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter() + { + Builder.HasRowsAffectedParameter(ConfigurationSource.Explicit); + return this; + } + + /// + /// Configures a new parameter that returns the rows affected if no such parameter exists. + /// + /// An action that performs configuration of the parameter. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction) + { + var parameterBuilder = Builder.HasRowsAffectedParameter(ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, null)); return this; } @@ -127,8 +176,46 @@ public virtual OwnedNavigationStoredProcedureBuilder HasResultColumn(string prop public virtual OwnedNavigationStoredProcedureBuilder HasResultColumn( string propertyName, Action buildAction) { - Builder.HasResultColumn(propertyName, ConfigurationSource.Explicit); - buildAction(new(Metadata.GetStoreIdentifier()!.Value, CreatePropertyBuilder(propertyName))); + var resultColumnBuilder = Builder.HasResultColumn(propertyName, ConfigurationSource.Explicit)!; + buildAction(new(resultColumnBuilder, CreatePropertyBuilder(propertyName))); + return this; + } + + /// + /// Configures a new column of the result that returns the rows affected for this stored procedure + /// if no such column exists. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn() + { + Builder.HasRowsAffectedResultColumn(ConfigurationSource.Explicit); + return this; + } + + /// + /// Configures a new column of the result that returns the rows affected for this stored procedure + /// if no such column exists. + /// + /// An action that performs configuration of the column. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction) + { + var resultColumnBuilder = Builder.HasRowsAffectedResultColumn(ConfigurationSource.Explicit)!; + buildAction(new(resultColumnBuilder, null)); + return this; + } + + /// + /// Configures the result of this stored procedure to be the number of rows affected. + /// + /// + /// A value indicating whether this stored procedure returns the number of rows affected. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true) + { + Builder.HasRowsAffectedReturn(rowsAffectedReturned, ConfigurationSource.Explicit); return this; } diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs index 9ef38f2d803..f302fad845f 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs @@ -79,11 +79,79 @@ public virtual OwnedNavigationStoredProcedureBuilder> propertyExpression, Action buildAction) { - Builder.HasParameter(propertyExpression, ConfigurationSource.Explicit); - buildAction(new(Metadata.GetStoreIdentifier()!.Value, CreatePropertyBuilder(propertyExpression))); + var parameterBuilder = Builder.HasParameter(propertyExpression, ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, CreatePropertyBuilder(propertyExpression))); return this; } + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + string propertyName) + => (OwnedNavigationStoredProcedureBuilder)base.HasOriginalValueParameter(propertyName); + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + string propertyName, Action buildAction) + => (OwnedNavigationStoredProcedureBuilder)base.HasOriginalValueParameter(propertyName, buildAction); + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + Expression> propertyExpression) + { + Builder.HasOriginalValueParameter(propertyExpression, ConfigurationSource.Explicit); + return this; + } + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + { + var parameterBuilder = Builder.HasOriginalValueParameter(propertyExpression, ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, CreatePropertyBuilder(propertyExpression))); + return this; + } + + /// + /// Configures a new parameter that returns the rows affected if no such parameter exists. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter() + => (OwnedNavigationStoredProcedureBuilder)base.HasRowsAffectedParameter(); + + /// + /// Configures a new parameter that returns the rows affected if no such parameter exists. + /// + /// An action that performs configuration of the parameter. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction) + => (OwnedNavigationStoredProcedureBuilder)base.HasRowsAffectedParameter(buildAction); + /// /// Configures a new column of the result for this stored procedure. This is used for database generated columns. /// @@ -130,10 +198,38 @@ public virtual OwnedNavigationStoredProcedureBuilder> propertyExpression, Action buildAction) { - Builder.HasResultColumn(propertyExpression, ConfigurationSource.Explicit); - buildAction(new(Metadata.GetStoreIdentifier()!.Value, CreatePropertyBuilder(propertyExpression))); + var resultColumnBuilder = Builder.HasResultColumn(propertyExpression, ConfigurationSource.Explicit)!; + buildAction(new(resultColumnBuilder, CreatePropertyBuilder(propertyExpression))); return this; } + + /// + /// Configures a new column of the result that returns the rows affected for this stored procedure + /// if no such column exists. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn() + => (OwnedNavigationStoredProcedureBuilder)base.HasRowsAffectedResultColumn(); + + /// + /// Configures a new column of the result that returns the rows affected for this stored procedure + /// if no such column exists. + /// + /// An action that performs configuration of the column. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction) + => (OwnedNavigationStoredProcedureBuilder)base.HasRowsAffectedResultColumn(buildAction); + + /// + /// Configures the result of this stored procedure to be the number of rows affected. + /// + /// + /// A value indicating whether this stored procedure returns the number of rows affected. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true) + => (OwnedNavigationStoredProcedureBuilder)base.HasRowsAffectedReturn(rowsAffectedReturned); /// /// Prevents automatically creating a transaction when executing this stored procedure. diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs index 2862c434d98..b0d559139ba 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs @@ -67,60 +67,57 @@ public virtual StoredProcedureBuilder HasParameter(string propertyName) public virtual StoredProcedureBuilder HasParameter( string propertyName, Action buildAction) { - Builder.HasParameter(propertyName, ConfigurationSource.Explicit); - buildAction(new(Metadata.GetStoreIdentifier()!.Value, CreatePropertyBuilder(propertyName))); + var parameterBuilder = Builder.HasParameter(propertyName, ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, 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. + /// Configures a new parameter that holds the original value if no parameter mapped to the given property exists. /// - [EntityFrameworkInternal] - protected virtual PropertyBuilder CreatePropertyBuilder(string propertyName) + /// The property name. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual StoredProcedureBuilder HasOriginalValueParameter(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())); - } + Builder.HasOriginalValueParameter(propertyName, ConfigurationSource.Explicit); + return this; + } -#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); + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + string propertyName, Action buildAction) + { + var parameterBuilder = Builder.HasOriginalValueParameter(propertyName, ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, 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. + /// Configures a new parameter that returns the rows affected if no such parameter exists. /// - [EntityFrameworkInternal] - protected virtual PropertyBuilder CreatePropertyBuilder( - Expression> propertyExpression) - where TDerivedEntity : class + /// The same builder instance so that multiple configuration calls can be chained. + public virtual StoredProcedureBuilder HasRowsAffectedParameter() { - 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. + Builder.HasRowsAffectedParameter(ConfigurationSource.Explicit); + return this; + } - return entityTypeBuilder.Property(memberInfo.GetMemberType(), memberInfo.Name); + /// + /// Configures a new parameter that returns the rows affected if no such parameter exists. + /// + /// An action that performs configuration of the parameter. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual StoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction) + { + var parameterBuilder = Builder.HasRowsAffectedParameter(ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, null)); + return this; } /// @@ -143,8 +140,45 @@ public virtual StoredProcedureBuilder HasResultColumn(string propertyName) public virtual StoredProcedureBuilder HasResultColumn( string propertyName, Action buildAction) { - Builder.HasResultColumn(propertyName, ConfigurationSource.Explicit); - buildAction(new(Metadata.GetStoreIdentifier()!.Value, CreatePropertyBuilder(propertyName))); + var resultColumnBuilder = Builder.HasResultColumn(propertyName, ConfigurationSource.Explicit)!; + buildAction(new(resultColumnBuilder, CreatePropertyBuilder(propertyName))); + return this; + } + + /// + /// Configures a new column of the result that returns the rows affected for this stored procedure + /// if no such column exists. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual StoredProcedureBuilder HasRowsAffectedResultColumn() + { + Builder.HasRowsAffectedResultColumn(ConfigurationSource.Explicit); + return this; + } + + /// + /// Configures a new column of the result that returns the rows affected for this stored procedure + /// if no such column exists. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual StoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction) + { + var resultColumnBuilder = Builder.HasRowsAffectedResultColumn(ConfigurationSource.Explicit)!; + buildAction(new(resultColumnBuilder, null)); + return this; + } + + /// + /// Configures the result of this stored procedure to be the number of rows affected. + /// + /// + /// A value indicating whether this stored procedure returns the number of rows affected. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual StoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true) + { + Builder.HasRowsAffectedReturn(rowsAffectedReturned, ConfigurationSource.Explicit); return this; } @@ -175,5 +209,56 @@ public virtual StoredProcedureBuilder HasAnnotation(string annotation, object? v 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); + } + EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder; } diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs index f5a22972b9f..a478e9efa04 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs @@ -103,11 +103,108 @@ public virtual StoredProcedureBuilder HasParameter buildAction) where TDerivedEntity : class, TEntity { - Builder.HasParameter(propertyExpression, ConfigurationSource.Explicit); - buildAction(new(Metadata.GetStoreIdentifier()!.Value, CreatePropertyBuilder(propertyExpression))); + var parameterBuilder = Builder.HasParameter(propertyExpression, ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, CreatePropertyBuilder(propertyExpression))); return this; } + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter(string propertyName) + => (StoredProcedureBuilder)base.HasOriginalValueParameter(propertyName); + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + string propertyName, Action buildAction) + => (StoredProcedureBuilder)base.HasOriginalValueParameter(propertyName, buildAction); + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + Expression> propertyExpression) + => HasOriginalValueParameter(propertyExpression); + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + Expression> propertyExpression) + where TDerivedEntity : class, TEntity + { + Builder.HasOriginalValueParameter(propertyExpression, ConfigurationSource.Explicit); + return this; + } + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + => HasOriginalValueParameter(propertyExpression, buildAction); + + /// + /// Configures a new parameter that holds the original value 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 HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + where TDerivedEntity : class, TEntity + { + var parameterBuilder = Builder.HasOriginalValueParameter(propertyExpression, ConfigurationSource.Explicit)!; + buildAction(new(parameterBuilder, CreatePropertyBuilder(propertyExpression))); + return this; + } + + /// + /// Configures a new parameter that returns the rows affected if no such parameter exists. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual StoredProcedureBuilder HasRowsAffectedParameter() + => (StoredProcedureBuilder)base.HasRowsAffectedParameter(); + + /// + /// Configures a new parameter that returns the rows affected if no such parameter exists. + /// + /// An action that performs configuration of the parameter. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual StoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction) + => (StoredProcedureBuilder)base.HasRowsAffectedParameter(buildAction); + /// /// Configures a new column of the result for this stored procedure. This is used for database generated columns. /// @@ -184,10 +281,38 @@ public virtual StoredProcedureBuilder HasResultColumn buildAction) where TDerivedEntity : class, TEntity { - Builder.HasResultColumn(propertyExpression, ConfigurationSource.Explicit); - buildAction(new(Metadata.GetStoreIdentifier()!.Value, CreatePropertyBuilder(propertyExpression))); + var resultColumnBuilder = Builder.HasResultColumn(propertyExpression, ConfigurationSource.Explicit)!; + buildAction(new(resultColumnBuilder, CreatePropertyBuilder(propertyExpression))); return this; } + + /// + /// Configures a new column of the result that returns the rows affected for this stored procedure + /// if no such column exists. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual StoredProcedureBuilder HasRowsAffectedResultColumn() + => (StoredProcedureBuilder)base.HasRowsAffectedResultColumn(); + + /// + /// Configures a new column of the result that returns the rows affected for this stored procedure + /// if no such column exists. + /// + /// An action that performs configuration of the column. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual StoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction) + => (StoredProcedureBuilder)base.HasRowsAffectedResultColumn(buildAction); + + /// + /// Configures the result of this stored procedure to be the number of rows affected. + /// + /// + /// A value indicating whether this stored procedure returns the number of rows affected. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual StoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true) + => (StoredProcedureBuilder)base.HasRowsAffectedReturn(rowsAffectedReturned); /// /// Prevents automatically creating a transaction when executing this stored procedure. diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs index 3103bc18c6f..3fe7f3baff5 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// 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 +public class StoredProcedureParameterBuilder : IInfrastructure { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -23,23 +23,19 @@ public class StoredProcedureParameterBuilder : IInfrastructure /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public StoredProcedureParameterBuilder(in StoreObjectIdentifier storeObject, PropertyBuilder propertyBuilder) + public StoredProcedureParameterBuilder( + IInternalStoredProcedureParameterBuilder builder, 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); + Builder = builder; PropertyBuilder = propertyBuilder; } /// - /// The stored procedure-specific overrides being configured. + /// The stored procedure parameter being configured. /// - public virtual IMutableRelationalPropertyOverrides Overrides => InternalOverrides; - + public virtual IMutableStoredProcedureParameter Metadata + => Builder.Metadata; + /// /// 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 @@ -47,9 +43,9 @@ public StoredProcedureParameterBuilder(in StoreObjectIdentifier storeObject, Pro /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual RelationalPropertyOverrides InternalOverrides { get; } + protected virtual IInternalStoredProcedureParameterBuilder Builder { get; } - private PropertyBuilder PropertyBuilder { get; } + private PropertyBuilder? PropertyBuilder { get; } /// /// Sets the name of the stored procedure parameter. @@ -60,11 +56,11 @@ public StoredProcedureParameterBuilder(in StoreObjectIdentifier storeObject, Pro /// /// 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) + public virtual StoredProcedureParameterBuilder HasName(string name) { - Check.NullButNotEmpty(name, nameof(name)); + Check.NotNull(name, nameof(name)); - InternalOverrides.SetColumnName(name, ConfigurationSource.Explicit); + Builder.HasName(name, ConfigurationSource.Explicit); return this; } @@ -79,7 +75,7 @@ public virtual StoredProcedureParameterBuilder HasName(string? name) /// The same builder instance so that further configuration calls can be chained. public virtual StoredProcedureParameterBuilder IsInputOutput() { - ((IMutableRelationalPropertyOverrides)InternalOverrides).Direction = ParameterDirection.InputOutput; + Builder.HasDirection(ParameterDirection.InputOutput, ConfigurationSource.Explicit); return this; } @@ -94,7 +90,7 @@ public virtual StoredProcedureParameterBuilder IsInputOutput() /// The same builder instance so that further configuration calls can be chained. public virtual StoredProcedureParameterBuilder IsOutput() { - ((IMutableRelationalPropertyOverrides)InternalOverrides).Direction = ParameterDirection.Output; + Builder.HasDirection(ParameterDirection.Output, ConfigurationSource.Explicit); return this; } @@ -111,12 +107,12 @@ public virtual StoredProcedureParameterBuilder HasAnnotation(string annotation, { Check.NotEmpty(annotation, nameof(annotation)); - InternalOverrides.Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); return this; } - PropertyBuilder IInfrastructure.Instance => PropertyBuilder; + PropertyBuilder? IInfrastructure.Instance => PropertyBuilder; #region Hidden System.Object members diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs index b83fc3da30e..406a10decbc 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// 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 +public class StoredProcedureResultColumnBuilder : IInfrastructure { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -22,22 +22,18 @@ public class StoredProcedureResultColumnBuilder : IInfrastructure [EntityFrameworkInternal] - public StoredProcedureResultColumnBuilder(in StoreObjectIdentifier storeObject, PropertyBuilder propertyBuilder) + public StoredProcedureResultColumnBuilder( + IInternalStoredProcedureResultColumnBuilder builder, 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); + Builder = builder; PropertyBuilder = propertyBuilder; } /// - /// The stored procedure-specific overrides being configured. + /// The stored procedure result column being configured. /// - public virtual IMutableRelationalPropertyOverrides Overrides => InternalOverrides; + public virtual IMutableStoredProcedureResultColumn Metadata + => Builder.Metadata; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -46,9 +42,9 @@ public StoredProcedureResultColumnBuilder(in StoreObjectIdentifier storeObject, /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual RelationalPropertyOverrides InternalOverrides { get; } + protected virtual IInternalStoredProcedureResultColumnBuilder Builder { get; } - private PropertyBuilder PropertyBuilder { get; } + private PropertyBuilder? PropertyBuilder { get; } /// /// Sets the name of the stored procedure result column. @@ -59,11 +55,11 @@ public StoredProcedureResultColumnBuilder(in StoreObjectIdentifier storeObject, /// /// 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) + public virtual StoredProcedureResultColumnBuilder HasName(string name) { Check.NullButNotEmpty(name, nameof(name)); - InternalOverrides.SetColumnName(name, ConfigurationSource.Explicit); + Builder.HasName(name, ConfigurationSource.Explicit); return this; } @@ -80,12 +76,12 @@ public virtual StoredProcedureResultColumnBuilder HasAnnotation(string annotatio { Check.NotEmpty(annotation, nameof(annotation)); - InternalOverrides.Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); return this; } - PropertyBuilder IInfrastructure.Instance => PropertyBuilder; + PropertyBuilder? IInfrastructure.Instance => PropertyBuilder; #region Hidden System.Object members diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 3e14e8158f8..a628a28c48a 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -52,7 +52,11 @@ protected override void ProcessModelAnnotations( if (runtime) { annotations[RelationalAnnotationNames.RelationalModel] = - RelationalModel.Create(runtimeModel, RelationalDependencies.RelationalAnnotationProvider, designTime: false); + RelationalModel.Create( + runtimeModel, + RelationalDependencies.RelationalAnnotationProvider, + (IRelationalTypeMappingSource)Dependencies.TypeMappingSource, + designTime: false); } else { @@ -488,12 +492,50 @@ protected virtual void ProcessTriggerAnnotations( { } - private static RuntimeStoredProcedure Create(IStoredProcedure storedProcedure, RuntimeEntityType runtimeEntityType) - => new(runtimeEntityType, + private RuntimeStoredProcedure Create(IStoredProcedure storedProcedure, RuntimeEntityType runtimeEntityType) + { + var runtimeStoredProcedure = new RuntimeStoredProcedure( + runtimeEntityType, storedProcedure.Name, storedProcedure.Schema, + storedProcedure.AreRowsAffectedReturned, storedProcedure.AreTransactionsSuppressed); + foreach (var parameter in storedProcedure.Parameters) + { + var runtimeParameter = Create(parameter, runtimeStoredProcedure); + CreateAnnotations( + parameter, runtimeParameter, static (convention, annotations, source, target, runtime) => + convention.ProcessStoredProcedureParameterAnnotations(annotations, source, target, runtime)); + } + + foreach (var resultColumn in storedProcedure.ResultColumns) + { + var runtimeResultColumn = Create(resultColumn, runtimeStoredProcedure); + CreateAnnotations( + resultColumn, runtimeResultColumn, static (convention, annotations, source, target, runtime) => + convention.ProcessStoredProcedureResultColumnAnnotations(annotations, source, target, runtime)); + } + + return runtimeStoredProcedure; + } + + private RuntimeStoredProcedureParameter Create( + IStoredProcedureParameter parameter, RuntimeStoredProcedure runtimeStoredProcedure) + => runtimeStoredProcedure.AddParameter( + parameter.Name, + parameter.Direction, + parameter.ForRowsAffected, + parameter.PropertyName, + parameter.ForOriginalValue); + + private RuntimeStoredProcedureResultColumn Create( + IStoredProcedureResultColumn resultColumn, RuntimeStoredProcedure runtimeStoredProcedure) + => runtimeStoredProcedure.AddResultColumn( + resultColumn.Name, + resultColumn.ForRowsAffected, + resultColumn.PropertyName); + /// /// Updates the stored procedure annotations that will be set on the read-only object. /// @@ -508,4 +550,34 @@ protected virtual void ProcessStoredProcedureAnnotations( bool runtime) { } + + /// + /// Updates the stored procedure parameter annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source stored procedure parameter. + /// The target stored procedure parameter that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected virtual void ProcessStoredProcedureParameterAnnotations( + Dictionary annotations, + IStoredProcedureParameter parameter, + RuntimeStoredProcedureParameter runtimeParameter, + bool runtime) + { + } + + /// + /// Updates the stored procedure result column annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source fstored procedure result column. + /// The target stored procedure result column that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected virtual void ProcessStoredProcedureResultColumnAnnotations( + Dictionary annotations, + IStoredProcedureResultColumn resultColumn, + RuntimeStoredProcedureResultColumn runtimeResultColumn, + bool runtime) + { + } } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs index d22c6816cd5..e073c26a982 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs @@ -76,39 +76,41 @@ public virtual void ProcessEntityTypeAnnotationChanged( IConventionAnnotation? oldAnnotation, IConventionContext context) { + var entityType = entityTypeBuilder.Metadata; if (name == RelationalAnnotationNames.ViewName || name == RelationalAnnotationNames.FunctionName - || name == RelationalAnnotationNames.SqlQuery) + || name == RelationalAnnotationNames.SqlQuery + || name == RelationalAnnotationNames.InsertStoredProcedure) { if (annotation?.Value != null && oldAnnotation?.Value == null - && entityTypeBuilder.Metadata.GetTableName() == null) + && entityType.GetTableName() == null) { ProcessTableChanged( entityTypeBuilder, - entityTypeBuilder.Metadata.GetDefaultTableName(), - entityTypeBuilder.Metadata.GetDefaultSchema(), + entityType.GetDefaultTableName(), + entityType.GetDefaultSchema(), null, null); } } else if (name == RelationalAnnotationNames.TableName) { - var schema = entityTypeBuilder.Metadata.GetSchema(); + var schema = entityType.GetSchema(); ProcessTableChanged( entityTypeBuilder, - (string?)oldAnnotation?.Value ?? entityTypeBuilder.Metadata.GetDefaultTableName(), + (string?)oldAnnotation?.Value ?? entityType.GetDefaultTableName(), schema, - entityTypeBuilder.Metadata.GetTableName(), + entityType.GetTableName(), schema); } else if (name == RelationalAnnotationNames.Schema) { - var tableName = entityTypeBuilder.Metadata.GetTableName(); + var tableName = entityType.GetTableName(); ProcessTableChanged( entityTypeBuilder, tableName, - (string?)oldAnnotation?.Value ?? entityTypeBuilder.Metadata.GetDefaultSchema(), + (string?)oldAnnotation?.Value ?? entityType.GetDefaultSchema(), tableName, entityTypeBuilder.Metadata.GetSchema()); } @@ -127,31 +129,18 @@ public virtual void ProcessEntityTypeAnnotationChanged( } } - private static void ProcessTableChanged( + private void ProcessTableChanged( IConventionEntityTypeBuilder entityTypeBuilder, string? oldTable, string? oldSchema, string? newTable, string? newSchema) { - if (newTable == null) + if (newTable == null || oldTable == null) { - if (entityTypeBuilder.Metadata.GetDerivedTypes().All(e => e.GetTableName() == null)) + foreach (var property in entityTypeBuilder.Metadata.GetDeclaredProperties()) { - foreach (var property in entityTypeBuilder.Metadata.GetProperties()) - { - property.Builder.ValueGenerated(null); - } - } - - return; - } - - if (oldTable == null) - { - foreach (var property in entityTypeBuilder.Metadata.GetProperties()) - { - property.Builder.ValueGenerated(GetValueGenerated(property, StoreObjectIdentifier.Table(newTable, newSchema))); + property.Builder.ValueGenerated(GetValueGenerated(property)); } return; @@ -174,7 +163,7 @@ private static void ProcessTableChanged( foreach (var property in primaryKey.Properties) { - property.Builder.ValueGenerated(GetValueGenerated(property, StoreObjectIdentifier.Table(newTable, newSchema))); + property.Builder.ValueGenerated(GetValueGenerated(property)); } } @@ -185,37 +174,14 @@ private static void ProcessTableChanged( /// The store value generation strategy to set for the given property. protected override ValueGenerated? GetValueGenerated(IConventionProperty property) { - var tableName = property.DeclaringEntityType.GetTableName(); - - return tableName == null + var table = property.GetMappedStoreObjects(StoreObjectType.Table).FirstOrDefault(); + return !MappingStrategyAllowsValueGeneration(property, property.DeclaringEntityType.GetMappingStrategy()) ? null - : !MappingStrategyAllowsValueGeneration(property, property.DeclaringEntityType.GetMappingStrategy()) - ? null - : GetValueGenerated(property, StoreObjectIdentifier.Table(tableName, property.DeclaringEntityType.GetSchema())); - } - - /// - /// Checks whether or not the mapping strategy and property allow value generation by convention. - /// - /// The property for which value generation is being considered. - /// The current mapping strategy. - /// if value generation is allowed; otherwise. - protected virtual bool MappingStrategyAllowsValueGeneration( - IConventionProperty property, - string? mappingStrategy) - { - if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy) - { - var propertyType = property.ClrType.UnwrapNullableType(); - if (property.IsPrimaryKey() - && propertyType.IsInteger() - && propertyType != typeof(byte)) - { - return false; - } - } - - return true; + : table.Name != null + ? GetValueGenerated(property, table) + : property.GetMappedStoreObjects(StoreObjectType.InsertStoredProcedure).Any() + ? GetValueGenerated((IReadOnlyProperty)property) + : null; } /// diff --git a/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs index 2e3ad31db5e..de0fdb3a07f 100644 --- a/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs @@ -4,15 +4,15 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a parameter. +/// Represents a function parameter. /// /// /// See Database functions for more information and examples. /// -public interface IConventionDbFunctionParameter : IConventionAnnotatable, IReadOnlyDbFunctionParameter +public interface IConventionDbFunctionParameter : IReadOnlyDbFunctionParameter, IConventionAnnotatable { /// - /// The to which this parameter belongs. + /// The function to which this parameter belongs. /// new IConventionDbFunction Function { get; } diff --git a/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs index 687b4e490b7..6bd4ebd0d16 100644 --- a/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs @@ -50,20 +50,4 @@ public interface IConventionRelationalPropertyOverrides : IReadOnlyRelationalPro /// /// The configuration source for . ConfigurationSource? GetColumnNameConfigurationSource(); - - /// - /// Sets the direction of the stored procedure parameter. - /// - /// The direction. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - ParameterDirection? SetDirection(ParameterDirection? direction, bool fromDataAnnotation = false) - => ((ParameterDirection?)SetAnnotation(RelationalAnnotationNames.ParameterDirection, direction, fromDataAnnotation)?.Value); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetDirectionConfigurationSource() - => FindAnnotation(RelationalAnnotationNames.ParameterDirection)?.GetConfigurationSource(); } diff --git a/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs b/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs index 1e83d739f97..d3840b83ed8 100644 --- a/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs @@ -1,8 +1,6 @@ // 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; /// @@ -19,19 +17,19 @@ public interface IConventionStoredProcedure : IReadOnlyStoredProcedure, IConvent /// /// Gets the builder that can be used to configure this stored procedure. /// - /// If the function has been removed from the model. + /// If the stored procedure has been removed from the model. new IConventionStoredProcedureBuilder Builder { get; } /// /// Gets the configuration source for this stored procedure. /// - /// The configuration source for this function. + /// The configuration source for this stored procedure. ConfigurationSource GetConfigurationSource(); /// /// Sets the name of the stored procedure in the database. /// - /// The name of the function in the database. + /// The name of the stored procedure in the database. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. string? SetName(string? name, bool fromDataAnnotation = false); @@ -45,7 +43,7 @@ public interface IConventionStoredProcedure : IReadOnlyStoredProcedure, IConvent /// /// Sets the schema of the stored procedure in the database. /// - /// The schema of the function in the database. + /// The schema of the stored procedure in the database. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. string? SetSchema(string? schema, bool fromDataAnnotation = false); @@ -56,21 +54,98 @@ public interface IConventionStoredProcedure : IReadOnlyStoredProcedure, IConvent /// The configuration source for . ConfigurationSource? GetSchemaConfigurationSource(); + /// + /// Gets the parameters for this stored procedure. + /// + new IEnumerable Parameters { get; } + + /// + /// Returns the parameter corresponding to the given property. + /// + /// The name of a property. + /// The parameter corresponding to the given property if found; otherwise. + new IConventionStoredProcedureParameter? FindParameter(string propertyName); + /// /// 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); + /// The added parameter. + IConventionStoredProcedureParameter? AddParameter(string propertyName, bool fromDataAnnotation = false); + + /// + /// Returns the original value parameter corresponding to the given property. + /// + /// The name of a property. + /// + /// The original value parameter corresponding to the given property if found; otherwise. + /// + new IConventionStoredProcedureParameter? FindOriginalValueParameter(string propertyName); + + /// + /// Adds a new parameter that will hold the original value of the property with the given name. + /// + /// The name of the corresponding property. + /// Indicates whether the configuration was specified using a data annotation. + /// The added parameter. + IConventionStoredProcedureParameter? AddOriginalValueParameter(string propertyName, bool fromDataAnnotation = false); + + /// + /// Returns the rows affected parameter. + /// + /// + /// The rows affected parameter if found; otherwise. + /// + new IConventionStoredProcedureParameter? FindRowsAffectedParameter(); + + /// + /// Adds an output parameter that returns the rows affected by this stored procedure. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The added parameter. + IConventionStoredProcedureParameter? AddRowsAffectedParameter(bool fromDataAnnotation = false); + + /// + /// Gets the columns of the result for this stored procedure. + /// + new IEnumerable ResultColumns { get; } + + /// + /// Returns the result column corresponding to the given property. + /// + /// The name of a property. + /// The result column corresponding to the given property if found; otherwise. + new IConventionStoredProcedureResultColumn? FindResultColumn(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. /// Indicates whether the configuration was specified using a data annotation. + /// The added column. + IConventionStoredProcedureResultColumn? AddResultColumn(string propertyName, bool fromDataAnnotation = false); + + /// + /// Returns the rows affected result column. + /// + /// The rows affected result column if found; otherwise. + new IConventionStoredProcedureResultColumn? FindRowsAffectedResultColumn(); + + /// + /// Adds a new column of the result that contains the rows affected by this stored procedure. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The added column. + IConventionStoredProcedureResultColumn? AddRowsAffectedResultColumn(bool fromDataAnnotation = false); + + /// + /// Configures whether this stored procedure returns the number of rows affected. + /// + /// A value indicating whether the number of rows affected is returned. + /// Indicates whether the configuration was specified using a data annotation. /// The configured value. - string? AddResultColumn(string propertyName, bool fromDataAnnotation = false); + bool SetAreRowsAffectedReturned(bool rowsAffectedReturned, bool fromDataAnnotation = false); /// /// Prevents automatically creating a transaction when executing this stored procedure. diff --git a/src/EFCore.Relational/Metadata/IConventionStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/IConventionStoredProcedureParameter.cs new file mode 100644 index 00000000000..b33d2619f3f --- /dev/null +++ b/src/EFCore.Relational/Metadata/IConventionStoredProcedureParameter.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a stored procedure parameter. +/// +public interface IConventionStoredProcedureParameter : IReadOnlyStoredProcedureParameter, IConventionAnnotatable +{ + /// + /// Gets the stored procedure to which this parameter belongs. + /// + new IConventionStoredProcedure StoredProcedure { get; } + + /// + /// Gets the builder that can be used to configure this stored procedure parameter. + /// + /// If the stored procedure parameter has been removed from the model. + new IConventionStoredProcedureParameterBuilder Builder { get; } + + /// + /// Sets the parameter name. + /// + /// The parameter name. + /// Indicates whether the configuration was specified using a data annotation. + string SetName(string name, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetNameConfigurationSource(); + + /// + /// Sets the direction of the parameter. + /// + /// The direction of the parameter. + /// Indicates whether the configuration was specified using a data annotation. + ParameterDirection SetDirection(ParameterDirection direction, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetDirectionConfigurationSource(); +} diff --git a/src/EFCore.Relational/Metadata/IConventionStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/IConventionStoredProcedureResultColumn.cs new file mode 100644 index 00000000000..1f7a51ad6bb --- /dev/null +++ b/src/EFCore.Relational/Metadata/IConventionStoredProcedureResultColumn.cs @@ -0,0 +1,34 @@ +// 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 stored procedure result column. +/// +public interface IConventionStoredProcedureResultColumn : IReadOnlyStoredProcedureResultColumn, IConventionAnnotatable +{ + /// + /// Gets the stored procedure to which this result column belongs. + /// + new IConventionStoredProcedure StoredProcedure { get; } + + /// + /// Gets the builder that can be used to configure this result column. + /// + /// If the stored procedure result column has been removed from the model. + new IConventionStoredProcedureResultColumnBuilder Builder { get; } + + /// + /// Sets the result column name. + /// + /// The result column name. + /// Indicates whether the configuration was specified using a data annotation. + string? SetName(string name, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetNameConfigurationSource(); +} diff --git a/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs index 75d96e6f473..8fa52d850f7 100644 --- a/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs @@ -4,7 +4,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a parameter. +/// Represents a function parameter. /// /// /// See Database functions for more information and examples. @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; public interface IMutableDbFunctionParameter : IReadOnlyDbFunctionParameter, IMutableAnnotatable { /// - /// Gets the to which this parameter belongs. + /// Gets the function to which this parameter belongs. /// new IMutableDbFunction Function { get; } diff --git a/src/EFCore.Relational/Metadata/IMutableRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IMutableRelationalPropertyOverrides.cs index badb6b3eb2f..24055085c2d 100644 --- a/src/EFCore.Relational/Metadata/IMutableRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/IMutableRelationalPropertyOverrides.cs @@ -23,15 +23,6 @@ public interface IMutableRelationalPropertyOverrides : IReadOnlyRelationalProper /// new string? ColumnName { get; set; } - /// - /// Gets or sets the direction of the stored procedure parameter. - /// - new ParameterDirection? Direction - { - get => ((ParameterDirection?)this[RelationalAnnotationNames.ParameterDirection]); - set => SetAnnotation(RelationalAnnotationNames.ParameterDirection, value); - } - /// /// Removes the column name override. /// diff --git a/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs b/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs index 25643f7622c..0780a082f2b 100644 --- a/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs @@ -32,17 +32,88 @@ public interface IMutableStoredProcedure : IReadOnlyStoredProcedure, IMutableAnn /// The configured value. new bool AreTransactionsSuppressed { get; set; } + /// + /// Gets or sets a value indicating whether this stored procedure returns the number of rows affected. + /// + new bool AreRowsAffectedReturned { get; set; } + + /// + /// Gets the parameters for this stored procedure. + /// + new IEnumerable Parameters { get; } + + /// + /// Returns the parameter corresponding to the given property. + /// + /// The name of a property. + /// The parameter corresponding to the given property if found; otherwise. + new IMutableStoredProcedureParameter? FindParameter(string propertyName); + /// /// 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); + /// The added parameter. + IMutableStoredProcedureParameter AddParameter(string propertyName); + + /// + /// Returns the original value parameter corresponding to the given property. + /// + /// The name of a property. + /// + /// The original value parameter corresponding to the given property if found; otherwise. + /// + new IMutableStoredProcedureParameter? FindOriginalValueParameter(string propertyName); + + /// + /// Adds a new parameter that holds the original value of the property with the given name. + /// + /// The name of the corresponding property. + /// The added parameter. + IMutableStoredProcedureParameter AddOriginalValueParameter(string propertyName); + + /// + /// Returns the rows affected parameter. + /// + /// + /// The rows affected parameter if found; otherwise. + /// + new IMutableStoredProcedureParameter? FindRowsAffectedParameter(); + + /// + /// Adds an output parameter that returns the rows affected by this stored procedure. + /// + /// The added parameter. + IMutableStoredProcedureParameter AddRowsAffectedParameter(); + + /// + /// Gets the columns of the result for this stored procedure. + /// + new IEnumerable ResultColumns { get; } + + /// + /// Returns the result column corresponding to the given property. + /// + /// The name of a property. + /// The result column corresponding to the given property if found; otherwise. + new IMutableStoredProcedureResultColumn? FindResultColumn(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); + /// The added column. + IMutableStoredProcedureResultColumn AddResultColumn(string propertyName); + + /// + /// Returns the rows affected result column. + /// + /// The rows affected result column if found; otherwise. + new IMutableStoredProcedureResultColumn? FindRowsAffectedResultColumn(); + + /// + /// Adds a new column of the result that contains the rows affected by this stored procedure. + /// + /// The added column. + IMutableStoredProcedureResultColumn AddRowsAffectedResultColumn(); } diff --git a/src/EFCore.Relational/Metadata/IMutableStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/IMutableStoredProcedureParameter.cs new file mode 100644 index 00000000000..c22a4aee8c2 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IMutableStoredProcedureParameter.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a stored procedure parameter. +/// +public interface IMutableStoredProcedureParameter : IReadOnlyStoredProcedureParameter, IMutableAnnotatable +{ + /// + /// Gets the stored procedure to which this parameter belongs. + /// + new IMutableStoredProcedure StoredProcedure { get; } + + /// + /// Gets or sets the parameter name. + /// + new string Name { get; set; } + + /// + /// Gets or sets the direction of the parameter. + /// + new static ParameterDirection Direction { get; set; } +} diff --git a/src/EFCore.Relational/Metadata/IMutableStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/IMutableStoredProcedureResultColumn.cs new file mode 100644 index 00000000000..038b42a086c --- /dev/null +++ b/src/EFCore.Relational/Metadata/IMutableStoredProcedureResultColumn.cs @@ -0,0 +1,20 @@ +// 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 stored procedure result column. +/// +public interface IMutableStoredProcedureResultColumn : IReadOnlyStoredProcedureResultColumn, IMutableAnnotatable +{ + /// + /// Gets the stored procedure to which this result column belongs. + /// + new IMutableStoredProcedure StoredProcedure { get; } + + /// + /// Gets or sets the result column name. + /// + new string Name { get; set; } +} diff --git a/src/EFCore.Relational/Metadata/IReadOnlyDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IReadOnlyDbFunctionParameter.cs index 855e388f035..dff4400a1f6 100644 --- a/src/EFCore.Relational/Metadata/IReadOnlyDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/IReadOnlyDbFunctionParameter.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; public interface IReadOnlyDbFunctionParameter : IReadOnlyAnnotatable { /// - /// Gets the to which this parameter belongs. + /// Gets the function to which this parameter belongs. /// IReadOnlyDbFunction Function { get; } diff --git a/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs index 3d5cf0bb282..9a82eec8229 100644 --- a/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs @@ -34,12 +34,6 @@ public interface IReadOnlyRelationalPropertyOverrides : IReadOnlyAnnotatable /// bool IsColumnNameOverridden { get; } - /// - /// Gets the direction of the stored procedure parameter. - /// - ParameterDirection? Direction - => ((ParameterDirection?)this[RelationalAnnotationNames.ParameterDirection]); - /// /// /// Creates a human-readable representation of the given metadata. diff --git a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs index ba911ba0411..3304300cf83 100644 --- a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs @@ -28,8 +28,12 @@ public interface IReadOnlyStoredProcedure : IReadOnlyAnnotatable /// /// Returns a value indicating whether automatic creation of transactions is disabled when executing this stored procedure. /// - /// The configured value. bool AreTransactionsSuppressed { get; } + + /// + /// Gets a value indicating whether this stored procedure returns the number of rows affected. + /// + bool AreRowsAffectedReturned { get; } /// /// Returns the store identifier of this stored procedure. @@ -62,28 +66,51 @@ public interface IReadOnlyStoredProcedure : IReadOnlyAnnotatable } /// - /// Gets the names of properties mapped to parameters for this stored procedure. + /// Gets the parameters for this stored procedure. + /// + IEnumerable Parameters { get; } + + /// + /// Returns the parameter corresponding to the given property. /// - IReadOnlyList Parameters { get; } + /// The name of a property. + /// The parameter corresponding to the given property if found; otherwise. + IReadOnlyStoredProcedureParameter? FindParameter(string propertyName); /// - /// Returns a value indicating whether there is a parameter corresponding to the given property. + /// Returns the original value 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); + /// + /// The original value parameter corresponding to the given property if found; otherwise. + /// + IReadOnlyStoredProcedureParameter? FindOriginalValueParameter(string propertyName); /// - /// Gets the names of properties mapped to columns of the result for this stored procedure. + /// Returns the rows affected parameter. /// - IReadOnlyList ResultColumns { get; } + /// + /// The rows affected parameter if found; otherwise. + /// + IReadOnlyStoredProcedureParameter? FindRowsAffectedParameter(); /// - /// Returns a value indicating whether there is a column of the result corresponding to the given property. + /// Gets the columns of the result for this stored procedure. + /// + IEnumerable ResultColumns { get; } + + /// + /// Returns the result column 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); + /// The result column corresponding to the given property if found; otherwise. + IReadOnlyStoredProcedureResultColumn? FindResultColumn(string propertyName); + + /// + /// Returns the rows affected result column. + /// + /// The rows affected result column if found; otherwise. + IReadOnlyStoredProcedureResultColumn? FindRowsAffectedResultColumn(); /// /// Returns the name of the stored procedure prepended by the schema diff --git a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedureParameter.cs new file mode 100644 index 00000000000..e6448b55ec9 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedureParameter.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; +using System.Text; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a stored procedure parameter. +/// +public interface IReadOnlyStoredProcedureParameter : IReadOnlyAnnotatable +{ + /// + /// Gets the stored procedure to which this parameter belongs. + /// + IReadOnlyStoredProcedure StoredProcedure { get; } + + /// + /// Gets the parameter name. + /// + string Name { get; } + + /// + /// Gets the name of property mapped to this parameter. + /// + string? PropertyName { get; } + + /// + /// Gets the direction of the parameter. + /// + ParameterDirection Direction { get; } + + /// + /// Gets a value indicating whether the parameter holds the original or the current property value. + /// + bool? ForOriginalValue { get; } + + /// + /// Gets a value indicating whether the parameter holds the rows affected by the stored procedure. + /// + bool ForRowsAffected { get; } + + /// + /// + /// 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("StoredProcedureParameter: "); + + builder.Append(Name); + + if (Direction != ParameterDirection.Input) + { + builder.Append(' ') + .Append(Direction); + } + + if (ForOriginalValue == true) + { + builder.Append(" ForOriginalValue"); + } + + if (ForRowsAffected) + { + builder.Append(" ForRowsAffected"); + } + + if ((options & MetadataDebugStringOptions.SingleLine) == 0) + { + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } + } + + return builder.ToString(); + } +} diff --git a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedureResultColumn.cs new file mode 100644 index 00000000000..e4b43ff4fc5 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedureResultColumn.cs @@ -0,0 +1,66 @@ +// 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 result column. +/// +public interface IReadOnlyStoredProcedureResultColumn : IReadOnlyAnnotatable +{ + /// + /// Gets the stored procedure to which this result column belongs. + /// + IReadOnlyStoredProcedure StoredProcedure { get; } + + /// + /// Gets the result column name. + /// + string Name { get; } + + /// + /// Gets the name of property mapped to this result column. + /// + string? PropertyName { get; } + + /// + /// Gets a value indicating whether the result column will hold the rows affected by the stored procedure. + /// + bool ForRowsAffected { get; } + + /// + /// + /// 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("StoredProcedureResultColumn: "); + + builder.Append(Name); + + if ((options & MetadataDebugStringOptions.SingleLine) == 0) + { + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } + } + + return builder.ToString(); + } +} diff --git a/src/EFCore.Relational/Metadata/IStoredProcedure.cs b/src/EFCore.Relational/Metadata/IStoredProcedure.cs index 3b0cf61e6cf..76caa9587c2 100644 --- a/src/EFCore.Relational/Metadata/IStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IStoredProcedure.cs @@ -23,11 +23,57 @@ public interface IStoredProcedure : IReadOnlyStoredProcedure, IAnnotatable /// IStoreStoredProcedure StoreStoredProcedure { get; } + /// + /// Gets the parameters for this stored procedure. + /// + new IEnumerable Parameters { get; } + + /// + /// Returns the parameter corresponding to the given property. + /// + /// The name of a property. + /// The parameter corresponding to the given property if found; otherwise. + new IStoredProcedureParameter? FindParameter(string propertyName); + + /// + /// Returns the original value parameter corresponding to the given property. + /// + /// The name of a property. + /// + /// The original value parameter corresponding to the given property if found; otherwise. + /// + new IStoredProcedureParameter? FindOriginalValueParameter(string propertyName); + + /// + /// Returns the rows affected parameter. + /// + /// + /// The rows affected parameter if found; otherwise. + /// + new IStoredProcedureParameter? FindRowsAffectedParameter(); + + /// + /// Gets the columns of the result for this stored procedure. + /// + new IEnumerable ResultColumns { get; } + + /// + /// Returns the result column corresponding to the given property. + /// + /// The name of a property. + /// The result column corresponding to the given property if found; otherwise. + new IStoredProcedureResultColumn? FindResultColumn(string propertyName); + + /// + /// Returns the rows affected result column. + /// > + /// The rows affected result column if found; otherwise. + new IStoredProcedureResultColumn? FindRowsAffectedResultColumn(); + /// /// Returns the store identifier of this stored procedure. /// /// The store identifier. new StoreObjectIdentifier GetStoreIdentifier() => ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()!.Value; - } diff --git a/src/EFCore.Relational/Metadata/IStoredProcedureMapping.cs b/src/EFCore.Relational/Metadata/IStoredProcedureMapping.cs index 5a29707021a..d50c7540d63 100644 --- a/src/EFCore.Relational/Metadata/IStoredProcedureMapping.cs +++ b/src/EFCore.Relational/Metadata/IStoredProcedureMapping.cs @@ -25,6 +25,11 @@ public interface IStoredProcedureMapping : ITableMappingBase /// StoreObjectIdentifier StoredProcedureIdentifier { get; } + /// + /// Gets the corresponding table mapping if it exists. + /// + ITableMapping? TableMapping { get; } + /// /// Gets the parameter mappings corresponding to the target stored procedure. /// diff --git a/src/EFCore.Relational/Metadata/IStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/IStoredProcedureParameter.cs new file mode 100644 index 00000000000..9c7e591bfc6 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IStoredProcedureParameter.cs @@ -0,0 +1,20 @@ +// 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 stored procedure parameter. +/// +public interface IStoredProcedureParameter : IReadOnlyStoredProcedureParameter, IAnnotatable +{ + /// + /// Gets the stored procedure to which this parameter belongs. + /// + new IStoredProcedure StoredProcedure { get; } + + /// + /// Gets the associated database stored procedure parameter. + /// + IStoreStoredProcedureParameter StoreParameter { get; } +} diff --git a/src/EFCore.Relational/Metadata/IStoredProcedureParameterMapping.cs b/src/EFCore.Relational/Metadata/IStoredProcedureParameterMapping.cs index f3f38d5817c..1f2d367e4bb 100644 --- a/src/EFCore.Relational/Metadata/IStoredProcedureParameterMapping.cs +++ b/src/EFCore.Relational/Metadata/IStoredProcedureParameterMapping.cs @@ -13,7 +13,12 @@ public interface IStoredProcedureParameterMapping : IColumnMappingBase /// /// Gets the target parameter. /// - IStoreStoredProcedureParameter Parameter { get; } + IStoreStoredProcedureParameter StoreParameter { get; } + + /// + /// Gets the associated stored procedure parameter. + /// + IStoredProcedureParameter Parameter { get; } /// /// Gets the containing stored procedure mapping. diff --git a/src/EFCore.Relational/Metadata/IStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/IStoredProcedureResultColumn.cs new file mode 100644 index 00000000000..1882b19d977 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IStoredProcedureResultColumn.cs @@ -0,0 +1,20 @@ +// 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 stored procedure result column. +/// +public interface IStoredProcedureResultColumn : IReadOnlyStoredProcedureResultColumn, IAnnotatable +{ + /// + /// Gets the stored procedure to which this parameter belongs. + /// + new IStoredProcedure StoredProcedure { get; } + + /// + /// Gets the associated database stored procedure result column. + /// + IStoreStoredProcedureResultColumn StoreResultColumn { get; } +} diff --git a/src/EFCore.Relational/Metadata/IStoredProcedureResultColumnMapping.cs b/src/EFCore.Relational/Metadata/IStoredProcedureResultColumnMapping.cs index fc4ce0ed160..e10ff97bfa0 100644 --- a/src/EFCore.Relational/Metadata/IStoredProcedureResultColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/IStoredProcedureResultColumnMapping.cs @@ -13,7 +13,12 @@ public interface IStoredProcedureResultColumnMapping : IColumnMappingBase /// /// Gets the target column. /// - new IStoreStoredProcedureResultColumn Column { get; } + IStoreStoredProcedureResultColumn StoreResultColumn { get; } + + /// + /// Gets the associated stored procedure result column. + /// + IStoredProcedureResultColumn ResultColumn { get; } /// /// Gets the containing stored procedure mapping. diff --git a/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs new file mode 100644 index 00000000000..5121ebbf504 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs @@ -0,0 +1,282 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +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 CurrentValuePropertyStoredProcedureParameter : + ConventionAnnotatable, + IMutableStoredProcedureParameter, + IConventionStoredProcedureParameter, + IRuntimeStoredProcedureParameter +{ + private string? _name; + private ParameterDirection? _direction; + + private ConfigurationSource? _nameConfigurationSource; + private ConfigurationSource? _directionConfigurationSource; + private InternalCurrentValuePropertyStoredProcedureParameterBuilder? _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 CurrentValuePropertyStoredProcedureParameter( + StoredProcedure storedProcedure, + string propertyName) + { + StoredProcedure = storedProcedure; + PropertyName = propertyName; + _builder = new(this, storedProcedure.Builder.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 virtual InternalCurrentValuePropertyStoredProcedureParameterBuilder 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; + + /// + /// 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 bool IsReadOnly + => ((Annotatable)StoredProcedure.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 virtual StoredProcedure StoredProcedure { 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. + /// + public virtual IStoreStoredProcedureParameter StoreParameter { get; set; } = default!; + + /// + /// 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 PropertyName { 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. + /// + public virtual bool? ForOriginalValue + => true; + + /// + /// 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 ForRowsAffected + => 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. + /// + public virtual string Name + { + get => _name ?? GetProperty().GetDefaultColumnName( + ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value); + set => SetName(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 SetName(string name, ConfigurationSource configurationSource) + { + _name = name; + + _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); + + 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 ConfigurationSource? GetNameConfigurationSource() + => _nameConfigurationSource; + + /// + /// 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 ParameterDirection Direction + { + get => _direction ?? ParameterDirection.Input; + set => SetDirection(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 ParameterDirection SetDirection(ParameterDirection direction, ConfigurationSource configurationSource) + { + if (!IsValid(direction)) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureParameterInvalidDirection( + direction, Name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()?.DisplayName())); + } + + _direction = direction; + + _directionConfigurationSource = configurationSource.Max(_directionConfigurationSource); + + return direction; + } + + /// + /// 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 IsValid(ParameterDirection direction) => direction switch + { + ParameterDirection.ReturnValue => false, + _ => true + }; + + /// + /// 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? GetDirectionConfigurationSource() + => _directionConfigurationSource; + + private IMutableProperty GetProperty() + => StoredProcedure.EntityType.FindProperty(PropertyName) + ?? StoredProcedure.EntityType.GetDerivedTypes().Select(t => t.FindDeclaredProperty(PropertyName)!) + .First(n => n != 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 override string ToString() + => ((IStoredProcedureParameter)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( + () => ((IStoredProcedureParameter)this).ToDebugString(), + () => ((IStoredProcedureParameter)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyStoredProcedure IReadOnlyStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IMutableStoredProcedure IMutableStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedure IConventionStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IStoredProcedure IStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedureParameterBuilder IConventionStoredProcedureParameter.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + string IConventionStoredProcedureParameter.SetName(string name, bool fromDataAnnotation) + => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + ParameterDirection IConventionStoredProcedureParameter.SetDirection(ParameterDirection direction, bool fromDataAnnotation) + => SetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs index b1878d8a0ae..e4df773f2ae 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs @@ -139,8 +139,13 @@ public static string GetFunctionName(MethodInfo methodInfo) return builder.ToString(); } - - /// + + /// + /// 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 IMutableModel Model { get; } /// @@ -172,9 +177,12 @@ public virtual bool IsInModel /// public virtual void SetRemovedFromModel() => _builder = null; - + /// - /// Indicates whether the function is read-only. + /// 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 bool IsReadOnly => ((Annotatable)Model).IsReadOnly; diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs index a26f7aa3df7..46f44a0b0bf 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs @@ -40,7 +40,7 @@ public DbFunctionParameter( Name = name; Function = function; ClrType = clrType; - _builder = new InternalDbFunctionParameterBuilder(this, function.Builder.ModelBuilder); + _builder = new(this, function.Builder.ModelBuilder); } /// @@ -72,9 +72,12 @@ public virtual bool IsInModel /// public virtual void SetRemovedFromModel() => _builder = null; - + /// - /// Indicates whether the function parameter is read-only. + /// 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 bool IsReadOnly => ((Annotatable)Function.Model).IsReadOnly; @@ -86,19 +89,39 @@ public override bool IsReadOnly /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual DbFunction Function { 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. + /// public virtual string Name { 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. + /// public virtual Type ClrType { 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. + /// [DebuggerStepThrough] public virtual ConfigurationSource GetConfigurationSource() => Function.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 IStoreFunctionParameter StoreFunctionParameter { get; set; } = default!; /// diff --git a/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureParameterBuilder.cs new file mode 100644 index 00000000000..54d1e416edd --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureParameterBuilder.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +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 interface IInternalStoredProcedureParameterBuilder +{ + /// + /// 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. + /// + IMutableStoredProcedureParameter Metadata { 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. + /// + IInternalStoredProcedureParameterBuilder? HasName(string name, ConfigurationSource 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. + /// + IInternalStoredProcedureParameterBuilder? HasDirection( + ParameterDirection direction, + ConfigurationSource 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. + /// + IInternalStoredProcedureParameterBuilder? HasAnnotation( + string name, + object? value, + ConfigurationSource configurationSource); +} diff --git a/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureResultColumnBuilder.cs new file mode 100644 index 00000000000..f2a0e1ba3cb --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureResultColumnBuilder.cs @@ -0,0 +1,40 @@ +// 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 interface IInternalStoredProcedureResultColumnBuilder +{ + /// + /// 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. + /// + IMutableStoredProcedureResultColumn Metadata { 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. + /// + IInternalStoredProcedureResultColumnBuilder? HasName(string name, ConfigurationSource 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. + /// + IInternalStoredProcedureResultColumnBuilder? HasAnnotation( + string name, + object? value, + ConfigurationSource configurationSource); +} diff --git a/src/EFCore.Relational/Metadata/Internal/IRuntimeStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/IRuntimeStoredProcedureParameter.cs new file mode 100644 index 00000000000..eb3590d3989 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/IRuntimeStoredProcedureParameter.cs @@ -0,0 +1,21 @@ +// 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 interface IRuntimeStoredProcedureParameter : IStoredProcedureParameter +{ + /// + /// 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. + /// + new IStoreStoredProcedureParameter StoreParameter { get; set; } +} diff --git a/src/EFCore.Relational/Metadata/Internal/IRuntimeStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/Internal/IRuntimeStoredProcedureResultColumn.cs new file mode 100644 index 00000000000..c0246ca0f2c --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/IRuntimeStoredProcedureResultColumn.cs @@ -0,0 +1,21 @@ +// 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 interface IRuntimeStoredProcedureResultColumn : IStoredProcedureResultColumn +{ + /// + /// 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. + /// + new IStoreStoredProcedureResultColumn StoreResultColumn { get; set; } +} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalCurrentValuePropertyStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalCurrentValuePropertyStoredProcedureParameterBuilder.cs new file mode 100644 index 00000000000..9d8e3a0b9b9 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/InternalCurrentValuePropertyStoredProcedureParameterBuilder.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +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 InternalCurrentValuePropertyStoredProcedureParameterBuilder : + AnnotatableBuilder, + IConventionStoredProcedureParameterBuilder, + IInternalStoredProcedureParameterBuilder +{ + /// + /// 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder( + CurrentValuePropertyStoredProcedureParameter parameter, IConventionModelBuilder modelBuilder) + : base(parameter, 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 virtual InternalCurrentValuePropertyStoredProcedureParameterBuilder? HasName( + string name, + ConfigurationSource configurationSource) + { + if (!CanSetName(name, configurationSource)) + { + return null; + } + + Metadata.SetName(name, 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 CanSetName( + string? name, + ConfigurationSource configurationSource) + => 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder? HasDirection( + ParameterDirection direction, + ConfigurationSource configurationSource) + { + if (!CanSetDirection(direction, configurationSource)) + { + return null; + } + + Metadata.SetDirection(direction, 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 CanSetDirection( + ParameterDirection direction, + ConfigurationSource configurationSource) + => configurationSource == ConfigurationSource.Explicit + || Metadata.Direction == direction + || (configurationSource.Overrides(Metadata.GetDirectionConfigurationSource()) && Metadata.IsValid(direction)); + + /// + IConventionStoredProcedureParameter IConventionStoredProcedureParameterBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + IMutableStoredProcedureParameter IInternalStoredProcedureParameterBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasName(string name, bool fromDataAnnotation) + => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasName( + string name, ConfigurationSource configurationSource) + => HasName(name, configurationSource); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureParameterBuilder.CanSetName(string? name, bool fromDataAnnotation) + => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasDirection( + ParameterDirection direction, bool fromDataAnnotation) + => HasDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasDirection( + ParameterDirection direction, ConfigurationSource configurationSource) + => HasDirection(direction, configurationSource); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureParameterBuilder.CanSetDirection(ParameterDirection direction, bool fromDataAnnotation) + => CanSetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasAnnotation( + string name, object? value, ConfigurationSource configurationSource) + => (IInternalStoredProcedureParameterBuilder?)HasAnnotation(name, value, configurationSource); +} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalOriginalValuePropertyStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalOriginalValuePropertyStoredProcedureParameterBuilder.cs new file mode 100644 index 00000000000..1335e9da55a --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/InternalOriginalValuePropertyStoredProcedureParameterBuilder.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +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 InternalOriginalValuePropertyStoredProcedureParameterBuilder : + AnnotatableBuilder, + IConventionStoredProcedureParameterBuilder, + IInternalStoredProcedureParameterBuilder +{ + /// + /// 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 InternalOriginalValuePropertyStoredProcedureParameterBuilder( + OriginalValuePropertyStoredProcedureParameter parameter, IConventionModelBuilder modelBuilder) + : base(parameter, 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 virtual InternalOriginalValuePropertyStoredProcedureParameterBuilder? HasName( + string name, + ConfigurationSource configurationSource) + { + if (!CanSetName(name, configurationSource)) + { + return null; + } + + Metadata.SetName(name, 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 CanSetName( + string? name, + ConfigurationSource configurationSource) + => 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 InternalOriginalValuePropertyStoredProcedureParameterBuilder? HasDirection( + ParameterDirection direction, + ConfigurationSource configurationSource) + { + if (!CanSetDirection(direction, configurationSource)) + { + return null; + } + + Metadata.SetDirection(direction, 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 CanSetDirection( + ParameterDirection direction, + ConfigurationSource configurationSource) + => configurationSource == ConfigurationSource.Explicit + || Metadata.Direction == direction + || (configurationSource.Overrides(Metadata.GetDirectionConfigurationSource()) && Metadata.IsValid(direction)); + + /// + IConventionStoredProcedureParameter IConventionStoredProcedureParameterBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + IMutableStoredProcedureParameter IInternalStoredProcedureParameterBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasName(string name, bool fromDataAnnotation) + => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasName( + string name, ConfigurationSource configurationSource) + => HasName(name, configurationSource); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureParameterBuilder.CanSetName(string? name, bool fromDataAnnotation) + => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasDirection( + ParameterDirection direction, bool fromDataAnnotation) + => HasDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasDirection( + ParameterDirection direction, ConfigurationSource configurationSource) + => HasDirection(direction, configurationSource); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureParameterBuilder.CanSetDirection(ParameterDirection direction, bool fromDataAnnotation) + => CanSetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasAnnotation( + string name, object? value, ConfigurationSource configurationSource) + => (IInternalStoredProcedureParameterBuilder?)HasAnnotation(name, value, configurationSource); +} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureParameterBuilder.cs new file mode 100644 index 00000000000..95362da92e1 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureParameterBuilder.cs @@ -0,0 +1,146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +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 InternalRowsAffectedStoredProcedureParameterBuilder : + AnnotatableBuilder, + IConventionStoredProcedureParameterBuilder, + IInternalStoredProcedureParameterBuilder +{ + /// + /// 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 InternalRowsAffectedStoredProcedureParameterBuilder( + RowsAffectedStoredProcedureParameter parameter, IConventionModelBuilder modelBuilder) + : base(parameter, 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 virtual InternalRowsAffectedStoredProcedureParameterBuilder? HasName( + string name, + ConfigurationSource configurationSource) + { + if (!CanSetName(name, configurationSource)) + { + return null; + } + + Metadata.Name = name; + + 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 CanSetName( + string? name, + ConfigurationSource configurationSource) + => 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 InternalRowsAffectedStoredProcedureParameterBuilder? HasDirection( + ParameterDirection direction, + ConfigurationSource configurationSource) + { + if (!CanSetDirection(direction, configurationSource)) + { + return null; + } + + Metadata.Direction = direction; + + 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 CanSetDirection( + ParameterDirection direction, + ConfigurationSource configurationSource) + => configurationSource == ConfigurationSource.Explicit; + + /// + IConventionStoredProcedureParameter IConventionStoredProcedureParameterBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + IMutableStoredProcedureParameter IInternalStoredProcedureParameterBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasName(string name, bool fromDataAnnotation) + => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasName( + string name, ConfigurationSource configurationSource) + => HasName(name, configurationSource); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureParameterBuilder.CanSetName(string? name, bool fromDataAnnotation) + => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasDirection( + ParameterDirection direction, bool fromDataAnnotation) + => HasDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasDirection( + ParameterDirection direction, ConfigurationSource configurationSource) + => HasDirection(direction, configurationSource); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureParameterBuilder.CanSetDirection(ParameterDirection direction, bool fromDataAnnotation) + => CanSetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasAnnotation( + string name, object? value, ConfigurationSource configurationSource) + => (IInternalStoredProcedureParameterBuilder?)HasAnnotation(name, value, configurationSource); +} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureResultColumnBuilder.cs new file mode 100644 index 00000000000..559898869a5 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureResultColumnBuilder.cs @@ -0,0 +1,92 @@ +// 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 InternalRowsAffectedStoredProcedureResultColumnBuilder : + AnnotatableBuilder, + IConventionStoredProcedureResultColumnBuilder, + IInternalStoredProcedureResultColumnBuilder +{ + /// + /// 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 InternalRowsAffectedStoredProcedureResultColumnBuilder( + RowsAffectedStoredProcedureResultColumn resultColumn, IConventionModelBuilder modelBuilder) + : base(resultColumn, 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 virtual InternalRowsAffectedStoredProcedureResultColumnBuilder? HasName( + string name, + ConfigurationSource configurationSource) + { + if (!CanSetName(name, configurationSource)) + { + return null; + } + + Metadata.Name = name; + + 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 CanSetName( + string? name, + ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetNameConfigurationSource()) + || Metadata.Name == name; + + /// + IConventionStoredProcedureResultColumn IConventionStoredProcedureResultColumnBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + IMutableStoredProcedureResultColumn IInternalStoredProcedureResultColumnBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasName(string name, bool fromDataAnnotation) + => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + IInternalStoredProcedureResultColumnBuilder? IInternalStoredProcedureResultColumnBuilder.HasName(string name, ConfigurationSource configurationSource) + => HasName(name, configurationSource); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureResultColumnBuilder.CanSetName(string? name, bool fromDataAnnotation) + => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + IInternalStoredProcedureResultColumnBuilder? IInternalStoredProcedureResultColumnBuilder.HasAnnotation(string name, object? value, ConfigurationSource configurationSource) + => (IInternalStoredProcedureResultColumnBuilder?)HasAnnotation(name, value, configurationSource); +} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs index b4bebbfdef0..5adbb5ebb31 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs @@ -1,8 +1,6 @@ // 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; /// @@ -163,21 +161,22 @@ public virtual bool CanSetSchema(string? schema, ConfigurationSource configurati /// 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( + public virtual InternalCurrentValuePropertyStoredProcedureParameterBuilder? HasParameter( string propertyName, ConfigurationSource configurationSource) { - if (!Metadata.ContainsParameter(propertyName)) + var parameter = Metadata.FindParameter(propertyName); + if (parameter == null) { if (!configurationSource.Overrides(Metadata.GetConfigurationSource())) { return null; } - Metadata.AddParameter(propertyName); + parameter = Metadata.AddParameter(propertyName); } Metadata.UpdateConfigurationSource(configurationSource); - return this; + return parameter.Builder; } /// @@ -186,7 +185,7 @@ public virtual bool CanSetSchema(string? schema, ConfigurationSource configurati /// 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( + public virtual InternalCurrentValuePropertyStoredProcedureParameterBuilder? HasParameter( Expression> propertyExpression, ConfigurationSource configurationSource) where TDerivedEntity : class @@ -199,30 +198,122 @@ public virtual bool CanSetSchema(string? schema, ConfigurationSource configurati /// 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) + => Metadata.FindParameter(propertyName) != null || 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 InternalOriginalValuePropertyStoredProcedureParameterBuilder? HasOriginalValueParameter( + string propertyName, ConfigurationSource configurationSource) + { + var parameter = Metadata.FindOriginalValueParameter(propertyName); + if (parameter == null) + { + if (!configurationSource.Overrides(Metadata.GetConfigurationSource())) + { + return null; + } + + parameter = Metadata.AddOriginalValueParameter(propertyName); + } + Metadata.UpdateConfigurationSource(configurationSource); + return parameter.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 InternalOriginalValuePropertyStoredProcedureParameterBuilder? HasOriginalValueParameter( + Expression> propertyExpression, + ConfigurationSource configurationSource) + where TDerivedEntity : class + => HasOriginalValueParameter(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 CanHaveOriginalValueParameter(string propertyName, ConfigurationSource configurationSource) + => Metadata.FindOriginalValueParameter(propertyName) != null + || 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( + public virtual InternalRowsAffectedStoredProcedureParameterBuilder? HasRowsAffectedParameter( + ConfigurationSource configurationSource) + { + var parameter = Metadata.FindRowsAffectedParameter(); + if (parameter == null) + { + if (!configurationSource.Overrides(Metadata.GetConfigurationSource())) + { + return null; + } + + parameter = Metadata.AddRowsAffectedParameter(); + } + + Metadata.UpdateConfigurationSource(configurationSource); + return parameter.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 InternalRowsAffectedStoredProcedureParameterBuilder? HasRowsAffectedParameter( + ConfigurationSource configurationSource) + where TDerivedEntity : class + => HasRowsAffectedParameter(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 CanHaveRowsAffectedParameter(ConfigurationSource configurationSource) + => Metadata.FindRowsAffectedParameter() != null + || 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 InternalStoredProcedureResultColumnBuilder? HasResultColumn( string propertyName, ConfigurationSource configurationSource) { - if (!Metadata.ContainsResultColumn(propertyName)) + var resultColumn = Metadata.FindResultColumn(propertyName); + if (resultColumn == null) { if (!configurationSource.Overrides(Metadata.GetConfigurationSource())) { return null; } - Metadata.AddResultColumn(propertyName); + resultColumn = Metadata.AddResultColumn(propertyName); } Metadata.UpdateConfigurationSource(configurationSource); - return this; + return resultColumn.Builder; } /// @@ -231,7 +322,7 @@ public virtual bool CanHaveParameter(string propertyName, ConfigurationSource co /// 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( + public virtual InternalStoredProcedureResultColumnBuilder? HasResultColumn( Expression> propertyExpression, ConfigurationSource configurationSource) where TDerivedEntity : class @@ -244,7 +335,79 @@ public virtual bool CanHaveParameter(string propertyName, ConfigurationSource co /// 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) + => Metadata.FindResultColumn(propertyName) != null + || 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 InternalRowsAffectedStoredProcedureResultColumnBuilder? HasRowsAffectedResultColumn( + ConfigurationSource configurationSource) + { + var resultColumn = Metadata.FindRowsAffectedResultColumn(); + if (resultColumn == null) + { + if (!configurationSource.Overrides(Metadata.GetConfigurationSource())) + { + return null; + } + + resultColumn = Metadata.AddRowsAffectedResultColumn(); + } + + Metadata.UpdateConfigurationSource(configurationSource); + return resultColumn.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 InternalRowsAffectedStoredProcedureResultColumnBuilder? HasRowsAffectedResultColumn( + ConfigurationSource configurationSource) + where TDerivedEntity : class + => HasRowsAffectedResultColumn(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 CanHaveRowsAffectedResultColumn(ConfigurationSource configurationSource) + => Metadata.FindRowsAffectedResultColumn() != null + || 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? HasRowsAffectedReturn(bool rowsAffectedReturned, ConfigurationSource configurationSource) + { + if (!CanHaveRowsAffectedReturn(rowsAffectedReturned, configurationSource)) + { + return null; + } + + Metadata.SetAreRowsAffectedReturned(rowsAffectedReturned); + 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 CanHaveRowsAffectedReturn(bool rowsAffectedReturned, ConfigurationSource configurationSource) + => Metadata.AreRowsAffectedReturned == rowsAffectedReturned || configurationSource.Overrides(Metadata.GetConfigurationSource()); /// @@ -308,7 +471,7 @@ bool IConventionStoredProcedureBuilder.CanSetSchema(string? schema, bool fromDat /// [DebuggerStepThrough] - IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasParameter(string propertyName, bool fromDataAnnotation) + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureBuilder.HasParameter(string propertyName, bool fromDataAnnotation) => HasParameter(propertyName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -316,10 +479,33 @@ bool IConventionStoredProcedureBuilder.CanSetSchema(string? schema, bool fromDat bool IConventionStoredProcedureBuilder.CanHaveParameter(string propertyName, bool fromDataAnnotation) => CanHaveParameter(propertyName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureBuilder.HasOriginalValueParameter( + string propertyName, bool fromDataAnnotation) + => HasOriginalValueParameter(propertyName, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureBuilder.CanHaveOriginalValueParameter(string propertyName, bool fromDataAnnotation) + => CanHaveOriginalValueParameter(propertyName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureBuilder.HasRowsAffectedParameter(bool fromDataAnnotation) + => HasRowsAffectedParameter( + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasResultColumn(string propertyName, bool fromDataAnnotation) + bool IConventionStoredProcedureBuilder.CanHaveRowsAffectedParameter(bool fromDataAnnotation) + => CanHaveRowsAffectedParameter(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureBuilder.HasResultColumn(string propertyName, bool fromDataAnnotation) => HasResultColumn(propertyName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -327,6 +513,17 @@ bool IConventionStoredProcedureBuilder.CanHaveParameter(string propertyName, boo bool IConventionStoredProcedureBuilder.CanHaveResultColumn(string propertyName, bool fromDataAnnotation) => CanHaveResultColumn(propertyName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureBuilder.HasRowsAffectedResultColumn( + string propertyName, bool fromDataAnnotation) + => HasRowsAffectedResultColumn(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureBuilder.CanHaveRowsAffectedResultColumn(string propertyName, bool fromDataAnnotation) + => CanHaveRowsAffectedResultColumn(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs new file mode 100644 index 00000000000..67191180a26 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs @@ -0,0 +1,96 @@ +// 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 InternalStoredProcedureResultColumnBuilder : + AnnotatableBuilder, + IConventionStoredProcedureResultColumnBuilder, + IInternalStoredProcedureResultColumnBuilder +{ + /// + /// 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 InternalStoredProcedureResultColumnBuilder( + PropertyStoredProcedureResultColumn resultColumn, IConventionModelBuilder modelBuilder) + : base(resultColumn, 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 virtual InternalStoredProcedureResultColumnBuilder? HasName( + string name, + ConfigurationSource configurationSource) + { + if (!CanSetName(name, configurationSource)) + { + return null; + } + + Metadata.SetName(name, 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 CanSetName( + string? name, + ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetNameConfigurationSource()) + || Metadata.Name == name; + + /// + IConventionStoredProcedureResultColumn IConventionStoredProcedureResultColumnBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + IMutableStoredProcedureResultColumn IInternalStoredProcedureResultColumnBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasName(string name, bool fromDataAnnotation) + => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureResultColumnBuilder? IInternalStoredProcedureResultColumnBuilder.HasName( + string name, ConfigurationSource configurationSource) + => HasName(name, configurationSource); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedureResultColumnBuilder.CanSetName(string? name, bool fromDataAnnotation) + => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IInternalStoredProcedureResultColumnBuilder? IInternalStoredProcedureResultColumnBuilder.HasAnnotation( + string name, object? value, ConfigurationSource configurationSource) + => (IInternalStoredProcedureResultColumnBuilder?)HasAnnotation(name, value, configurationSource); +} diff --git a/src/EFCore.Relational/Metadata/Internal/OriginalValuePropertyStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/OriginalValuePropertyStoredProcedureParameter.cs new file mode 100644 index 00000000000..fa4ecbe89be --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/OriginalValuePropertyStoredProcedureParameter.cs @@ -0,0 +1,285 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +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 OriginalValuePropertyStoredProcedureParameter : + ConventionAnnotatable, + IMutableStoredProcedureParameter, + IConventionStoredProcedureParameter, + IRuntimeStoredProcedureParameter +{ + private string? _name; + private ParameterDirection? _direction; + + private ConfigurationSource? _nameConfigurationSource; + private ConfigurationSource? _directionConfigurationSource; + private InternalOriginalValuePropertyStoredProcedureParameterBuilder? _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 OriginalValuePropertyStoredProcedureParameter( + StoredProcedure storedProcedure, + string propertyName) + { + StoredProcedure = storedProcedure; + PropertyName = propertyName; + _builder = new(this, storedProcedure.Builder.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 virtual InternalOriginalValuePropertyStoredProcedureParameterBuilder 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; + + /// + /// 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 bool IsReadOnly + => ((Annotatable)StoredProcedure.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 virtual StoredProcedure StoredProcedure { 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. + /// + public virtual IStoreStoredProcedureParameter StoreParameter { get; set; } = default!; + + /// + /// 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 PropertyName { 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. + /// + public virtual bool? ForOriginalValue + => true; + + /// + /// 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 ForRowsAffected + => 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. + /// + public virtual string Name + { + get => _name ?? GetProperty().GetDefaultColumnName( + ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value)!; + set => SetName(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 SetName(string name, ConfigurationSource configurationSource) + { + _name = name; + + _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); + + 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 ConfigurationSource? GetNameConfigurationSource() + => _nameConfigurationSource; + + /// + /// 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 ParameterDirection Direction + { + get => _direction ?? ParameterDirection.Input; + set => SetDirection(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 ParameterDirection SetDirection(ParameterDirection direction, ConfigurationSource configurationSource) + { + if (!IsValid(direction)) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureParameterInvalidDirection( + direction, Name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()?.DisplayName())); + } + + _direction = direction; + + _directionConfigurationSource = configurationSource.Max(_directionConfigurationSource); + + return direction; + } + + /// + /// 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 IsValid(ParameterDirection direction) => direction switch + { + ParameterDirection.Output => false, + ParameterDirection.ReturnValue => false, + _ => true + }; + + /// + /// 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? GetDirectionConfigurationSource() + => _directionConfigurationSource; + + private IMutableProperty GetProperty() + => StoredProcedure.EntityType.FindProperty(PropertyName) + ?? StoredProcedure.EntityType.GetDerivedTypes().Select(t => t.FindDeclaredProperty(PropertyName)!) + .First(n => n != 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 override string ToString() + => ((IStoredProcedureParameter)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( + () => ((IStoredProcedureParameter)this).ToDebugString(), + () => ((IStoredProcedureParameter)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyStoredProcedure IReadOnlyStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IMutableStoredProcedure IMutableStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedure IConventionStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IStoredProcedure IStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedureParameterBuilder IConventionStoredProcedureParameter.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + [DebuggerStepThrough] + string IConventionStoredProcedureParameter.SetName(string name, bool fromDataAnnotation) + => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + ParameterDirection IConventionStoredProcedureParameter.SetDirection(ParameterDirection direction, bool fromDataAnnotation) + => SetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore.Relational/Metadata/Internal/PropertyStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/Internal/PropertyStoredProcedureResultColumn.cs new file mode 100644 index 00000000000..9991cc53dab --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/PropertyStoredProcedureResultColumn.cs @@ -0,0 +1,213 @@ +// 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 PropertyStoredProcedureResultColumn : + ConventionAnnotatable, + IMutableStoredProcedureResultColumn, + IConventionStoredProcedureResultColumn, + IRuntimeStoredProcedureResultColumn +{ + private InternalStoredProcedureResultColumnBuilder? _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 PropertyStoredProcedureResultColumn( + StoredProcedure storedProcedure, + string propertyName) + { + StoredProcedure = storedProcedure; + PropertyName = propertyName; + _builder = new(this, storedProcedure.Builder.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 virtual InternalStoredProcedureResultColumnBuilder 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; + + /// + /// 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 bool IsReadOnly + => ((Annotatable)StoredProcedure.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 virtual StoredProcedure StoredProcedure { 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. + /// + public virtual IStoreStoredProcedureResultColumn StoreResultColumn { get; set; } = default!; + + /// + /// 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 PropertyName { 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. + /// + public virtual bool ForRowsAffected + => 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. + /// + public virtual string Name + { + get => GetProperty().GetColumnName( + ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value)!; + set => SetName(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? SetName(string name, ConfigurationSource configurationSource) + { + if (configurationSource == ConfigurationSource.Explicit) + { + GetProperty().SetColumnName(name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value); + return name; + } + + return ((IConventionProperty)GetProperty()).SetColumnName( + name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value, + fromDataAnnotation: configurationSource == ConfigurationSource.DataAnnotation); + } + + /// + /// 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? GetNameConfigurationSource() + => ((IConventionProperty)GetProperty()!) + .GetColumnNameConfigurationSource(((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value); + + private IMutableProperty GetProperty() + => StoredProcedure.EntityType.FindProperty(PropertyName) + ?? StoredProcedure.EntityType.GetDerivedTypes().Select(t => t.FindDeclaredProperty(PropertyName)!) + .First(n => n != 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 override string ToString() + => ((IStoredProcedureResultColumn)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( + () => ((IStoredProcedureResultColumn)this).ToDebugString(), + () => ((IStoredProcedureResultColumn)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyStoredProcedure IReadOnlyStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IMutableStoredProcedure IMutableStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedure IConventionStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IStoredProcedure IStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedureResultColumnBuilder IConventionStoredProcedureResultColumn.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + string? IConventionStoredProcedureResultColumn.SetName(string name, bool fromDataAnnotation) + => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 534d3160ffc..11919dc3ffd 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -128,10 +128,17 @@ public override bool IsReadOnly /// public static IModel Add( IModel model, - IRelationalAnnotationProvider? relationalAnnotationProvider, + IRelationalAnnotationProvider relationalAnnotationProvider, + IRelationalTypeMappingSource relationalTypeMappingSource, bool designTime) { - model.AddRuntimeAnnotation(RelationalAnnotationNames.RelationalModel, Create(model, relationalAnnotationProvider, designTime)); + model.AddRuntimeAnnotation( + RelationalAnnotationNames.RelationalModel, + Create( + model, + relationalAnnotationProvider, + relationalTypeMappingSource, + designTime)); return model; } @@ -143,7 +150,8 @@ public static IModel Add( /// public static IRelationalModel Create( IModel model, - IRelationalAnnotationProvider? relationalAnnotationProvider, + IRelationalAnnotationProvider relationalAnnotationProvider, + IRelationalTypeMappingSource relationalTypeMappingSource, bool designTime) { var databaseModel = new RelationalModel(model); @@ -160,7 +168,7 @@ public static IRelationalModel Create( AddMappedFunctions(databaseModel, entityType); - AddStoredProcedures(databaseModel, entityType); + AddStoredProcedures(databaseModel, entityType, relationalTypeMappingSource); } AddTvfs(databaseModel); @@ -870,7 +878,10 @@ private static StoreFunction GetOrCreateStoreFunction(IRuntimeDbFunction dbFunct return storeFunction; } - private static void AddStoredProcedures(RelationalModel databaseModel, IEntityType entityType) + private static void AddStoredProcedures( + RelationalModel databaseModel, + IEntityType entityType, + IRelationalTypeMappingSource relationalTypeMappingSource) { var mappedType = entityType; @@ -890,6 +901,17 @@ private static void AddStoredProcedures(RelationalModel databaseModel, IEntityTy var isTpc = mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy; while (mappedType != null) { + var includesDerivedTypes = !isTpc && mappedType == entityType; + + var tableMappings = entityType.GetTableMappings().Where(m + => m.Table.Name == mappedType.GetTableName() + && m.Table.Schema == mappedType.GetSchema() + && m.IsSplitEntityTypePrincipal != false + && m.IncludesDerivedTypes == includesDerivedTypes); + var tableMapping = tableMappings.FirstOrDefault(); + + Check.DebugAssert(tableMapping == null || tableMappings.Count() == 1, "Expected table mapping to be unique"); + var insertSproc = (IRuntimeStoredProcedure?)mappedType.GetInsertStoredProcedure(); if (insertSproc != null && insertStoredProcedureMappings != null) @@ -898,9 +920,11 @@ private static void AddStoredProcedures(RelationalModel databaseModel, IEntityTy entityType, mappedType, insertSproc, + tableMapping, databaseModel, insertStoredProcedureMappings, - includesDerivedTypes: !isTpc && mappedType == entityType); + includesDerivedTypes, + relationalTypeMappingSource); } else if (entityType == mappedType) { @@ -915,9 +939,11 @@ private static void AddStoredProcedures(RelationalModel databaseModel, IEntityTy entityType, mappedType, deleteSproc, + tableMapping, databaseModel, deleteStoredProcedureMappings, - includesDerivedTypes: !isTpc && mappedType == entityType); + includesDerivedTypes, + relationalTypeMappingSource); } else if (entityType == mappedType) { @@ -932,9 +958,11 @@ private static void AddStoredProcedures(RelationalModel databaseModel, IEntityTy entityType, mappedType, updateSproc, + tableMapping, databaseModel, updateStoredProcedureMappings, - includesDerivedTypes: !isTpc && mappedType == entityType); + includesDerivedTypes, + relationalTypeMappingSource); } else if (entityType == mappedType) { @@ -972,15 +1000,17 @@ private static void CreateStoredProcedureMapping( IEntityType entityType, IEntityType mappedType, IRuntimeStoredProcedure storedProcedure, + ITableMapping? tableMapping, RelationalModel model, List storedProcedureMappings, - bool includesDerivedTypes) + bool includesDerivedTypes, + IRelationalTypeMappingSource relationalTypeMappingSource) { var storeStoredProcedure = GetOrCreateStoreStoredProcedure(storedProcedure, model); var identifier = storedProcedure.GetStoreIdentifier(); var storedProcedureMapping = new StoredProcedureMapping( - entityType, storeStoredProcedure, storedProcedure, includesDerivedTypes); + entityType, storeStoredProcedure, storedProcedure, tableMapping, includesDerivedTypes); var (parameterMappingAnnotationName, columnMappingAnnotationName) = identifier.StoreObjectType switch { StoreObjectType.InsertStoredProcedure @@ -996,7 +1026,19 @@ private static void CreateStoredProcedureMapping( foreach (var parameter in storedProcedure.Parameters) { - var property = mappedType.FindProperty(parameter); + if (parameter.PropertyName == null) + { + GetOrCreateStoreStoredProcedureParameter( + parameter, + null, + storeStoredProcedure, + identifier, + relationalTypeMappingSource); + + continue; + } + + var property = mappedType.FindProperty(parameter.PropertyName); if (property == null) { Check.DebugAssert( @@ -1007,9 +1049,14 @@ private static void CreateStoredProcedureMapping( { foreach (var derivedProperty in entityType.GetDerivedProperties()) { - if (derivedProperty.Name == parameter) + if (derivedProperty.Name == parameter.PropertyName) { - GetOrCreateStoreStoredProcedureParameter(derivedProperty, storeStoredProcedure, identifier); + GetOrCreateStoreStoredProcedureParameter( + parameter, + derivedProperty, + storeStoredProcedure, + identifier, + relationalTypeMappingSource); break; } } @@ -1018,9 +1065,15 @@ private static void CreateStoredProcedureMapping( continue; } - var storeParameter = GetOrCreateStoreStoredProcedureParameter(property, storeStoredProcedure, identifier); + var storeParameter = GetOrCreateStoreStoredProcedureParameter( + parameter, + property, + storeStoredProcedure, + identifier, + relationalTypeMappingSource); - var columnMapping = new StoredProcedureParameterMapping(property, storeParameter, storedProcedureMapping); + var columnMapping = new StoredProcedureParameterMapping( + property, parameter, storeParameter, storedProcedureMapping); storedProcedureMapping.AddParameterMapping(columnMapping); storeParameter.AddPropertyMapping(columnMapping); @@ -1036,7 +1089,19 @@ private static void CreateStoredProcedureMapping( foreach (var resultColumn in storedProcedure.ResultColumns) { - var property = mappedType.FindProperty(resultColumn); + if (resultColumn.PropertyName == null) + { + GetOrCreateStoreStoredProcedureResultColumn( + resultColumn, + null, + storeStoredProcedure, + identifier, + relationalTypeMappingSource); + + continue; + } + + var property = mappedType.FindProperty(resultColumn.PropertyName); if (property == null) { Check.DebugAssert( @@ -1047,9 +1112,14 @@ private static void CreateStoredProcedureMapping( { foreach (var derivedProperty in entityType.GetDerivedProperties()) { - if (derivedProperty.Name == resultColumn) + if (derivedProperty.Name == resultColumn.PropertyName) { - GetOrCreateStoreStoredProcedureResultColumn(derivedProperty, storeStoredProcedure, identifier); + GetOrCreateStoreStoredProcedureResultColumn( + resultColumn, + derivedProperty, + storeStoredProcedure, + identifier, + relationalTypeMappingSource); break; } } @@ -1058,9 +1128,15 @@ private static void CreateStoredProcedureMapping( continue; } - var column = GetOrCreateStoreStoredProcedureResultColumn(property, storeStoredProcedure, identifier); + var column = GetOrCreateStoreStoredProcedureResultColumn( + resultColumn, + property, + storeStoredProcedure, + identifier, + relationalTypeMappingSource); - var columnMapping = new StoredProcedureResultColumnMapping(property, column, storedProcedureMapping); + var columnMapping = new StoredProcedureResultColumnMapping( + property, resultColumn, column, storedProcedureMapping); storedProcedureMapping.AddColumnMapping(columnMapping); column.AddPropertyMapping(columnMapping); @@ -1101,22 +1177,28 @@ static StoreStoredProcedure GetOrCreateStoreStoredProcedure( } static StoreStoredProcedureParameter GetOrCreateStoreStoredProcedureParameter( - IProperty property, + IStoredProcedureParameter parameter, + IProperty? property, StoreStoredProcedure storeStoredProcedure, - StoreObjectIdentifier identifier) + StoreObjectIdentifier identifier, + IRelationalTypeMappingSource relationalTypeMappingSource) { - var columnName = property.GetColumnName(identifier)!; - var storeParameter = (StoreStoredProcedureParameter?)storeStoredProcedure.FindParameter(columnName); + var name = parameter.Name; + var storeParameter = (StoreStoredProcedureParameter?)storeStoredProcedure.FindParameter(name); if (storeParameter == null) { storeParameter = new StoreStoredProcedureParameter( - columnName, - property.GetColumnType(identifier), + name, + property?.GetColumnType(identifier) + ?? relationalTypeMappingSource.FindMapping(typeof(int))!.StoreType, storeStoredProcedure, - property.GetDirection(identifier)) { IsNullable = property.IsColumnNullable(identifier) }; + parameter.Direction) + { + IsNullable = property?.IsColumnNullable(identifier) ?? false + }; storeStoredProcedure.AddParameter(storeParameter); } - else if (!property.IsColumnNullable(identifier)) + else if (property?.IsColumnNullable(identifier) == false) { storeParameter.IsNullable = false; } @@ -1125,21 +1207,27 @@ static StoreStoredProcedureParameter GetOrCreateStoreStoredProcedureParameter( } static StoreStoredProcedureResultColumn GetOrCreateStoreStoredProcedureResultColumn( - IProperty property, + IStoredProcedureResultColumn resultColumn, + IProperty? property, StoreStoredProcedure storeStoredProcedure, - StoreObjectIdentifier identifier) + StoreObjectIdentifier identifier, + IRelationalTypeMappingSource relationalTypeMappingSource) { - var columnName = property.GetColumnName(identifier)!; - var column = (StoreStoredProcedureResultColumn?)storeStoredProcedure.FindResultColumn(columnName); + var name = resultColumn.Name; + var column = (StoreStoredProcedureResultColumn?)storeStoredProcedure.FindResultColumn(name); if (column == null) { - column = new StoreStoredProcedureResultColumn(columnName, property.GetColumnType(identifier), storeStoredProcedure) + column = new StoreStoredProcedureResultColumn( + name, + property?.GetColumnType(identifier) + ?? relationalTypeMappingSource.FindMapping(typeof(int))!.StoreType, + storeStoredProcedure) { - IsNullable = property.IsColumnNullable(identifier) + IsNullable = property?.IsColumnNullable(identifier) ?? false }; storeStoredProcedure.AddResultColumn(column); } - else if (!property.IsColumnNullable(identifier)) + else if (property?.IsColumnNullable(identifier) == false) { column.IsNullable = false; } diff --git a/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureParameter.cs new file mode 100644 index 00000000000..51ba8a5b0ee --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureParameter.cs @@ -0,0 +1,238 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +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 RowsAffectedStoredProcedureParameter : + ConventionAnnotatable, + IMutableStoredProcedureParameter, + IConventionStoredProcedureParameter, + IRuntimeStoredProcedureParameter +{ + private string _name = "RowsAffected"; + + private ConfigurationSource? _nameConfigurationSource; + private InternalRowsAffectedStoredProcedureParameterBuilder? _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 RowsAffectedStoredProcedureParameter(StoredProcedure storedProcedure) + { + StoredProcedure = storedProcedure; + _builder = new(this, storedProcedure.Builder.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 virtual InternalRowsAffectedStoredProcedureParameterBuilder 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; + + /// + /// 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 bool IsReadOnly + => ((Annotatable)StoredProcedure.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 virtual StoredProcedure StoredProcedure { 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. + /// + public virtual IStoreStoredProcedureParameter StoreParameter { get; set; } = default!; + + /// + /// 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? ForOriginalValue + => 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 ForRowsAffected + => true; + + /// + /// 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? PropertyName => 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 string Name + { + get => _name; + set => SetName(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 SetName(string name, ConfigurationSource configurationSource) + { + _name = name; + + _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); + + 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 ConfigurationSource? GetNameConfigurationSource() + => _nameConfigurationSource; + + /// + /// 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 ParameterDirection Direction + { + get => ParameterDirection.Output; + set => throw new InvalidOperationException( + RelationalStrings.StoredProcedureParameterInvalidConfiguration( + nameof(Direction), Name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()?.DisplayName())); + } + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + public virtual ConfigurationSource? GetDirectionConfigurationSource() + => 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 override string ToString() + => ((IStoredProcedureParameter)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( + () => ((IStoredProcedureParameter)this).ToDebugString(), + () => ((IStoredProcedureParameter)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyStoredProcedure IReadOnlyStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IMutableStoredProcedure IMutableStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedure IConventionStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IStoredProcedure IStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedureParameterBuilder IConventionStoredProcedureParameter.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + string IConventionStoredProcedureParameter.SetName(string name, bool fromDataAnnotation) + => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + ParameterDirection IConventionStoredProcedureParameter.SetDirection(ParameterDirection direction, bool fromDataAnnotation) + => Direction = direction; +} diff --git a/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureResultColumn.cs new file mode 100644 index 00000000000..62e2178af8f --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureResultColumn.cs @@ -0,0 +1,203 @@ +// 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 RowsAffectedStoredProcedureResultColumn : + ConventionAnnotatable, + IMutableStoredProcedureResultColumn, + IConventionStoredProcedureResultColumn, + IRuntimeStoredProcedureResultColumn +{ + private string _name = "RowsAffected"; + + private ConfigurationSource? _nameConfigurationSource; + private InternalRowsAffectedStoredProcedureResultColumnBuilder? _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 RowsAffectedStoredProcedureResultColumn(StoredProcedure storedProcedure) + { + StoredProcedure = storedProcedure; + _builder = new(this, storedProcedure.Builder.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 virtual InternalRowsAffectedStoredProcedureResultColumnBuilder 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; + + /// + /// 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 bool IsReadOnly + => ((Annotatable)StoredProcedure.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 virtual StoredProcedure StoredProcedure { 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. + /// + public virtual IStoreStoredProcedureResultColumn StoreResultColumn { get; set; } = default!; + + /// + /// 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? PropertyName + => 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 ForRowsAffected + => true; + + /// + /// 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 Name + { + get => _name; + set => SetName(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 SetName(string name, ConfigurationSource configurationSource) + { + _name = name; + + _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); + + 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 ConfigurationSource? GetNameConfigurationSource() + => _nameConfigurationSource; + + /// + /// 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() + => ((IStoredProcedureResultColumn)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( + () => ((IStoredProcedureResultColumn)this).ToDebugString(), + () => ((IStoredProcedureResultColumn)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyStoredProcedure IReadOnlyStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IMutableStoredProcedure IMutableStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedure IConventionStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IStoredProcedure IStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IConventionStoredProcedureResultColumnBuilder IConventionStoredProcedureResultColumn.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + string IConventionStoredProcedureResultColumn.SetName(string name, bool fromDataAnnotation) + => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs index 74132283939..971e0d18a47 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs @@ -80,7 +80,7 @@ public virtual void AddParameter(IStoreStoredProcedureParameter parameter) .Concat(property.GetDeleteStoredProcedureParameterMappings()) .Concat(property.GetUpdateStoredProcedureParameterMappings()) .FirstOrDefault(cm => cm.StoredProcedureMapping.StoreStoredProcedure == this) - ?.Parameter; + ?.StoreParameter; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -129,6 +129,18 @@ public virtual void AddResultColumn(IStoreStoredProcedureResultColumn column) public override string ToString() => ((IStoreStoredProcedure)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( + () => ((IStoreStoredProcedure)this).ToDebugString(), + () => ((IStoreStoredProcedure)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + /// IEnumerable IStoreStoredProcedure.StoredProcedures { diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs index ddfa4297778..4bbe6e9ce9a 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs @@ -51,6 +51,18 @@ public virtual StoreStoredProcedure StoredProcedure public override string ToString() => ((IStoreStoredProcedureParameter)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( + () => ((IStoreStoredProcedureParameter)this).ToDebugString(), + () => ((IStoreStoredProcedureParameter)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + /// IStoreStoredProcedure IStoreStoredProcedureParameter.StoredProcedure { diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureResultColumn.cs index f1f16053304..1e7211dda09 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureResultColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureResultColumn.cs @@ -18,7 +18,10 @@ public class StoreStoredProcedureResultColumn /// 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 StoreStoredProcedureResultColumn(string name, string type, StoreStoredProcedure storedProcedure) + public StoreStoredProcedureResultColumn( + string name, + string type, + StoreStoredProcedure storedProcedure) : base(name, type, storedProcedure) { } @@ -41,6 +44,18 @@ public virtual StoreStoredProcedure StoredProcedure public override string ToString() => ((IStoreStoredProcedureResultColumn)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( + () => ((IStoreStoredProcedureResultColumn)this).ToDebugString(), + () => ((IStoreStoredProcedureResultColumn)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + /// IStoreStoredProcedure IStoreStoredProcedureResultColumn.StoredProcedure { diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs index 6841e9ed6b4..8315ed32d63 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs @@ -12,14 +12,18 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; public class StoredProcedure : ConventionAnnotatable, IRuntimeStoredProcedure, IMutableStoredProcedure, IConventionStoredProcedure { - private readonly List _parameters = new(); - private readonly HashSet _parametersSet = new(); - private readonly List _resultColumns = new(); - private readonly HashSet _resultColumnsSet = new(); + private readonly List _parameters = new(); + private readonly Dictionary _currentValueParameters = new(); + private readonly Dictionary _originalValueParameters = new(); + private RowsAffectedStoredProcedureParameter? _rowsAffectedParameter; + private readonly List _resultColumns = new(); + private RowsAffectedStoredProcedureResultColumn? _rowsAffectedResultColumn; + private readonly Dictionary _propertyResultColumns = new(); private string? _schema; private string? _name; private InternalStoredProcedureBuilder? _builder; private bool _areTransactionsSuppressed; + private bool _areRowsAffectedReturned; private IStoreStoredProcedure? _storeStoredProcedure; private ConfigurationSource _configurationSource; @@ -350,7 +354,12 @@ public virtual string? Name var tableName = EntityType.GetTableName() ?? EntityType.GetDefaultTableName(); if (tableName == null) { - return null; + if (_configurationSource == ConfigurationSource.Convention) + { + return null; + } + + tableName = Uniquifier.Truncate(EntityType.ShortName(), EntityType.Model.GetMaxIdentifierLength()); } string? suffix; @@ -439,7 +448,7 @@ 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 @@ -457,7 +466,51 @@ public virtual bool SetAreTransactionsSuppressed(bool areTransactionsSuppressed, return areTransactionsSuppressed; } - /// + /// + /// 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 AreRowsAffectedReturned + { + get => _areRowsAffectedReturned; + set => SetAreRowsAffectedReturned(value); + } + + /// + /// 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 SetAreRowsAffectedReturned(bool areTransactionsSuppressed) + { + EnsureMutable(); + + if (ResultColumns.Any()) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureRowsAffectedReturnConflictingResultColumn( + ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); + } + + if (_rowsAffectedParameter != null) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter( + ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); + } + + _areRowsAffectedReturned = areTransactionsSuppressed; + + return areTransactionsSuppressed; + } + + /// + /// 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? GetAreTransactionsSuppressedConfigurationSource() => _areTransactionsSuppressedConfigurationSource; @@ -486,55 +539,193 @@ private static void UpdateOverrides( } } } - - /// - public virtual IReadOnlyList 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 IReadOnlyList Parameters { [DebuggerStepThrough] 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 CurrentValuePropertyStoredProcedureParameter? FindParameter(string propertyName) + => _currentValueParameters.TryGetValue(propertyName, out var parameter) + ? parameter + : null; - /// - public virtual bool ContainsParameter(string propertyName) - => _parametersSet.Contains(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 CurrentValuePropertyStoredProcedureParameter AddParameter(string propertyName) + { + if (_currentValueParameters.ContainsKey(propertyName)) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateParameter( + propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); + } - /// - public virtual bool AddParameter(string propertyName) + var parameter = new CurrentValuePropertyStoredProcedureParameter(this, propertyName); + _parameters.Add(parameter); + _currentValueParameters.Add(propertyName, parameter); + + return parameter; + } + + /// + /// 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 OriginalValuePropertyStoredProcedureParameter? FindOriginalValueParameter(string propertyName) + => _originalValueParameters.TryGetValue(propertyName, out var parameter) + ? parameter + : 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 OriginalValuePropertyStoredProcedureParameter AddOriginalValueParameter(string propertyName) { - if (!_parametersSet.Contains(propertyName)) + if (_originalValueParameters.ContainsKey(propertyName)) { - _parameters.Add(propertyName); - _parametersSet.Add(propertyName); + throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateOriginalValueParameter( + propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); + } - return true; + var parameter = new OriginalValuePropertyStoredProcedureParameter(this, propertyName); + _parameters.Add(parameter); + _originalValueParameters.Add(propertyName, parameter); + + return parameter; + } + + /// + /// 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 RowsAffectedStoredProcedureParameter? FindRowsAffectedParameter() + => _rowsAffectedParameter; + + /// + /// 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 RowsAffectedStoredProcedureParameter AddRowsAffectedParameter() + { + if (_rowsAffectedParameter != null + || _areRowsAffectedReturned) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter( + ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - return false; + var parameter = new RowsAffectedStoredProcedureParameter(this); + _parameters.Add(parameter); + _rowsAffectedParameter = parameter; + + return parameter; } - /// - public virtual IReadOnlyList ResultColumns + /// + /// 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 IReadOnlyList ResultColumns { [DebuggerStepThrough] get => _resultColumns; } + + /// + /// 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 PropertyStoredProcedureResultColumn? FindResultColumn(string propertyName) + => _propertyResultColumns.TryGetValue(propertyName, out var resultColumn) + ? resultColumn + : null; - /// - public virtual bool ContainsResultColumn(string propertyName) - => _resultColumnsSet.Contains(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 PropertyStoredProcedureResultColumn AddResultColumn(string propertyName) + { + if (_propertyResultColumns.ContainsKey(propertyName)) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateResultColumn( + propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); + } - /// - public virtual bool AddResultColumn(string propertyName) + if (_areRowsAffectedReturned) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureRowsAffectedReturnResultColumn( + propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); + } + + var resultColumn = new PropertyStoredProcedureResultColumn(this, propertyName); + _resultColumns.Add(resultColumn); + _propertyResultColumns.Add(propertyName, resultColumn); + + return resultColumn; + } + + /// + /// 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 RowsAffectedStoredProcedureResultColumn? FindRowsAffectedResultColumn() + => _rowsAffectedResultColumn; + + /// + /// 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 RowsAffectedStoredProcedureResultColumn AddRowsAffectedResultColumn() { - if (!_resultColumnsSet.Contains(propertyName)) + if (_rowsAffectedResultColumn != null + || _areRowsAffectedReturned) { - _resultColumns.Add(propertyName); - _resultColumnsSet.Add(propertyName); - - return true; + throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn( + ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - return false; + var resultColumn = new RowsAffectedStoredProcedureResultColumn(this); + _resultColumns.Add(resultColumn); + _rowsAffectedResultColumn = resultColumn; + + return resultColumn; } /// @@ -603,6 +794,62 @@ IStoreStoredProcedure IRuntimeStoredProcedure.StoreStoredProcedure get => _storeStoredProcedure!; set => _storeStoredProcedure = value; } + + /// + IEnumerable IReadOnlyStoredProcedure.Parameters + { + [DebuggerStepThrough] + get => Parameters; + } + + /// + IEnumerable IMutableStoredProcedure.Parameters + { + [DebuggerStepThrough] + get => Parameters; + } + + /// + IEnumerable IConventionStoredProcedure.Parameters + { + [DebuggerStepThrough] + get => Parameters.Cast(); + } + + /// + IEnumerable IStoredProcedure.Parameters + { + [DebuggerStepThrough] + get => Parameters.Cast(); + } + + /// + IEnumerable IReadOnlyStoredProcedure.ResultColumns + { + [DebuggerStepThrough] + get => ResultColumns; + } + + /// + IEnumerable IMutableStoredProcedure.ResultColumns + { + [DebuggerStepThrough] + get => ResultColumns; + } + + /// + IEnumerable IConventionStoredProcedure.ResultColumns + { + [DebuggerStepThrough] + get => ResultColumns.Cast(); + } + + /// + IEnumerable IStoredProcedure.ResultColumns + { + [DebuggerStepThrough] + get => ResultColumns.Cast(); + } /// [DebuggerStepThrough] @@ -616,17 +863,164 @@ IStoreStoredProcedure IRuntimeStoredProcedure.StoreStoredProcedure /// [DebuggerStepThrough] - string? IConventionStoredProcedure.AddParameter(string propertyName, bool fromDataAnnotation) - => AddParameter(propertyName) ? propertyName : null; + bool IConventionStoredProcedure.SetAreTransactionsSuppressed(bool areTransactionsSuppressed, bool fromDataAnnotation) + => SetAreTransactionsSuppressed( + areTransactionsSuppressed, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + bool IConventionStoredProcedure.SetAreRowsAffectedReturned(bool rowsAffectedReturned, bool fromDataAnnotation) + => SetAreRowsAffectedReturned(rowsAffectedReturned); + + /// + [DebuggerStepThrough] + IReadOnlyStoredProcedureParameter? IReadOnlyStoredProcedure.FindParameter(string propertyName) + => FindParameter(propertyName); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureParameter? IMutableStoredProcedure.FindParameter(string propertyName) + => FindParameter(propertyName); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameter? IConventionStoredProcedure.FindParameter(string propertyName) + => FindParameter(propertyName); + + /// + [DebuggerStepThrough] + IStoredProcedureParameter? IStoredProcedure.FindParameter(string propertyName) + => FindParameter(propertyName); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameter? IConventionStoredProcedure.AddParameter(string propertyName, bool fromDataAnnotation) + => AddParameter(propertyName); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureParameter IMutableStoredProcedure.AddParameter(string propertyName) + => AddParameter(propertyName); + + /// + [DebuggerStepThrough] + IReadOnlyStoredProcedureParameter? IReadOnlyStoredProcedure.FindOriginalValueParameter(string propertyName) + => FindOriginalValueParameter(propertyName); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureParameter? IMutableStoredProcedure.FindOriginalValueParameter(string propertyName) + => FindOriginalValueParameter(propertyName); /// [DebuggerStepThrough] - string? IConventionStoredProcedure.AddResultColumn(string propertyName, bool fromDataAnnotation) - => AddResultColumn(propertyName) ? propertyName : null; + IConventionStoredProcedureParameter? IConventionStoredProcedure.FindOriginalValueParameter(string propertyName) + => FindOriginalValueParameter(propertyName); + + /// + [DebuggerStepThrough] + IStoredProcedureParameter? IStoredProcedure.FindOriginalValueParameter(string propertyName) + => FindOriginalValueParameter(propertyName); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureParameter IMutableStoredProcedure.AddOriginalValueParameter(string propertyName) + => AddOriginalValueParameter(propertyName); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameter? IConventionStoredProcedure.AddOriginalValueParameter( + string propertyName, bool fromDataAnnotation) + => AddOriginalValueParameter(propertyName); + + /// + [DebuggerStepThrough] + IReadOnlyStoredProcedureParameter? IReadOnlyStoredProcedure.FindRowsAffectedParameter() + => FindRowsAffectedParameter(); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureParameter? IMutableStoredProcedure.FindRowsAffectedParameter() + => FindRowsAffectedParameter(); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameter? IConventionStoredProcedure.FindRowsAffectedParameter() + => FindRowsAffectedParameter(); + + /// + [DebuggerStepThrough] + IStoredProcedureParameter? IStoredProcedure.FindRowsAffectedParameter() + => FindRowsAffectedParameter(); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureParameter IMutableStoredProcedure.AddRowsAffectedParameter() + => AddRowsAffectedParameter(); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameter? IConventionStoredProcedure.AddRowsAffectedParameter(bool fromDataAnnotation) + => AddRowsAffectedParameter(); + + /// + [DebuggerStepThrough] + IReadOnlyStoredProcedureResultColumn? IReadOnlyStoredProcedure.FindResultColumn(string propertyName) + => FindResultColumn(propertyName); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureResultColumn? IMutableStoredProcedure.FindResultColumn(string propertyName) + => FindResultColumn(propertyName); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumn? IConventionStoredProcedure.FindResultColumn(string propertyName) + => FindResultColumn(propertyName); + + /// + [DebuggerStepThrough] + IStoredProcedureResultColumn? IStoredProcedure.FindResultColumn(string propertyName) + => FindResultColumn(propertyName); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureResultColumn IMutableStoredProcedure.AddResultColumn(string propertyName) + => AddResultColumn(propertyName); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumn? IConventionStoredProcedure.AddResultColumn( + string propertyName, bool fromDataAnnotation) + => AddResultColumn(propertyName); /// [DebuggerStepThrough] - bool IConventionStoredProcedure.SetAreTransactionsSuppressed(bool areTransactionsSuppressed, bool fromDataAnnotation) - => SetAreTransactionsSuppressed( - areTransactionsSuppressed, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IReadOnlyStoredProcedureResultColumn? IReadOnlyStoredProcedure.FindRowsAffectedResultColumn() + => FindRowsAffectedResultColumn(); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureResultColumn? IMutableStoredProcedure.FindRowsAffectedResultColumn() + => FindRowsAffectedResultColumn(); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumn? IConventionStoredProcedure.FindRowsAffectedResultColumn() + => FindRowsAffectedResultColumn(); + + /// + [DebuggerStepThrough] + IStoredProcedureResultColumn? IStoredProcedure.FindRowsAffectedResultColumn() + => FindRowsAffectedResultColumn(); + + /// + [DebuggerStepThrough] + IMutableStoredProcedureResultColumn IMutableStoredProcedure.AddRowsAffectedResultColumn() + => AddRowsAffectedResultColumn(); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumn? IConventionStoredProcedure.AddRowsAffectedResultColumn(bool fromDataAnnotation) + => AddRowsAffectedResultColumn(); } diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureMapping.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureMapping.cs index f6f38ca14f7..16baa9ecb62 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureMapping.cs @@ -21,11 +21,13 @@ public StoredProcedureMapping( IEntityType entityType, StoreStoredProcedure storeStoredProcedure, IStoredProcedure storedProcedure, + ITableMapping? tableMapping, bool includesDerivedTypes) : base(entityType, storeStoredProcedure, includesDerivedTypes) { StoredProcedure = storedProcedure; StoredProcedureIdentifier = storedProcedure.GetStoreIdentifier(); + TableMapping = tableMapping; } /// @@ -37,6 +39,9 @@ public virtual IStoreStoredProcedure StoreStoredProcedure /// public virtual StoreObjectIdentifier StoredProcedureIdentifier { get; } + + /// + public virtual ITableMapping? TableMapping { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -75,6 +80,18 @@ public virtual bool AddParameterMapping(IStoredProcedureParameterMapping paramet public override string ToString() => ((IStoredProcedureMapping)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( + () => ((IStoredProcedureMapping)this).ToDebugString(), + () => ((IStoredProcedureMapping)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + /// IEnumerable IStoredProcedureMapping.ResultColumnMappings { diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameterMapping.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameterMapping.cs index 6c69bc8382c..78bb03d3604 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameterMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameterMapping.cs @@ -19,11 +19,16 @@ public class StoredProcedureParameterMapping : ColumnMappingBase, IStoredProcedu /// public StoredProcedureParameterMapping( IProperty property, - StoreStoredProcedureParameter parameter, + IStoredProcedureParameter parameter, + StoreStoredProcedureParameter storeParameter, StoredProcedureMapping storedProcedureMapping) - : base(property, parameter, storedProcedureMapping) + : base(property, storeParameter, storedProcedureMapping) { + Parameter = parameter; } + + /// + public virtual IStoredProcedureParameter Parameter { get; } /// public virtual IStoredProcedureMapping StoredProcedureMapping @@ -47,8 +52,20 @@ protected override RelationalTypeMapping GetTypeMapping() public override string ToString() => ((IStoredProcedureParameterMapping)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( + () => ((IStoredProcedureParameterMapping)this).ToDebugString(), + () => ((IStoredProcedureParameterMapping)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + /// - IStoreStoredProcedureParameter IStoredProcedureParameterMapping.Parameter + IStoreStoredProcedureParameter IStoredProcedureParameterMapping.StoreParameter { [DebuggerStepThrough] get => (IStoreStoredProcedureParameter)Column; diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumnMapping.cs index c64a612185a..eab7c006067 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumnMapping.cs @@ -19,12 +19,19 @@ public class StoredProcedureResultColumnMapping : ColumnMappingBase, IStoredProc /// public StoredProcedureResultColumnMapping( IProperty property, - StoreStoredProcedureResultColumn column, + IStoredProcedureResultColumn resultColumn, + StoreStoredProcedureResultColumn storeResultColumn, StoredProcedureMapping storedProcedureMapping) - : base(property, column, storedProcedureMapping) + : base(property, storeResultColumn, storedProcedureMapping) { + ResultColumn = resultColumn; } + /// + /// Gets the associated stored procedure result column. + /// + public virtual IStoredProcedureResultColumn ResultColumn { get; } + /// public virtual IStoredProcedureMapping StoredProcedureMapping => (IStoredProcedureMapping)TableMapping; @@ -47,8 +54,20 @@ protected override RelationalTypeMapping GetTypeMapping() public override string ToString() => ((IStoredProcedureResultColumnMapping)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( + () => ((IStoredProcedureResultColumnMapping)this).ToDebugString(), + () => ((IStoredProcedureResultColumnMapping)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + /// - IStoreStoredProcedureResultColumn IStoredProcedureResultColumnMapping.Column + IStoreStoredProcedureResultColumn IStoredProcedureResultColumnMapping.StoreResultColumn { [DebuggerStepThrough] get => (IStoreStoredProcedureResultColumn)Column; diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index e2e520f71e3..09b3a35b107 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -92,11 +92,6 @@ public static class RelationalAnnotationNames /// public const string UpdateStoredProcedure = Prefix + "UpdateStoredProcedure"; - /// - /// The name for mapped function name annotations. - /// - public const string ParameterDirection = Prefix + "ParameterDirection"; - /// /// The name for mapped sql query annotations. /// diff --git a/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs b/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs index fcb5e845c37..662c79d8a6a 100644 --- a/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Data; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -13,10 +14,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// public class RuntimeStoredProcedure : AnnotatableBase, IRuntimeStoredProcedure { - private readonly List _parameters = new(); - private readonly List _resultColumns = new(); + private readonly List _parameters = new(); + private readonly List _resultColumns = new(); private readonly string? _schema; private readonly string _name; + private readonly bool _areRowsAffectedReturned; private readonly bool _areTransactionsSuppressed; private IStoreStoredProcedure? _storeStoredProcedure; @@ -26,16 +28,19 @@ public class RuntimeStoredProcedure : AnnotatableBase, IRuntimeStoredProcedure /// The mapped entity type. /// The name. /// The schema. + /// Whether this stored procedure returns the number of rows affected. /// Whether the automatic transactions are surpressed. public RuntimeStoredProcedure( RuntimeEntityType entityType, string name, string? schema, + bool areRowsAffectedReturned, bool areTransactionsSuppressed) { EntityType = entityType; _name = name; _schema = schema; + _areRowsAffectedReturned = areRowsAffectedReturned; _areTransactionsSuppressed = areTransactionsSuppressed; } @@ -47,19 +52,47 @@ public RuntimeStoredProcedure( /// /// Adds a new parameter mapped to the property with the given name. /// + /// The name of the parameter. + /// The direction. + /// Whether the parameter holds the rows affected. /// The name of the corresponding property. - public virtual void AddParameter(string propertyName) + /// Whether the parameter holds the original value. + public virtual RuntimeStoredProcedureParameter AddParameter( + string name, + ParameterDirection direction, + bool forRowsAffected, + string? propertyName, + bool? forOriginalValue) { - _parameters.Add(propertyName); + var parameter = new RuntimeStoredProcedureParameter( + this, + name, + direction, + forRowsAffected, + propertyName, + forOriginalValue); + _parameters.Add(parameter); + return parameter; } /// /// Adds a new column of the result for this stored procedure mapped to the property with the given name /// + /// The name of the result column. + /// Whether the column holds the rows affected. /// The name of the corresponding property. - public virtual void AddResultColumn(string propertyName) + public virtual RuntimeStoredProcedureResultColumn AddResultColumn( + string name, + bool forRowsAffected, + string? propertyName) { - _resultColumns.Add(propertyName); + var resultColumn = new RuntimeStoredProcedureResultColumn( + this, + name, + forRowsAffected, + propertyName); + _resultColumns.Add(resultColumn); + return resultColumn; } /// @@ -124,30 +157,88 @@ bool IReadOnlyStoredProcedure.AreTransactionsSuppressed [DebuggerStepThrough] get => _areTransactionsSuppressed; } + + /// + bool IReadOnlyStoredProcedure.AreRowsAffectedReturned + { + [DebuggerStepThrough] + get => _areRowsAffectedReturned; + } /// - IReadOnlyList IReadOnlyStoredProcedure.Parameters + IEnumerable IReadOnlyStoredProcedure.Parameters { [DebuggerStepThrough] get => _parameters; } + + /// + IEnumerable IStoredProcedure.Parameters + { + [DebuggerStepThrough] + get => _parameters; + } + + /// + IReadOnlyStoredProcedureParameter? IReadOnlyStoredProcedure.FindParameter(string propertyName) + => _parameters.FirstOrDefault((IReadOnlyStoredProcedureParameter p) + => p.ForOriginalValue == false && p.PropertyName == propertyName); + + /// + [DebuggerStepThrough] + IStoredProcedureParameter? IStoredProcedure.FindParameter(string propertyName) + => (IStoredProcedureParameter?)((IReadOnlyStoredProcedure)this).FindParameter(propertyName); + + /// + IReadOnlyStoredProcedureParameter? IReadOnlyStoredProcedure.FindOriginalValueParameter(string propertyName) + => _parameters.FirstOrDefault((IReadOnlyStoredProcedureParameter p) + => p.ForOriginalValue == true && p.PropertyName == propertyName); /// - bool IReadOnlyStoredProcedure.ContainsParameter(string propertyName) - => _parameters.Contains(propertyName); + IStoredProcedureParameter? IStoredProcedure.FindOriginalValueParameter(string propertyName) + => (IStoredProcedureParameter?)((IReadOnlyStoredProcedure)this).FindOriginalValueParameter(propertyName); + + /// + IReadOnlyStoredProcedureParameter? IReadOnlyStoredProcedure.FindRowsAffectedParameter() + => _parameters.FirstOrDefault((IStoredProcedureParameter p) + => p.ForRowsAffected); + + /// + IStoredProcedureParameter? IStoredProcedure.FindRowsAffectedParameter() + => (IStoredProcedureParameter?)((IReadOnlyStoredProcedure)this).FindRowsAffectedParameter(); /// - IReadOnlyList IReadOnlyStoredProcedure.ResultColumns + IEnumerable IReadOnlyStoredProcedure.ResultColumns + { + [DebuggerStepThrough] + get => _resultColumns; + } + + /// + IEnumerable IStoredProcedure.ResultColumns { [DebuggerStepThrough] get => _resultColumns; } /// - bool IReadOnlyStoredProcedure.ContainsResultColumn(string propertyName) - => _resultColumns.Contains(propertyName); + IReadOnlyStoredProcedureResultColumn? IReadOnlyStoredProcedure.FindResultColumn(string propertyName) + => _resultColumns.FirstOrDefault((IReadOnlyStoredProcedureResultColumn c) + => c.PropertyName == propertyName); + + /// + IStoredProcedureResultColumn? IStoredProcedure.FindResultColumn(string propertyName) + => (IStoredProcedureResultColumn?)((IReadOnlyStoredProcedure)this).FindResultColumn(propertyName); + /// + IReadOnlyStoredProcedureResultColumn? IReadOnlyStoredProcedure.FindRowsAffectedResultColumn() + => _resultColumns.FirstOrDefault((IReadOnlyStoredProcedureResultColumn c) + => c.ForRowsAffected); + /// + IStoredProcedureResultColumn? IStoredProcedure.FindRowsAffectedResultColumn() + => (IStoredProcedureResultColumn?)((IReadOnlyStoredProcedure)this).FindRowsAffectedResultColumn(); + /// IStoreStoredProcedure IStoredProcedure.StoreStoredProcedure { diff --git a/src/EFCore.Relational/Metadata/RuntimeStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/RuntimeStoredProcedureParameter.cs new file mode 100644 index 00000000000..786611dd15d --- /dev/null +++ b/src/EFCore.Relational/Metadata/RuntimeStoredProcedureParameter.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// Represents a stored procedure parameter. +/// +public class RuntimeStoredProcedureParameter : AnnotatableBase, IRuntimeStoredProcedureParameter +{ + private IStoreStoredProcedureParameter? _storeParameter; + private readonly string? _propertyName; + private readonly bool _forRowsAffected; + private readonly bool? _forOriginalValue; + private readonly string _name; + private readonly ParameterDirection _direction; + + /// + /// 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 RuntimeStoredProcedureParameter( + RuntimeStoredProcedure storedProcedure, + string name, + ParameterDirection direction, + bool forRowsAffected, + string? propertyName, + bool? forOriginalValue) + { + StoredProcedure = storedProcedure; + _propertyName = propertyName; + _forOriginalValue = forOriginalValue; + _forRowsAffected = forRowsAffected; + _name = name; + _direction = direction; + } + + /// + /// Gets the stored procedure to which this parameter belongs. + /// + public virtual RuntimeStoredProcedure StoredProcedure { 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. + /// + public override string ToString() + => ((IStoredProcedureParameter)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( + () => ((IStoredProcedureParameter)this).ToDebugString(), + () => ((IStoredProcedureParameter)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyStoredProcedure IReadOnlyStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IStoredProcedure IStoredProcedureParameter.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + string IReadOnlyStoredProcedureParameter.Name + { + [DebuggerStepThrough] + get => _name; + } + + /// + string? IReadOnlyStoredProcedureParameter.PropertyName + { + [DebuggerStepThrough] + get => _propertyName; + } + + /// + ParameterDirection IReadOnlyStoredProcedureParameter.Direction + { + [DebuggerStepThrough] + get => _direction; + } + + /// + bool? IReadOnlyStoredProcedureParameter.ForOriginalValue + { + [DebuggerStepThrough] + get => _forOriginalValue; + } + + /// + bool IReadOnlyStoredProcedureParameter.ForRowsAffected + { + [DebuggerStepThrough] + get => _forRowsAffected; + } + + /// + IStoreStoredProcedureParameter IStoredProcedureParameter.StoreParameter + { + [DebuggerStepThrough] + get => _storeParameter!; + } + + /// + IStoreStoredProcedureParameter IRuntimeStoredProcedureParameter.StoreParameter + { + [DebuggerStepThrough] + get => _storeParameter!; + set => _storeParameter = value; + } +} diff --git a/src/EFCore.Relational/Metadata/RuntimeStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/RuntimeStoredProcedureResultColumn.cs new file mode 100644 index 00000000000..cb2aedc7621 --- /dev/null +++ b/src/EFCore.Relational/Metadata/RuntimeStoredProcedureResultColumn.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// Represents a stored procedure result column. +/// +public class RuntimeStoredProcedureResultColumn : AnnotatableBase, IRuntimeStoredProcedureResultColumn +{ + private IStoreStoredProcedureResultColumn? _storeResultColumn; + private readonly string? _propertyName; + private readonly bool _forRowsAffected; + private readonly string _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. + /// + [EntityFrameworkInternal] + public RuntimeStoredProcedureResultColumn( + RuntimeStoredProcedure storedProcedure, + string name, + bool forRowsAffected, + string? propertyName) + { + StoredProcedure = storedProcedure; + _propertyName = propertyName; + _forRowsAffected = forRowsAffected; + _name = name; + } + + /// + /// Gets the stored procedure to which this parameter belongs. + /// + public virtual RuntimeStoredProcedure StoredProcedure { 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. + /// + public override string ToString() + => ((IStoredProcedureResultColumn)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( + () => ((IStoredProcedureResultColumn)this).ToDebugString(), + () => ((IStoredProcedureResultColumn)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyStoredProcedure IReadOnlyStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + IStoredProcedure IStoredProcedureResultColumn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } + + /// + string IReadOnlyStoredProcedureResultColumn.Name + { + [DebuggerStepThrough] + get => _name; + } + + /// + string? IReadOnlyStoredProcedureResultColumn.PropertyName + { + [DebuggerStepThrough] + get => _propertyName; + } + + /// + bool IReadOnlyStoredProcedureResultColumn.ForRowsAffected + { + [DebuggerStepThrough] + get => _forRowsAffected; + } + + /// + IStoreStoredProcedureResultColumn IStoredProcedureResultColumn.StoreResultColumn + { + [DebuggerStepThrough] + get => _storeResultColumn!; + } + + /// + IStoreStoredProcedureResultColumn IRuntimeStoredProcedureResultColumn.StoreResultColumn + { + [DebuggerStepThrough] + get => _storeResultColumn!; + set => _storeResultColumn = value; + } +} diff --git a/src/EFCore.Relational/Metadata/RuntimeTrigger.cs b/src/EFCore.Relational/Metadata/RuntimeTrigger.cs index e5ad7097b4b..41397d4325a 100644 --- a/src/EFCore.Relational/Metadata/RuntimeTrigger.cs +++ b/src/EFCore.Relational/Metadata/RuntimeTrigger.cs @@ -12,11 +12,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata; public class RuntimeTrigger : AnnotatableBase, ITrigger { /// - /// 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. + /// Initializes a new instance of the class. /// + /// The entity type. + /// The name in the model. + /// The name in the database. + /// The name of the table. + /// The schema of the table. public RuntimeTrigger( RuntimeEntityType entityType, string modelName, @@ -33,10 +35,8 @@ public RuntimeTrigger( /// public virtual string ModelName { get; } - - /// - /// Gets the database name of the trigger. - /// + + /// public virtual string Name { get; } /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 2ce1e55662f..2edd066765a 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1237,6 +1237,46 @@ public static string StoredProcedureDeleteNonKeyProperty(object? entityType, obj GetString("StoredProcedureDeleteNonKeyProperty", nameof(entityType), nameof(property), nameof(sproc)), entityType, property, sproc); + /// + /// The original value parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because original value another parameter for this property already exists. + /// + public static string StoredProcedureDuplicateOriginalValueParameter(object? property, object? sproc) + => string.Format( + GetString("StoredProcedureDuplicateOriginalValueParameter", nameof(property), nameof(sproc)), + property, sproc); + + /// + /// The parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because another parameter for this property already exists. + /// + public static string StoredProcedureDuplicateParameter(object? property, object? sproc) + => string.Format( + GetString("StoredProcedureDuplicateParameter", nameof(property), nameof(sproc)), + property, sproc); + + /// + /// The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because another result column for this property already exists. + /// + public static string StoredProcedureDuplicateResultColumn(object? property, object? sproc) + => string.Format( + GetString("StoredProcedureDuplicateResultColumn", nameof(property), nameof(sproc)), + property, sproc); + + /// + /// The rows affected parameter cannot be added to the stored procedure '{sproc}' because a rows affected parameter for this property already exists or the rows affected are being returned. + /// + public static string StoredProcedureDuplicateRowsAffectedParameter(object? sproc) + => string.Format( + GetString("StoredProcedureDuplicateRowsAffectedParameter", nameof(sproc)), + sproc); + + /// + /// The rows affected result column cannot be added to the stored procedure '{sproc}' because a rows affected parameter for this property exists or the rows affected are already being returned. + /// + public static string StoredProcedureDuplicateRowsAffectedResultColumn(object? sproc) + => string.Format( + GetString("StoredProcedureDuplicateRowsAffectedResultColumn", nameof(sproc)), + sproc); + /// /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the store-generated properties {properties} are not mapped to any output parameter or result column. /// @@ -1277,6 +1317,22 @@ public static string StoredProcedureOverrideMismatch(object? propertySpecificati GetString("StoredProcedureOverrideMismatch", nameof(propertySpecification), nameof(sproc)), propertySpecification, sproc); + /// + /// '{facet}' cannot be configured for the parameter '{parameter}' of the stored procedure '{sproc}'. + /// + public static string StoredProcedureParameterInvalidConfiguration(object? facet, object? parameter, object? sproc) + => string.Format( + GetString("StoredProcedureParameterInvalidConfiguration", nameof(facet), nameof(parameter), nameof(sproc)), + facet, parameter, sproc); + + /// + /// Unsupported direction '{direction}' was specified for the parameter '{parameter}' of the stored procedure '{sproc}'. + /// + public static string StoredProcedureParameterInvalidDirection(object? direction, object? parameter, object? sproc) + => string.Format( + GetString("StoredProcedureParameterInvalidDirection", nameof(direction), nameof(parameter), nameof(sproc)), + direction, parameter, sproc); + /// /// No property named '{property}' found on the entity type '{entityType}' corresponding to the parameter on the stored procedure '{sproc}' /// @@ -1317,6 +1373,30 @@ public static string StoredProcedureResultColumnNotGenerated(object? entityType, GetString("StoredProcedureResultColumnNotGenerated", nameof(entityType), nameof(property), nameof(sproc)), entityType, property, sproc); + /// + /// TThe stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter for this property already exists. + /// + public static string StoredProcedureRowsAffectedReturnConflictingParameter(object? sproc) + => string.Format( + GetString("StoredProcedureRowsAffectedReturnConflictingParameter", nameof(sproc)), + sproc); + + /// + /// The stored procedure '{sproc}' cannot be configured to return the rows affected because at least one result column was already added. + /// + public static string StoredProcedureRowsAffectedReturnConflictingResultColumn(object? sproc) + => string.Format( + GetString("StoredProcedureRowsAffectedReturnConflictingResultColumn", nameof(sproc)), + sproc); + + /// + /// The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because rows affected are being returned. + /// + public static string StoredProcedureRowsAffectedReturnResultColumn(object? property, object? sproc) + => string.Format( + GetString("StoredProcedureRowsAffectedReturnResultColumn", nameof(property), nameof(sproc)), + 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. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index cb446cd6249..a58f30607f7 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -864,6 +864,21 @@ The property '{entityType}.{property}' is mapped to a parameter of the stored procedure '{sproc}', but only concurrency token and key properties are supported for Delete stored procedures. + + The original value parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because original value another parameter for this property already exists. + + + The parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because another parameter for this property already exists. + + + The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because another result column for this property already exists. + + + The rows affected parameter cannot be added to the stored procedure '{sproc}' because a rows affected parameter for this property already exists or the rows affected are being returned. + + + The rows affected result column cannot be added to the stored procedure '{sproc}' because a rows affected parameter for this property exists or the rows affected are already being returned. + The entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the store-generated properties {properties} are not mapped to any output parameter or result column. @@ -879,6 +894,12 @@ 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}'. + + '{facet}' cannot be configured for the parameter '{parameter}' of the stored procedure '{sproc}'. + + + Unsupported direction '{direction}' was specified for the parameter '{parameter}' of the stored procedure '{sproc}'. + No property named '{property}' found on the entity type '{entityType}' corresponding to the parameter on the stored procedure '{sproc}' @@ -894,6 +915,15 @@ The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is not configured as store-generated. + + TThe stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter for this property already exists. + + + The stored procedure '{sproc}' cannot be configured to return the rows affected because at least one result column was already added. + + + The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because rows affected are being returned. + 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. diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs index 19d6c35ca06..56e1ba5eb72 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - - // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index 8b26227b643..d6697ef782d 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -382,7 +382,8 @@ public static bool IsStrictlyDerivedFrom(this EntityType entityType, IReadOnlyEn public static IEnumerable FindDerivedNavigations( this IReadOnlyEntityType entityType, string navigationName) - => entityType.GetDerivedNavigations().Where(navigation => navigationName == navigation.Name); + => entityType.GetDerivedTypes().Select(t => t.FindDeclaredNavigation(navigationName)!) + .Where(n => n != null); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index f65ae0b71ed..012e9d5e58f 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -83,7 +83,6 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.IsFixedLength, RelationalAnnotationNames.Collation, RelationalAnnotationNames.IsStored, - RelationalAnnotationNames.ParameterDirection, RelationalAnnotationNames.TpcMappingStrategy, RelationalAnnotationNames.TphMappingStrategy, RelationalAnnotationNames.TptMappingStrategy, diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 9786af15cd1..768c8ccfab6 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -2516,7 +2516,6 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba StoreObjectIdentifier.InsertStoredProcedure(""Derived_Insert"", ""TPC""), true, ""DerivedId""); - idDerivedInsert.AddAnnotation(""foo"", ""bar3""); overrides.Add(StoreObjectIdentifier.InsertStoredProcedure(""Derived_Insert"", ""TPC""), idDerivedInsert); var idPrincipalBaseView = new RuntimeRelationalPropertyOverrides( id, @@ -2525,14 +2524,6 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba null); idPrincipalBaseView.AddAnnotation(""foo"", ""bar2""); overrides.Add(StoreObjectIdentifier.View(""PrincipalBaseView"", ""TPC""), idPrincipalBaseView); - var idPrincipalBaseInsert = new RuntimeRelationalPropertyOverrides( - id, - StoreObjectIdentifier.InsertStoredProcedure(""PrincipalBase_Insert"", ""TPC""), - true, - ""BaseId""); - idPrincipalBaseInsert.AddAnnotation(""foo"", ""bar""); - idPrincipalBaseInsert.AddAnnotation(""Relational:ParameterDirection"", ParameterDirection.Output); - overrides.Add(StoreObjectIdentifier.InsertStoredProcedure(""PrincipalBase_Insert"", ""TPC""), idPrincipalBaseInsert); id.AddAnnotation(""Relational:RelationalOverrides"", overrides); id.AddAnnotation(""Relational:DefaultValueSql"", ""NEXT VALUE FOR [TPC].[PrincipalBaseSequence]""); @@ -2601,11 +2592,16 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) runtimeEntityType, ""PrincipalBase_Insert"", ""TPC"", + false, true); - insertSproc.AddParameter(""PrincipalBaseId""); - insertSproc.AddParameter(""PrincipalDerivedId""); - insertSproc.AddParameter(""Id""); + var principalBaseId = insertSproc.AddParameter( + ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", true); + var principalDerivedId = insertSproc.AddParameter( + ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", true); + var baseId = insertSproc.AddParameter( + ""BaseId"", ParameterDirection.Output, false, ""Id"", true); + baseId.AddAnnotation(""foo"", ""bar""); insertSproc.AddAnnotation(""foo"", ""bar1""); runtimeEntityType.AddAnnotation(""Relational:InsertStoredProcedure"", insertSproc); @@ -2613,20 +2609,26 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) runtimeEntityType, ""PrincipalBase_Delete"", ""TPC"", + false, false); - deleteSproc.AddParameter(""Id""); + var id = deleteSproc.AddParameter( + ""Id"", ParameterDirection.Input, false, ""Id"", true); runtimeEntityType.AddAnnotation(""Relational:DeleteStoredProcedure"", deleteSproc); var updateSproc = new RuntimeStoredProcedure( runtimeEntityType, ""PrincipalBase_Update"", ""TPC"", + false, false); - updateSproc.AddParameter(""PrincipalBaseId""); - updateSproc.AddParameter(""PrincipalDerivedId""); - updateSproc.AddParameter(""Id""); + var principalBaseId0 = updateSproc.AddParameter( + ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", true); + var principalDerivedId0 = updateSproc.AddParameter( + ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", true); + var id0 = updateSproc.AddParameter( + ""Id"", ParameterDirection.Input, false, ""Id"", true); runtimeEntityType.AddAnnotation(""Relational:UpdateStoredProcedure"", updateSproc); runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); @@ -2648,6 +2650,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) c => AssertFileContents( "PrincipalDerivedEntityType.cs", @"// using System; +using System.Data; using System.Reflection; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -2676,31 +2679,42 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) runtimeEntityType, ""Derived_Insert"", ""TPC"", + false, false); - insertSproc.AddParameter(""PrincipalBaseId""); - insertSproc.AddParameter(""PrincipalDerivedId""); - insertSproc.AddResultColumn(""Id""); + var principalBaseId = insertSproc.AddParameter( + ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", true); + var principalDerivedId = insertSproc.AddParameter( + ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", true); + var derivedId = insertSproc.AddResultColumn( + ""DerivedId"", false, ""Id""); + derivedId.AddAnnotation(""foo"", ""bar3""); runtimeEntityType.AddAnnotation(""Relational:InsertStoredProcedure"", insertSproc); var deleteSproc = new RuntimeStoredProcedure( runtimeEntityType, ""Derived_Delete"", ""TPC"", + false, false); - deleteSproc.AddParameter(""Id""); + var id = deleteSproc.AddParameter( + ""Id"", ParameterDirection.Input, false, ""Id"", true); runtimeEntityType.AddAnnotation(""Relational:DeleteStoredProcedure"", deleteSproc); var updateSproc = new RuntimeStoredProcedure( runtimeEntityType, ""Derived_Update"", ""Derived"", + false, false); - updateSproc.AddParameter(""PrincipalBaseId""); - updateSproc.AddParameter(""PrincipalDerivedId""); - updateSproc.AddParameter(""Id""); + var principalBaseId0 = updateSproc.AddParameter( + ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", true); + var principalDerivedId0 = updateSproc.AddParameter( + ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", true); + var id0 = updateSproc.AddParameter( + ""Id"", ParameterDirection.Input, false, ""Id"", true); runtimeEntityType.AddAnnotation(""Relational:UpdateStoredProcedure"", updateSproc); runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); @@ -2740,33 +2754,37 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) var insertSproc = principalBase.GetInsertStoredProcedure()!; Assert.Equal("PrincipalBase_Insert", insertSproc.Name); Assert.Equal("TPC", insertSproc.Schema); - Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId", "Id" }, insertSproc.Parameters); + Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId", "Id" }, insertSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(insertSproc.ResultColumns); Assert.True(insertSproc.AreTransactionsSuppressed); + Assert.False(insertSproc.AreRowsAffectedReturned); Assert.Equal("bar1", insertSproc["foo"]); Assert.Same(principalBase, insertSproc.EntityType); - Assert.Equal("BaseId", id.GetColumnName(StoreObjectIdentifier.Create(principalBase, StoreObjectType.InsertStoredProcedure).Value)); - Assert.Equal("bar", - id.FindOverrides(StoreObjectIdentifier.Create(principalBase, StoreObjectType.InsertStoredProcedure).Value)["foo"]); + Assert.Equal("BaseId", insertSproc.Parameters.Last().Name); + Assert.Equal("bar", insertSproc.Parameters.Last()["foo"]); + Assert.Null(id.FindOverrides(StoreObjectIdentifier.Create(principalBase, StoreObjectType.InsertStoredProcedure).Value)); var updateSproc = principalBase.GetUpdateStoredProcedure()!; Assert.Equal("PrincipalBase_Update", updateSproc.Name); Assert.Equal("TPC", updateSproc.Schema); - Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId", "Id" }, updateSproc.Parameters); + Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId", "Id" }, updateSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(updateSproc.ResultColumns); Assert.False(updateSproc.AreTransactionsSuppressed); + Assert.False(updateSproc.AreRowsAffectedReturned); Assert.Empty(updateSproc.GetAnnotations()); Assert.Same(principalBase, updateSproc.EntityType); - Assert.Equal("Id", id.GetColumnName(StoreObjectIdentifier.Create(principalBase, StoreObjectType.UpdateStoredProcedure).Value)); + Assert.Equal("Id", updateSproc.Parameters.Last().Name); Assert.Null(id.FindOverrides(StoreObjectIdentifier.Create(principalBase, StoreObjectType.UpdateStoredProcedure).Value)); var deleteSproc = principalBase.GetDeleteStoredProcedure()!; Assert.Equal("PrincipalBase_Delete", deleteSproc.Name); Assert.Equal("TPC", deleteSproc.Schema); - Assert.Equal(new[] { "Id" }, deleteSproc.Parameters); + Assert.Equal(new[] { "Id" }, deleteSproc.Parameters.Select(p => p.Name)); Assert.Empty(deleteSproc.ResultColumns); + Assert.False(deleteSproc.AreTransactionsSuppressed); + Assert.False(deleteSproc.AreRowsAffectedReturned); Assert.Same(principalBase, deleteSproc.EntityType); - Assert.Equal("Id", id.GetColumnName(StoreObjectIdentifier.Create(principalBase, StoreObjectType.DeleteStoredProcedure).Value)); + Assert.Equal("Id", deleteSproc.Parameters.Last().Name); Assert.Null(id.FindOverrides(StoreObjectIdentifier.Create(principalBase, StoreObjectType.DeleteStoredProcedure).Value)); Assert.Equal("PrincipalBase", principalBase.GetDiscriminatorValue()); @@ -2791,34 +2809,35 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) insertSproc = principalDerived.GetInsertStoredProcedure()!; Assert.Equal("Derived_Insert", insertSproc.Name); Assert.Equal("TPC", insertSproc.Schema); - Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId" }, insertSproc.Parameters); - Assert.Equal(new[] { "Id" }, insertSproc.ResultColumns); + Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId" }, insertSproc.Parameters.Select(p => p.PropertyName)); + Assert.Equal(new[] { "Id" }, insertSproc.ResultColumns.Select(p => p.PropertyName)); Assert.False(insertSproc.AreTransactionsSuppressed); Assert.Null(insertSproc["foo"]); Assert.Same(principalDerived, insertSproc.EntityType); + Assert.Equal("DerivedId", insertSproc.ResultColumns.Last().Name); Assert.Equal("DerivedId", id.GetColumnName(StoreObjectIdentifier.Create(principalDerived, StoreObjectType.InsertStoredProcedure).Value)); - Assert.Equal("bar3", - id.FindOverrides(StoreObjectIdentifier.Create(principalDerived, StoreObjectType.InsertStoredProcedure).Value)["foo"]); + Assert.Equal("bar3", insertSproc.ResultColumns.Last()["foo"]); + Assert.Null(id.FindOverrides(StoreObjectIdentifier.Create(principalDerived, StoreObjectType.InsertStoredProcedure).Value)["foo"]); updateSproc = principalDerived.GetUpdateStoredProcedure()!; Assert.Equal("Derived_Update", updateSproc.Name); Assert.Equal("Derived", updateSproc.Schema); - Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId", "Id" }, updateSproc.Parameters); + Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId", "Id" }, updateSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(updateSproc.ResultColumns); Assert.False(updateSproc.AreTransactionsSuppressed); Assert.Empty(updateSproc.GetAnnotations()); Assert.Same(principalDerived, updateSproc.EntityType); - Assert.Equal("Id", id.GetColumnName(StoreObjectIdentifier.Create(principalDerived, StoreObjectType.UpdateStoredProcedure).Value)); + Assert.Equal("Id", updateSproc.Parameters.Last().Name); Assert.Null(id.FindOverrides(StoreObjectIdentifier.Create(principalDerived, StoreObjectType.UpdateStoredProcedure).Value)); deleteSproc = principalDerived.GetDeleteStoredProcedure()!; Assert.Equal("Derived_Delete", deleteSproc.Name); Assert.Equal("TPC", deleteSproc.Schema); - Assert.Equal(new[] { "Id" }, deleteSproc.Parameters); + Assert.Equal(new[] { "Id" }, deleteSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(deleteSproc.ResultColumns); Assert.False(deleteSproc.AreTransactionsSuppressed); Assert.Same(principalDerived, deleteSproc.EntityType); - Assert.Equal("Id", id.GetColumnName(StoreObjectIdentifier.Create(principalDerived, StoreObjectType.DeleteStoredProcedure).Value)); + Assert.Equal("Id", deleteSproc.Parameters.Last().Name); Assert.Null(id.FindOverrides(StoreObjectIdentifier.Create(principalDerived, StoreObjectType.DeleteStoredProcedure).Value)); Assert.Equal("PrincipalDerived>", principalDerived.GetDiscriminatorValue()); diff --git a/test/EFCore.Relational.Specification.Tests/Query/SimpleQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/SimpleQueryRelationalTestBase.cs index 5331a46ad70..e0cc9149aa5 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/SimpleQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/SimpleQueryRelationalTestBase.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.ComponentModel.DataAnnotations.Schema; using NameSpace1; diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 75e0aa1899f..a3e7b2df0b5 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -2879,8 +2879,9 @@ public virtual void Detects_unmatched_stored_procedure_result_columns_in_TPT() public virtual void Detects_InsertUsingStoredProcedure_without_a_name() { var modelBuilder = CreateConventionModelBuilder(); - modelBuilder.Entity().InsertUsingStoredProcedure(s => { }).UseTpcMappingStrategy(); + var entityType = (IConventionEntityType)modelBuilder.Entity().UseTpcMappingStrategy().Metadata; modelBuilder.Entity>(); + entityType.SetInsertStoredProcedure(); VerifyError( RelationalStrings.StoredProcedureNoName(nameof(Abstract), "InsertStoredProcedure"), diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 827b0e86125..2c90b649644 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -99,6 +99,37 @@ public void Can_use_relational_model_with_sprocs(bool mapToTables, Mapping mappi Assert.True(model.Model.GetEntityTypes().All(et => !et.GetViewMappings().Any())); AssertDefaultMappings(model, mapping); + AssertTables(model, mapping); + AssertSprocs(model, mapping, mappedToTables: true); + } + + [ConditionalTheory] + [InlineData(Mapping.TPH)] + [InlineData(Mapping.TPT)] + [InlineData(Mapping.TPC)] + public void Can_use_relational_model_with_sprocs_and_views(Mapping mapping) + { + var model = CreateTestModel(mapToViews: true, mapToSprocs: true, mapping: mapping); + + Assert.Equal(11, model.Model.GetEntityTypes().Count()); + + Assert.Equal( + mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Views.Count()); + + Assert.Equal(mapping switch + { + Mapping.TPC => 24, + Mapping.TPH => 18, + _ => 27 + }, model.StoredProcedures.Count()); + + AssertDefaultMappings(model, mapping); + AssertViews(model, mapping); AssertSprocs(model, mapping); } @@ -1019,7 +1050,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) } } - private static void AssertSprocs(IRelationalModel model, Mapping mapping) + private static void AssertSprocs(IRelationalModel model, Mapping mapping, bool mappedToTables = false) { var orderType = model.Model.FindEntityType(typeof(Order)); var orderInsertMapping = orderType.GetInsertStoredProcedureMappings().Single(); @@ -1061,17 +1092,17 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) Assert.Equal("default_datetime_mapping", orderDateInsertMapping.TypeMapping.StoreType); Assert.Same(orderInsertMapping, orderDateInsertMapping.TableMapping); - var orderDateColumn = orderDateInsertMapping.Parameter; - Assert.Same(orderDateInsertMapping.Parameter, orderDateInsertMapping.Column); - Assert.Same(orderDateColumn, ordersInsertSproc.FindParameter("OrderDate")); - Assert.Same(orderDateColumn, ordersInsertSproc.FindParameter(orderDate)); - Assert.Equal("OrderDate", orderDateColumn.Name); - Assert.Equal("default_datetime_mapping", orderDateColumn.StoreType); - Assert.False(orderDateColumn.IsNullable); - Assert.Equal(ParameterDirection.Input, orderDateColumn.Direction); - Assert.Same(ordersInsertSproc, orderDateColumn.StoredProcedure); - Assert.Same(orderDateColumn.StoredProcedure, orderDateColumn.Table); - Assert.Same(orderDateInsertMapping, orderDateColumn.FindParameterMapping(orderType)); + var orderDateParameter = orderDateInsertMapping.StoreParameter; + Assert.Same(orderDateInsertMapping.StoreParameter, orderDateInsertMapping.Column); + Assert.Same(orderDateParameter, ordersInsertSproc.FindParameter("OrderDate")); + Assert.Same(orderDateParameter, ordersInsertSproc.FindParameter(orderDate)); + Assert.Equal("OrderDate", orderDateParameter.Name); + Assert.Equal("default_datetime_mapping", orderDateParameter.StoreType); + Assert.False(orderDateParameter.IsNullable); + Assert.Equal(ParameterDirection.Input, orderDateParameter.Direction); + Assert.Same(ordersInsertSproc, orderDateParameter.StoredProcedure); + Assert.Same(orderDateParameter.StoredProcedure, orderDateParameter.Table); + Assert.Same(orderDateInsertMapping, orderDateParameter.FindParameterMapping(orderType)); var abstractBaseType = model.Model.FindEntityType(typeof(AbstractBase)); var abstractCustomerType = model.Model.FindEntityType(typeof(AbstractCustomer)); @@ -1159,7 +1190,7 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) Assert.Empty(billingAddressDeleteSproc.ResultColumns.Select(m => m.Name)); - Assert.Equal(new[] { orderDate }, orderDateColumn.PropertyMappings.Select(m => m.Property)); + Assert.Equal(new[] { orderDate }, orderDateParameter.PropertyMappings.Select(m => m.Property)); var specialCustomerInsertSproc = specialCustomerType.GetInsertStoredProcedureMappings().Last().StoreStoredProcedure; @@ -1316,7 +1347,7 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) Assert.Null(specialCustomerType.GetInsertStoredProcedureMappings().First().IsSplitEntityTypePrincipal); Assert.False(specialCustomerType.GetInsertStoredProcedureMappings().First().IncludesDerivedTypes); Assert.Null(specialCustomerType.GetInsertStoredProcedureMappings().Last().IsSplitEntityTypePrincipal); - Assert.True(specialCustomerType.GetTableMappings().Last().IncludesDerivedTypes); + Assert.True(specialCustomerType.GetInsertStoredProcedureMappings().Last().IncludesDerivedTypes); Assert.Equal("SpecialCustomer_Insert", specialCustomerInsertSproc.Name); Assert.Single(specialCustomerInsertSproc.ResultColumns); @@ -1390,7 +1421,7 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) var idPropertyUpdateParameter = baseUpdateSproc.FindParameter(idProperty)!; var idPropertyUpdateParameterMapping = idProperty.GetUpdateStoredProcedureParameterMappings().First(); Assert.Same(idPropertyUpdateParameter, baseUpdateSproc.FindParameter("UpdateId")); - Assert.Same(idPropertyUpdateParameter, idPropertyUpdateParameterMapping.Parameter); + Assert.Same(idPropertyUpdateParameter, idPropertyUpdateParameterMapping.StoreParameter); Assert.Equal("UpdateId", idPropertyUpdateParameter.Name); Assert.Equal("default_int_mapping", idPropertyUpdateParameter.StoreType); Assert.Equal(ParameterDirection.Input, idPropertyUpdateParameter.Direction); @@ -1419,7 +1450,7 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) var idPropertyDeleteParameter = baseDeleteSproc.FindParameter(idProperty)!; var idPropertyDeleteParameterMapping = idProperty.GetDeleteStoredProcedureParameterMappings().First(); Assert.Same(idPropertyDeleteParameter, baseDeleteSproc.FindParameter("DeleteId")); - Assert.Same(idPropertyDeleteParameter, idPropertyDeleteParameterMapping.Parameter); + Assert.Same(idPropertyDeleteParameter, idPropertyDeleteParameterMapping.StoreParameter); Assert.Equal("DeleteId", idPropertyDeleteParameter.Name); Assert.Equal("default_int_mapping", idPropertyDeleteParameter.StoreType); Assert.Equal(ParameterDirection.Input, idPropertyDeleteParameter.Direction); @@ -1638,7 +1669,7 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) var idPropertyUpdateParameter = baseUpdateSproc.FindParameter(idProperty)!; var idPropertyUpdateParameterMapping = idProperty.GetUpdateStoredProcedureParameterMappings().First(); Assert.Same(idPropertyUpdateParameter, baseUpdateSproc.FindParameter("UpdateId")); - Assert.Same(idPropertyUpdateParameter, idPropertyUpdateParameterMapping.Parameter); + Assert.Same(idPropertyUpdateParameter, idPropertyUpdateParameterMapping.StoreParameter); Assert.Equal("UpdateId", idPropertyUpdateParameter.Name); Assert.Equal("default_int_mapping", idPropertyUpdateParameter.StoreType); Assert.Equal(ParameterDirection.Input, idPropertyUpdateParameter.Direction); @@ -1656,7 +1687,7 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) var idPropertyDeleteParameter = baseDeleteSproc.FindParameter(idProperty)!; var idPropertyDeleteParameterMapping = idProperty.GetDeleteStoredProcedureParameterMappings().First(); Assert.Same(idPropertyDeleteParameter, baseDeleteSproc.FindParameter("DeleteId")); - Assert.Same(idPropertyDeleteParameter, idPropertyDeleteParameterMapping.Parameter); + Assert.Same(idPropertyDeleteParameter, idPropertyDeleteParameterMapping.StoreParameter); Assert.Equal("DeleteId", idPropertyDeleteParameter.Name); Assert.Equal("default_int_mapping", idPropertyDeleteParameter.StoreType); Assert.Equal(ParameterDirection.Input, idPropertyDeleteParameter.Direction); @@ -1786,7 +1817,7 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) var idPropertyUpdateParameter = customerUpdateSproc.FindParameter(idProperty)!; var idPropertyUpdateParameterMapping = idProperty.GetUpdateStoredProcedureParameterMappings().First(); Assert.Same(idPropertyUpdateParameter, customerUpdateSproc.FindParameter("UpdateId")); - Assert.Same(idPropertyUpdateParameter, idPropertyUpdateParameterMapping.Parameter); + Assert.Same(idPropertyUpdateParameter, idPropertyUpdateParameterMapping.StoreParameter); Assert.Equal("UpdateId", idPropertyUpdateParameter.Name); Assert.Equal("default_int_mapping", idPropertyUpdateParameter.StoreType); Assert.Equal(ParameterDirection.Input, idPropertyUpdateParameter.Direction); @@ -1817,7 +1848,7 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping) var idPropertyDeleteParameter = customerDeleteSproc.FindParameter(idProperty)!; var idPropertyDeleteParameterMapping = idProperty.GetDeleteStoredProcedureParameterMappings().First(); Assert.Same(idPropertyDeleteParameter, customerDeleteSproc.FindParameter("DeleteId")); - Assert.Same(idPropertyDeleteParameter, idPropertyDeleteParameterMapping.Parameter); + Assert.Same(idPropertyDeleteParameter, idPropertyDeleteParameterMapping.StoreParameter); Assert.Equal("DeleteId", idPropertyDeleteParameter.Name); Assert.Equal("default_int_mapping", idPropertyDeleteParameter.StoreType); Assert.Equal(ParameterDirection.Input, idPropertyDeleteParameter.Direction); @@ -1944,7 +1975,7 @@ private IRelationalModel CreateTestModel( cb .InsertUsingStoredProcedure( s => s - .HasParameter(c => c.Id, p => p.HasName("InsertId")) + .HasParameter(c => c.Id, p => p.HasName("InsertId").IsOutput()) .HasParameter(c => c.EnumValue) .HasParameter(c => c.Name) .HasParameter(c => c.SomeShort)) @@ -1961,11 +1992,6 @@ private IRelationalModel CreateTestModel( { cb.InsertUsingStoredProcedure(s => s.HasParameter("SpecialtyAk")); } - else - { - cb.InsertUsingStoredProcedure( - s => s.HasParameter(c => c.Id, p => p.IsOutput())); - } } } }); @@ -2003,7 +2029,7 @@ private IRelationalModel CreateTestModel( cb .InsertUsingStoredProcedure( s => s - .HasParameter(b => b.Id, p => p.HasName("InsertId")) + .HasParameter(b => b.Id, p => p.HasName("InsertId").IsOutput()) .HasParameter(c => c.Specialty) .HasParameter(c => c.RelatedCustomerSpecialty) .HasParameter("AnotherRelatedCustomerId") @@ -2028,7 +2054,7 @@ private IRelationalModel CreateTestModel( { cb .InsertUsingStoredProcedure( - s => s + s => s .HasResultColumn(b => b.Id, p => p.HasName("InsertId")) .HasParameter(c => c.Specialty) .HasParameter(c => c.RelatedCustomerSpecialty) @@ -2118,7 +2144,7 @@ private IRelationalModel CreateTestModel( cb .InsertUsingStoredProcedure( s => s - .HasParameter(b => b.Id, p => p.HasName("InsertId")) + .HasParameter(b => b.Id, p => p.HasName("InsertId").IsOutput()) .HasParameter(c => c.Specialty) .HasParameter(c => c.RelatedCustomerSpecialty) .HasParameter("AnotherRelatedCustomerId") diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index ba7efa3b850..3ef2789e153 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -250,12 +250,12 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() var insertSproc = bookLabel.GetInsertStoredProcedure()!; Assert.Equal("Insert", insertSproc.Name); Assert.Equal("mySchema", insertSproc.Schema); - Assert.Equal(new[] { "BookId", "Discriminator" }, insertSproc.Parameters); - Assert.Equal(new[] { "Id" }, insertSproc.ResultColumns); - Assert.True(insertSproc.ContainsParameter("Discriminator")); - Assert.False(insertSproc.ContainsParameter("Id")); - Assert.False(insertSproc.ContainsResultColumn("Discriminator")); - Assert.True(insertSproc.ContainsResultColumn("Id")); + Assert.Equal(new[] { "BookId", "Discriminator" }, insertSproc.Parameters.Select(p => p.PropertyName)); + Assert.Equal(new[] { "Id" }, insertSproc.ResultColumns.Select(p => p.PropertyName)); + Assert.True(insertSproc.FindParameter("Discriminator")!.ForOriginalValue); + Assert.Null(insertSproc.FindParameter("Id")); + Assert.Null(insertSproc.FindResultColumn("Discriminator")); + Assert.False(insertSproc.FindResultColumn("Id")!.ForRowsAffected); Assert.True(insertSproc.AreTransactionsSuppressed); Assert.Equal("bar1", insertSproc["foo"]); Assert.Same(bookLabel, insertSproc.EntityType); @@ -263,7 +263,7 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() var updateSproc = bookLabel.GetUpdateStoredProcedure()!; Assert.Equal("Update", updateSproc.Name); Assert.Equal("dbo", updateSproc.Schema); - Assert.Equal(new[] { "Id", "BookId" }, updateSproc.Parameters); + Assert.Equal(new[] { "Id", "BookId" }, updateSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(updateSproc.ResultColumns); Assert.True(updateSproc.AreTransactionsSuppressed); Assert.Equal("bar2", updateSproc["foo"]); @@ -272,22 +272,22 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() var deleteSproc = bookLabel.GetDeleteStoredProcedure()!; Assert.Equal("BookLabel_Delete", deleteSproc.Name); Assert.Equal("mySchema", deleteSproc.Schema); - Assert.Equal(new[] { "Id" }, deleteSproc.Parameters); + Assert.Equal(new[] { "Id" }, deleteSproc.Parameters.Select(p => p.PropertyName)); 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.Single(id.GetOverrides()); Assert.Equal( "InsertId", id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.InsertStoredProcedure)!.Value)); Assert.Equal( - "UpdateId", + "Id", id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.UpdateStoredProcedure)!.Value)); Assert.Equal( - "DeleteId", + "Id", id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.DeleteStoredProcedure)!.Value)); var specialBookLabel = model.FindEntityType(typeof(SpecialBookLabel))!; @@ -550,7 +550,7 @@ public virtual void Can_use_sproc_mapping_with_owned_reference() var insertSproc = bookOwnership1.DeclaringEntityType.GetInsertStoredProcedure()!; Assert.Equal("Insert", insertSproc.Name); Assert.Null(insertSproc.Schema); - Assert.Equal(new[] { "Id", "BookId" }, insertSproc.Parameters); + Assert.Equal(new[] { "Id", "BookId" }, insertSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(insertSproc.ResultColumns); Assert.True(insertSproc.AreTransactionsSuppressed); Assert.Equal("bar1", insertSproc["foo"]); @@ -559,8 +559,8 @@ public virtual void Can_use_sproc_mapping_with_owned_reference() 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.Equal(new[] { "Id", "BookId" }, updateSproc.Parameters.Select(p => p.PropertyName)); + Assert.Equal(new[] { "Id" }, updateSproc.ResultColumns.Select(p => p.Name)); Assert.True(updateSproc.AreTransactionsSuppressed); Assert.Equal("bar2", updateSproc["foo"]); Assert.Same(bookOwnership1.DeclaringEntityType, updateSproc.EntityType); @@ -568,22 +568,22 @@ public virtual void Can_use_sproc_mapping_with_owned_reference() var deleteSproc = bookOwnership1.DeclaringEntityType.GetDeleteStoredProcedure()!; Assert.Equal("BookLabel_Delete", deleteSproc.Name); Assert.Null(deleteSproc.Schema); - Assert.Equal(new[] { "BookId" }, deleteSproc.Parameters); + Assert.Equal(new[] { "BookId" }, deleteSproc.Parameters.Select(p => p.PropertyName)); 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.Empty(bookId.GetOverrides()); Assert.Equal( - "InsertId", + "BookId", bookId.GetColumnName(StoreObjectIdentifier.Create(bookOwnership1.DeclaringEntityType, StoreObjectType.InsertStoredProcedure)!.Value)); Assert.Equal( - "UpdateId", + "BookId", bookId.GetColumnName(StoreObjectIdentifier.Create(bookOwnership1.DeclaringEntityType, StoreObjectType.UpdateStoredProcedure)!.Value)); Assert.Equal( - "DeleteId", + "BookId", bookId.GetColumnName(StoreObjectIdentifier.Create(bookOwnership1.DeclaringEntityType, StoreObjectType.DeleteStoredProcedure)!.Value)); Assert.Null(bookOwnership2.DeclaringEntityType.GetInsertStoredProcedure()); @@ -1424,6 +1424,34 @@ public abstract TestStoredProcedureBuilder HasParameter> propertyExpression, Action buildAction) where TDerivedEntity : class, TEntity; + + public abstract TestStoredProcedureBuilder HasOriginalValueParameter( + string propertyName); + + public abstract TestStoredProcedureBuilder HasOriginalValueParameter( + string propertyName, + Action buildAction); + + public abstract TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression); + + public abstract TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction); + + public abstract TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression) + where TDerivedEntity : class, TEntity; + + public abstract TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + where TDerivedEntity : class, TEntity; + + public abstract TestStoredProcedureBuilder HasRowsAffectedParameter(); + + public abstract TestStoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction); public abstract TestStoredProcedureBuilder HasResultColumn( string propertyName); @@ -1448,10 +1476,14 @@ public abstract TestStoredProcedureBuilder HasResultColumn buildAction) where TDerivedEntity : class, TEntity; - public abstract TestStoredProcedureBuilder SuppressTransactions(bool suppress = true); + public abstract TestStoredProcedureBuilder HasRowsAffectedResultColumn(); - //public abstract StoredProcedureBuilder HasRowsAffectedParameter( - // Action> buildAction); + public abstract TestStoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction); + + public abstract TestStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true); + + public abstract TestStoredProcedureBuilder SuppressTransactions(bool suppress = true); public abstract TestStoredProcedureBuilder HasAnnotation(string annotation, object? value); } @@ -1499,6 +1531,40 @@ public override TestStoredProcedureBuilder HasParameter> propertyExpression, Action buildAction) => Wrap(StoredProcedureBuilder.HasParameter(propertyExpression, s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + string propertyName) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyName)); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + string propertyName, + Action buildAction) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyName, s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression)); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression, s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression)); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression, s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasRowsAffectedParameter() + => Wrap(StoredProcedureBuilder.HasRowsAffectedParameter()); + + public override TestStoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction) + => Wrap(StoredProcedureBuilder.HasRowsAffectedParameter(s => buildAction(new(s)))); public override TestStoredProcedureBuilder HasResultColumn( string propertyName) @@ -1527,6 +1593,16 @@ public override TestStoredProcedureBuilder HasResultColumn buildAction) => Wrap(StoredProcedureBuilder.HasResultColumn(propertyExpression, s => buildAction(new(s)))); + public override TestStoredProcedureBuilder HasRowsAffectedResultColumn() + => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn()); + + public override TestStoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction) + => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn(s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned) + => Wrap(StoredProcedureBuilder.HasRowsAffectedReturn(rowsAffectedReturned)); + public override TestStoredProcedureBuilder SuppressTransactions(bool suppress) => Wrap(StoredProcedureBuilder.SuppressTransactions(suppress)); @@ -1582,6 +1658,44 @@ public override TestStoredProcedureBuilder HasParameter buildAction(new(s)))); + public override TestStoredProcedureBuilder HasOriginalValueParameter( + string propertyName) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyName)); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + string propertyName, + Action buildAction) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyName, s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression.GetMemberAccess().Name)); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + => Wrap( + StoredProcedureBuilder.HasOriginalValueParameter( + propertyExpression.GetMemberAccess().Name, s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression.GetMemberAccess().Name)); + + public override TestStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + => Wrap( + StoredProcedureBuilder.HasOriginalValueParameter( + propertyExpression.GetMemberAccess().Name, s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasRowsAffectedParameter() + => Wrap(StoredProcedureBuilder.HasRowsAffectedParameter()); + + public override TestStoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction) + => Wrap(StoredProcedureBuilder.HasRowsAffectedParameter(s => buildAction(new(s)))); + public override TestStoredProcedureBuilder HasResultColumn( string propertyName) => Wrap(StoredProcedureBuilder.HasResultColumn(propertyName)); @@ -1613,6 +1727,16 @@ public override TestStoredProcedureBuilder HasResultColumn buildAction(new(s)))); + public override TestStoredProcedureBuilder HasRowsAffectedResultColumn() + => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn()); + + public override TestStoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction) + => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn(s => buildAction(new(s)))); + + public override TestStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned) + => Wrap(StoredProcedureBuilder.HasRowsAffectedReturn(rowsAffectedReturned)); + public override TestStoredProcedureBuilder SuppressTransactions(bool suppress) => Wrap(StoredProcedureBuilder.SuppressTransactions(suppress)); @@ -1637,6 +1761,25 @@ public abstract TestOwnedNavigationStoredProcedureBuilder HasParameter( Expression> propertyExpression, Action buildAction); + + public abstract TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + string propertyName); + + public abstract TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + string propertyName, + Action buildAction); + + public abstract TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression); + + public abstract TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction); + + public abstract TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter(); + + public abstract TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction); public abstract TestOwnedNavigationStoredProcedureBuilder HasResultColumn( string propertyName); @@ -1652,12 +1795,17 @@ public abstract TestOwnedNavigationStoredProcedureBuilder> propertyExpression, Action buildAction); + public abstract TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn(); + + public abstract TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction); + + public abstract TestOwnedNavigationStoredProcedureBuilder + HasRowsAffectedReturn(bool rowsAffectedReturned = true); + public abstract TestOwnedNavigationStoredProcedureBuilder SuppressTransactions(bool suppress = true); - //public abstract StoredProcedureBuilder HasRowsAffectedParameter( - // Action> buildAction); - public abstract TestOwnedNavigationStoredProcedureBuilder HasAnnotation( string annotation, object? value); @@ -1702,7 +1850,32 @@ public override TestOwnedNavigationStoredProcedureBuilder> propertyExpression, Action buildAction) => Wrap(StoredProcedureBuilder.HasParameter(propertyExpression, s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + string propertyName) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyName)); + + public override TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + string propertyName, + Action buildAction) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyName, s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression)); + public override TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression, s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter() + => Wrap(StoredProcedureBuilder.HasRowsAffectedParameter()); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction) + => Wrap(StoredProcedureBuilder.HasRowsAffectedParameter(s => buildAction(new(s)))); + public override TestOwnedNavigationStoredProcedureBuilder HasResultColumn( string propertyName) => Wrap(StoredProcedureBuilder.HasResultColumn(propertyName)); @@ -1714,12 +1887,22 @@ public override TestOwnedNavigationStoredProcedureBuilder HasResultColumn( Expression> propertyExpression) - => Wrap(StoredProcedureBuilder.HasResultColumn(propertyExpression)); + => Wrap(StoredProcedureBuilder.HasResultColumn(propertyExpression)); public override TestOwnedNavigationStoredProcedureBuilder HasResultColumn( Expression> propertyExpression, Action buildAction) - => Wrap(StoredProcedureBuilder.HasResultColumn(propertyExpression, s => buildAction(new(s)))); + => Wrap(StoredProcedureBuilder.HasResultColumn(propertyExpression, s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn() + => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn()); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction) + => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn(s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned) + => Wrap(StoredProcedureBuilder.HasRowsAffectedReturn(rowsAffectedReturned)); public override TestOwnedNavigationStoredProcedureBuilder SuppressTransactions(bool suppress) => Wrap(StoredProcedureBuilder.SuppressTransactions(suppress)); @@ -1769,6 +1952,33 @@ public override TestOwnedNavigationStoredProcedureBuilder Wrap( StoredProcedureBuilder.HasParameter( propertyExpression.GetMemberAccess().Name, s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + string propertyName) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyName)); + + public override TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + string propertyName, + Action buildAction) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyName, s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression) + => Wrap(StoredProcedureBuilder.HasOriginalValueParameter(propertyExpression.GetMemberAccess().Name)); + + public override TestOwnedNavigationStoredProcedureBuilder HasOriginalValueParameter( + Expression> propertyExpression, + Action buildAction) + => Wrap( + StoredProcedureBuilder.HasOriginalValueParameter( + propertyExpression.GetMemberAccess().Name, s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter() + => Wrap(StoredProcedureBuilder.HasRowsAffectedParameter()); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedParameter( + Action buildAction) + => Wrap(StoredProcedureBuilder.HasRowsAffectedParameter(s => buildAction(new(s)))); public override TestOwnedNavigationStoredProcedureBuilder HasResultColumn( string propertyName) @@ -1789,6 +1999,16 @@ public override TestOwnedNavigationStoredProcedureBuilder Wrap( StoredProcedureBuilder.HasResultColumn( propertyExpression.GetMemberAccess().Name, s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn() + => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn()); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn( + Action buildAction) + => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn(s => buildAction(new(s)))); + + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned) + => Wrap(StoredProcedureBuilder.HasRowsAffectedReturn(rowsAffectedReturned)); public override TestOwnedNavigationStoredProcedureBuilder SuppressTransactions(bool suppress) => Wrap(StoredProcedureBuilder.SuppressTransactions(suppress)); @@ -1814,7 +2034,7 @@ StoredProcedureParameterBuilder IInfrastructure protected virtual TestStoredProcedureParameterBuilder Wrap(StoredProcedureParameterBuilder storedProcedureParameterBuilder) => new(storedProcedureParameterBuilder); - public virtual TestStoredProcedureParameterBuilder HasName(string? name) + public virtual TestStoredProcedureParameterBuilder HasName(string name) => Wrap(StoredProcedureParameterBuilder.HasName(name)); public virtual TestStoredProcedureParameterBuilder IsOutput() @@ -1844,7 +2064,7 @@ StoredProcedureResultColumnBuilder IInfrastructure new TestStoredProcedureResultColumnBuilder(storedProcedureResultColumnBuilder); - public virtual TestStoredProcedureResultColumnBuilder HasName(string? name) + public virtual TestStoredProcedureResultColumnBuilder HasName(string name) => Wrap(StoredProcedureResultColumnBuilder.HasName(name)); public virtual TestStoredProcedureResultColumnBuilder HasAnnotation( diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index 8a8f77c70f6..c49391a69f5 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -69,6 +69,20 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(IConventionStoredProcedureBuilder), typeof(IStoredProcedure)) }, + { + typeof(IReadOnlyStoredProcedureParameter), + (typeof(IMutableStoredProcedureParameter), + typeof(IConventionStoredProcedureParameter), + typeof(IConventionStoredProcedureParameterBuilder), + typeof(IStoredProcedureParameter)) + }, + { + typeof(IReadOnlyStoredProcedureResultColumn), + (typeof(IMutableStoredProcedureResultColumn), + typeof(IConventionStoredProcedureResultColumn), + typeof(IConventionStoredProcedureResultColumnBuilder), + typeof(IStoredProcedureResultColumn)) + }, { typeof(IReadOnlySequence), (typeof(IMutableSequence), @@ -113,15 +127,19 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(ITable), typeof(IView), typeof(IStoreFunction), + typeof(IStoreStoredProcedure), typeof(ITableMappingBase), typeof(ITableMapping), typeof(IViewMapping), typeof(IFunctionMapping), + typeof(IStoredProcedureMapping), typeof(IColumnBase), typeof(IColumn), typeof(IViewColumn), typeof(IFunctionColumn), typeof(IStoreFunctionParameter), + typeof(IStoreStoredProcedureParameter), + typeof(IStoreStoredProcedureResultColumn), typeof(IFunctionColumnMapping), typeof(IColumnMappingBase), typeof(IColumnMapping), @@ -130,7 +148,6 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(IForeignKeyConstraint), typeof(IUniqueConstraint), typeof(ITrigger), - typeof(IEntityTypeMappingFragment), typeof(IRelationalPropertyOverrides) }; @@ -248,7 +265,10 @@ public override .GetRuntimeMethods() .Single( m => m.Name == "GenerateCacheKeyCore" - && m.DeclaringType == typeof(RelationalCompiledQueryCacheKeyGenerator)) + && m.DeclaringType == typeof(RelationalCompiledQueryCacheKeyGenerator)), + typeof(IAnnotationCodeGenerator).GetMethod( + "RemoveAnnotationsHandledByConventions", AnyInstance, + new[] { typeof(IAnnotatable), typeof(IDictionary) }) }; public override HashSet UnmatchedMetadataMethods { get; } = new() @@ -281,6 +301,23 @@ public override typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(methodTypes[0], methodTypes[1])), typeof(Action) }), + GetMethod( + typeof(StoredProcedureBuilder<>), + nameof(StoredProcedureBuilder.HasOriginalValueParameter), + genericParameterCount: 2, + (typeTypes, methodTypes) => new[] + { + typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(methodTypes[0], methodTypes[1])) + }), + GetMethod( + typeof(StoredProcedureBuilder<>), + nameof(StoredProcedureBuilder.HasOriginalValueParameter), + genericParameterCount: 2, + (typeTypes, methodTypes) => new[] + { + typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(methodTypes[0], methodTypes[1])), + typeof(Action) + }), GetMethod( typeof(StoredProcedureBuilder<>), nameof(StoredProcedureBuilder.HasResultColumn), @@ -297,7 +334,10 @@ public override { typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(methodTypes[0], methodTypes[1])), typeof(Action) - }) + }), + typeof(IConventionStoredProcedure).GetMethod( + nameof(IConventionStoredProcedure.SetAreRowsAffectedReturned), + new[] { typeof(bool), typeof(bool) }) }; public override HashSet AsyncMethodExceptions { get; } = new() From 4a3f93cda23d5ebb6a782f022564b5e1b151a955 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 2 Aug 2022 10:46:22 -0700 Subject: [PATCH 2/8] React to PR feedback --- .../OwnedNavigationStoredProcedureBuilder.cs | 2 +- ...OwnedNavigationStoredProcedureBuilder``.cs | 4 +- .../Builders/StoredProcedureBuilder.cs | 2 +- .../Builders/StoredProcedureBuilder`.cs | 4 +- .../RelationalValueGenerationConvention.cs | 116 +++++++++++------- ...ntValuePropertyStoredProcedureParameter.cs | 2 +- .../Metadata/Internal/DbFunction.cs | 2 +- .../Metadata/Internal/DbFunctionParameter.cs | 2 +- .../Metadata/Internal/StoredProcedure.cs | 10 +- .../Properties/RelationalStrings.Designer.cs | 8 +- .../Properties/RelationalStrings.resx | 8 +- .../Metadata/RelationalModelTest.cs | 13 +- .../RelationalModelBuilderTest.cs | 20 +-- .../RelationalApiConsistencyTest.cs | 5 +- 14 files changed, 114 insertions(+), 84 deletions(-) diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs index f049e38ec37..c60ace6e276 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder.cs @@ -213,7 +213,7 @@ public virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedResultColumn /// A value indicating whether this stored procedure returns the number of rows affected. /// /// The same builder instance so that multiple configuration calls can be chained. - public virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true) + public virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned = true) { Builder.HasRowsAffectedReturn(rowsAffectedReturned, ConfigurationSource.Explicit); return this; diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs index f302fad845f..4c866fbfa68 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationStoredProcedureBuilder``.cs @@ -228,8 +228,8 @@ public virtual OwnedNavigationStoredProcedureBuilder /// The same builder instance so that multiple configuration calls can be chained. - public new virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true) - => (OwnedNavigationStoredProcedureBuilder)base.HasRowsAffectedReturn(rowsAffectedReturned); + public new virtual OwnedNavigationStoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned = true) + => (OwnedNavigationStoredProcedureBuilder)base.HasRowsAffectedReturnValue(rowsAffectedReturned); /// /// Prevents automatically creating a transaction when executing this stored procedure. diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs index b0d559139ba..5ab996b3ec1 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs @@ -176,7 +176,7 @@ public virtual StoredProcedureBuilder HasRowsAffectedResultColumn( /// A value indicating whether this stored procedure returns the number of rows affected. /// /// The same builder instance so that multiple configuration calls can be chained. - public virtual StoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true) + public virtual StoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned = true) { Builder.HasRowsAffectedReturn(rowsAffectedReturned, ConfigurationSource.Explicit); return this; diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs index a478e9efa04..03fab03faf0 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder`.cs @@ -311,8 +311,8 @@ public virtual StoredProcedureBuilder HasResultColumn /// The same builder instance so that multiple configuration calls can be chained. - public new virtual StoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true) - => (StoredProcedureBuilder)base.HasRowsAffectedReturn(rowsAffectedReturned); + public new virtual StoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned = true) + => (StoredProcedureBuilder)base.HasRowsAffectedReturnValue(rowsAffectedReturned); /// /// Prevents automatically creating a transaction when executing this stored procedure. diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs index e073c26a982..214367c5dc5 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs @@ -77,55 +77,57 @@ public virtual void ProcessEntityTypeAnnotationChanged( IConventionContext context) { var entityType = entityTypeBuilder.Metadata; - if (name == RelationalAnnotationNames.ViewName - || name == RelationalAnnotationNames.FunctionName - || name == RelationalAnnotationNames.SqlQuery - || name == RelationalAnnotationNames.InsertStoredProcedure) + switch (name) { - if (annotation?.Value != null - && oldAnnotation?.Value == null - && entityType.GetTableName() == null) - { + case RelationalAnnotationNames.ViewName: + case RelationalAnnotationNames.FunctionName: + case RelationalAnnotationNames.SqlQuery: + case RelationalAnnotationNames.InsertStoredProcedure: + if (annotation?.Value != null + && oldAnnotation?.Value == null + && entityType.GetTableName() == null) + { + ProcessTableChanged( + entityTypeBuilder, + entityType.GetDefaultTableName(), + entityType.GetDefaultSchema(), + null, + null); + } + break; + + case RelationalAnnotationNames.TableName: + var schema = entityType.GetSchema(); ProcessTableChanged( entityTypeBuilder, - entityType.GetDefaultTableName(), - entityType.GetDefaultSchema(), - null, - null); - } - } - else if (name == RelationalAnnotationNames.TableName) - { - var schema = entityType.GetSchema(); - ProcessTableChanged( - entityTypeBuilder, - (string?)oldAnnotation?.Value ?? entityType.GetDefaultTableName(), - schema, - entityType.GetTableName(), - schema); - } - else if (name == RelationalAnnotationNames.Schema) - { - var tableName = entityType.GetTableName(); - ProcessTableChanged( - entityTypeBuilder, - tableName, - (string?)oldAnnotation?.Value ?? entityType.GetDefaultSchema(), - tableName, - entityTypeBuilder.Metadata.GetSchema()); - } - else if (name == RelationalAnnotationNames.MappingStrategy) - { - var primaryKey = entityTypeBuilder.Metadata.FindPrimaryKey(); - if (primaryKey == null) - { - return; - } + (string?)oldAnnotation?.Value ?? entityType.GetDefaultTableName(), + schema, + entityType.GetTableName(), + schema); + break; + + case RelationalAnnotationNames.Schema: + var tableName = entityType.GetTableName(); + ProcessTableChanged( + entityTypeBuilder, + tableName, + (string?)oldAnnotation?.Value ?? entityType.GetDefaultSchema(), + tableName, + entityTypeBuilder.Metadata.GetSchema()); + break; + + case RelationalAnnotationNames.MappingStrategy: + var primaryKey = entityTypeBuilder.Metadata.FindPrimaryKey(); + if (primaryKey == null) + { + return; + } - foreach (var property in primaryKey.Properties) - { - property.Builder.ValueGenerated(GetValueGenerated(property)); - } + foreach (var property in primaryKey.Properties) + { + property.Builder.ValueGenerated(GetValueGenerated(property)); + } + break; } } @@ -183,6 +185,30 @@ private void ProcessTableChanged( ? GetValueGenerated((IReadOnlyProperty)property) : null; } + + /// + /// Checks whether or not the mapping strategy and property allow value generation by convention. + /// + /// The property for which value generation is being considered. + /// The current mapping strategy. + /// if value generation is allowed; otherwise. + protected virtual bool MappingStrategyAllowsValueGeneration( + IConventionProperty property, + string? mappingStrategy) + { + if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy) + { + var propertyType = property.ClrType.UnwrapNullableType(); + if (property.IsPrimaryKey() + && propertyType.IsInteger() + && propertyType != typeof(byte)) + { + return false; + } + } + + return true; + } /// /// Returns the store value generation strategy to set for the given property. diff --git a/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs index 5121ebbf504..710b39c819c 100644 --- a/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs @@ -109,7 +109,7 @@ public override bool IsReadOnly /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool? ForOriginalValue - => true; + => false; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs index e4df773f2ae..54c6c4e1494 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs @@ -177,7 +177,7 @@ public virtual bool IsInModel /// public virtual void SetRemovedFromModel() => _builder = 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 diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs index 46f44a0b0bf..e3b489f64fe 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs @@ -72,7 +72,7 @@ public virtual bool IsInModel /// public virtual void SetRemovedFromModel() => _builder = 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 diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs index 8315ed32d63..9b7f5affc6a 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs @@ -484,11 +484,11 @@ public virtual bool AreRowsAffectedReturned /// 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 SetAreRowsAffectedReturned(bool areTransactionsSuppressed) + public virtual bool SetAreRowsAffectedReturned(bool areRowsAffectedReturned) { EnsureMutable(); - if (ResultColumns.Any()) + if (ResultColumns.Any() || _rowsAffectedResultColumn != null) { throw new InvalidOperationException(RelationalStrings.StoredProcedureRowsAffectedReturnConflictingResultColumn( ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); @@ -500,9 +500,9 @@ public virtual bool SetAreRowsAffectedReturned(bool areTransactionsSuppressed) ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - _areRowsAffectedReturned = areTransactionsSuppressed; + _areRowsAffectedReturned = areRowsAffectedReturned; - return areTransactionsSuppressed; + return areRowsAffectedReturned; } /// @@ -634,6 +634,7 @@ public virtual OriginalValuePropertyStoredProcedureParameter AddOriginalValuePar public virtual RowsAffectedStoredProcedureParameter AddRowsAffectedParameter() { if (_rowsAffectedParameter != null + || _rowsAffectedResultColumn != null || _areRowsAffectedReturned) { throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter( @@ -715,6 +716,7 @@ public virtual PropertyStoredProcedureResultColumn AddResultColumn(string proper public virtual RowsAffectedStoredProcedureResultColumn AddRowsAffectedResultColumn() { if (_rowsAffectedResultColumn != null + || _rowsAffectedParameter != null || _areRowsAffectedReturned) { throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn( diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 2edd066765a..5dc8bb8f472 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1238,7 +1238,7 @@ public static string StoredProcedureDeleteNonKeyProperty(object? entityType, obj entityType, property, sproc); /// - /// The original value parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because original value another parameter for this property already exists. + /// The original value parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because another original value parameter for this property already exists. /// public static string StoredProcedureDuplicateOriginalValueParameter(object? property, object? sproc) => string.Format( @@ -1262,7 +1262,7 @@ public static string StoredProcedureDuplicateResultColumn(object? property, obje property, sproc); /// - /// The rows affected parameter cannot be added to the stored procedure '{sproc}' because a rows affected parameter for this property already exists or the rows affected are being returned. + /// The rows affected parameter cannot be added to the stored procedure '{sproc}' because the rows affected are already returned via another parameter, via the stored procedure return value or via a result column. /// public static string StoredProcedureDuplicateRowsAffectedParameter(object? sproc) => string.Format( @@ -1270,7 +1270,7 @@ public static string StoredProcedureDuplicateRowsAffectedParameter(object? sproc sproc); /// - /// The rows affected result column cannot be added to the stored procedure '{sproc}' because a rows affected parameter for this property exists or the rows affected are already being returned. + /// The rows affected result column cannot be added to the stored procedure '{sproc}' because the rows affected are already returned via another column, via a parameter or via the stored procedure return value. /// public static string StoredProcedureDuplicateRowsAffectedResultColumn(object? sproc) => string.Format( @@ -1374,7 +1374,7 @@ public static string StoredProcedureResultColumnNotGenerated(object? entityType, entityType, property, sproc); /// - /// TThe stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter for this property already exists. + /// The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter for this property already exists. /// public static string StoredProcedureRowsAffectedReturnConflictingParameter(object? sproc) => string.Format( diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index a58f30607f7..520058b53a0 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -865,7 +865,7 @@ The property '{entityType}.{property}' is mapped to a parameter of the stored procedure '{sproc}', but only concurrency token and key properties are supported for Delete stored procedures. - The original value parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because original value another parameter for this property already exists. + The original value parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because another original value parameter for this property already exists. The parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because another parameter for this property already exists. @@ -874,10 +874,10 @@ The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because another result column for this property already exists. - The rows affected parameter cannot be added to the stored procedure '{sproc}' because a rows affected parameter for this property already exists or the rows affected are being returned. + The rows affected parameter cannot be added to the stored procedure '{sproc}' because the rows affected are already returned via another parameter, via the stored procedure return value or via a result column. - The rows affected result column cannot be added to the stored procedure '{sproc}' because a rows affected parameter for this property exists or the rows affected are already being returned. + The rows affected result column cannot be added to the stored procedure '{sproc}' because the rows affected are already returned via another column, via a parameter or via the stored procedure return value. The entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the store-generated properties {properties} are not mapped to any output parameter or result column. @@ -916,7 +916,7 @@ The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is not configured as store-generated. - TThe stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter for this property already exists. + The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter for this property already exists. The stored procedure '{sproc}' cannot be configured to return the rows affected because at least one result column was already added. diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 2c90b649644..28c1f21f2f0 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -1975,7 +1975,7 @@ private IRelationalModel CreateTestModel( cb .InsertUsingStoredProcedure( s => s - .HasParameter(c => c.Id, p => p.HasName("InsertId").IsOutput()) + .HasParameter(c => c.Id, p => p.HasName("InsertId")) .HasParameter(c => c.EnumValue) .HasParameter(c => c.Name) .HasParameter(c => c.SomeShort)) @@ -1992,6 +1992,11 @@ private IRelationalModel CreateTestModel( { cb.InsertUsingStoredProcedure(s => s.HasParameter("SpecialtyAk")); } + else + { + cb.InsertUsingStoredProcedure( + s => s.HasParameter(c => c.Id, p => p.IsOutput())); + } } } }); @@ -2029,7 +2034,7 @@ private IRelationalModel CreateTestModel( cb .InsertUsingStoredProcedure( s => s - .HasParameter(b => b.Id, p => p.HasName("InsertId").IsOutput()) + .HasParameter(b => b.Id, p => p.HasName("InsertId")) .HasParameter(c => c.Specialty) .HasParameter(c => c.RelatedCustomerSpecialty) .HasParameter("AnotherRelatedCustomerId") @@ -2054,7 +2059,7 @@ private IRelationalModel CreateTestModel( { cb .InsertUsingStoredProcedure( - s => s + s => s .HasResultColumn(b => b.Id, p => p.HasName("InsertId")) .HasParameter(c => c.Specialty) .HasParameter(c => c.RelatedCustomerSpecialty) @@ -2144,7 +2149,7 @@ private IRelationalModel CreateTestModel( cb .InsertUsingStoredProcedure( s => s - .HasParameter(b => b.Id, p => p.HasName("InsertId").IsOutput()) + .HasParameter(b => b.Id, p => p.HasName("InsertId")) .HasParameter(c => c.Specialty) .HasParameter(c => c.RelatedCustomerSpecialty) .HasParameter("AnotherRelatedCustomerId") diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 3ef2789e153..1b227335de6 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -1481,7 +1481,7 @@ public abstract TestStoredProcedureBuilder HasResultColumn HasRowsAffectedResultColumn( Action buildAction); - public abstract TestStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned = true); + public abstract TestStoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned = true); public abstract TestStoredProcedureBuilder SuppressTransactions(bool suppress = true); @@ -1600,8 +1600,8 @@ public override TestStoredProcedureBuilder HasRowsAffectedResultColumn( Action buildAction) => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn(s => buildAction(new(s)))); - public override TestStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned) - => Wrap(StoredProcedureBuilder.HasRowsAffectedReturn(rowsAffectedReturned)); + public override TestStoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned) + => Wrap(StoredProcedureBuilder.HasRowsAffectedReturnValue(rowsAffectedReturned)); public override TestStoredProcedureBuilder SuppressTransactions(bool suppress) => Wrap(StoredProcedureBuilder.SuppressTransactions(suppress)); @@ -1734,8 +1734,8 @@ public override TestStoredProcedureBuilder HasRowsAffectedResultColumn( Action buildAction) => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn(s => buildAction(new(s)))); - public override TestStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned) - => Wrap(StoredProcedureBuilder.HasRowsAffectedReturn(rowsAffectedReturned)); + public override TestStoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned) + => Wrap(StoredProcedureBuilder.HasRowsAffectedReturnValue(rowsAffectedReturned)); public override TestStoredProcedureBuilder SuppressTransactions(bool suppress) => Wrap(StoredProcedureBuilder.SuppressTransactions(suppress)); @@ -1801,7 +1801,7 @@ public abstract TestOwnedNavigationStoredProcedureBuilder buildAction); public abstract TestOwnedNavigationStoredProcedureBuilder - HasRowsAffectedReturn(bool rowsAffectedReturned = true); + HasRowsAffectedReturnValue(bool rowsAffectedReturned = true); public abstract TestOwnedNavigationStoredProcedureBuilder SuppressTransactions(bool suppress = true); @@ -1901,8 +1901,8 @@ public override TestOwnedNavigationStoredProcedureBuilder buildAction) => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn(s => buildAction(new(s)))); - public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned) - => Wrap(StoredProcedureBuilder.HasRowsAffectedReturn(rowsAffectedReturned)); + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned) + => Wrap(StoredProcedureBuilder.HasRowsAffectedReturnValue(rowsAffectedReturned)); public override TestOwnedNavigationStoredProcedureBuilder SuppressTransactions(bool suppress) => Wrap(StoredProcedureBuilder.SuppressTransactions(suppress)); @@ -2007,8 +2007,8 @@ public override TestOwnedNavigationStoredProcedureBuilder buildAction) => Wrap(StoredProcedureBuilder.HasRowsAffectedResultColumn(s => buildAction(new(s)))); - public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedReturn(bool rowsAffectedReturned) - => Wrap(StoredProcedureBuilder.HasRowsAffectedReturn(rowsAffectedReturned)); + public override TestOwnedNavigationStoredProcedureBuilder HasRowsAffectedReturnValue(bool rowsAffectedReturned) + => Wrap(StoredProcedureBuilder.HasRowsAffectedReturnValue(rowsAffectedReturned)); public override TestOwnedNavigationStoredProcedureBuilder SuppressTransactions(bool suppress) => Wrap(StoredProcedureBuilder.SuppressTransactions(suppress)); diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index c49391a69f5..29fecbd5d3f 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -265,10 +265,7 @@ public override .GetRuntimeMethods() .Single( m => m.Name == "GenerateCacheKeyCore" - && m.DeclaringType == typeof(RelationalCompiledQueryCacheKeyGenerator)), - typeof(IAnnotationCodeGenerator).GetMethod( - "RemoveAnnotationsHandledByConventions", AnyInstance, - new[] { typeof(IAnnotatable), typeof(IDictionary) }) + && m.DeclaringType == typeof(RelationalCompiledQueryCacheKeyGenerator)) }; public override HashSet UnmatchedMetadataMethods { get; } = new() From 490b28bffd5bb79c378f9f86873c32052a3d9516 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 2 Aug 2022 23:37:42 -0700 Subject: [PATCH 3/8] Added sproc mapping to table mapping Remove EFCore.Design dependency from EFCore.Specification.Tests --- .../Metadata/ITableMapping.cs | 15 +++ .../Metadata/Internal/RelationalModel.cs | 27 +++++- .../Metadata/Internal/StoredProcedure.cs | 14 +-- .../Metadata/Internal/TableMapping.cs | 33 ++++++- .../Properties/RelationalStrings.Designer.cs | 18 +--- .../Properties/RelationalStrings.resx | 8 +- .../Design/CSharpMigrationsGeneratorTest.cs | 10 +- .../Design/MigrationScaffolderTest.cs | 2 +- .../Internal/CSharpModelGeneratorTest.cs | 2 +- .../TestUtilities/DesignTestHelpers.cs | 2 +- .../DataAnnotationRelationalTestBase.cs | 48 ++++++++++ ...Core.Relational.Specification.Tests.csproj | 1 + .../Migrations/MigrationsTestBase.cs | 2 +- .../OwnedEntityQueryRelationalTestBase.cs | 91 +++++++++++++++++++ .../TestUtilities/RelationalTestHelpers.cs | 77 ++++++++++++++++ .../TestUtilities/TestOperationReporter.cs | 0 .../Design/AnnotationCodeGeneratorTest.cs | 2 +- .../RelationalModelValidatorTest.cs | 2 +- .../TableValuedDbFunctionConventionTest.cs | 2 +- .../SequenceUniquificationConventionTest.cs | 4 +- ...leSharingConcurrencyTokenConventionTest.cs | 4 +- .../Metadata/DbFunctionMetadataTests.cs | 2 +- .../RelationalBuilderExtensionsTest.cs | 2 +- ...tionalEntityTypeAttributeConventionTest.cs | 6 +- .../Metadata/RelationalIndexTest.cs | 2 +- .../RelationalMetadataExtensionsTest.cs | 2 +- .../Metadata/RelationalModelTest.cs | 2 +- ...lationalPropertyAttributeConventionTest.cs | 6 +- .../Metadata/TriggerTest.cs | 2 +- .../Internal/MigrationsModelDifferTest.cs | 2 +- .../RelationalDatabaseFacadeExtensionsTest.cs | 18 ++-- .../RelationalEventIdTest.cs | 2 +- .../RelationalDatabaseFacadeExtensionsTest.cs | 4 +- .../Storage/RelationalParameterBuilderTest.cs | 2 +- .../Storage/RelationalTypeMapperTest.cs | 2 +- ...elpers.cs => FakeRelationalTestHelpers.cs} | 8 +- .../TestRelationalConventionSetBuilder.cs | 2 +- .../Update/BatchExecutorTest.cs | 2 +- .../Update/CommandBatchPreparerTest.cs | 18 ++-- .../Update/ModificationCommandComparerTest.cs | 4 +- .../Update/ModificationCommandTest.cs | 4 +- .../ReaderModificationCommandBatchTest.cs | 6 +- .../Update/UpdateSqlGeneratorTest.cs | 2 +- .../DataAnnotationTestBase.cs | 48 ---------- .../EFCore.Specification.Tests.csproj | 1 - .../Query/OwnedEntityQueryTestBase.cs | 91 ------------------- .../QueryExpressionInterceptionTestBase.cs | 2 - .../TestUtilities/TestHelpers.cs | 69 +------------- .../Migrations/MigrationsSqlServerTest.cs | 2 +- .../TestUtilities/SqlServerTestHelpers.cs | 2 +- .../Migrations/MigrationsSqliteTest.cs | 2 +- .../TestUtilities/SqliteTestHelpers.cs | 2 +- 52 files changed, 359 insertions(+), 324 deletions(-) create mode 100644 test/EFCore.Relational.Specification.Tests/TestUtilities/RelationalTestHelpers.cs rename test/{EFCore.Specification.Tests => EFCore.Relational.Specification.Tests}/TestUtilities/TestOperationReporter.cs (100%) rename test/EFCore.Relational.Tests/TestUtilities/{RelationalTestHelpers.cs => FakeRelationalTestHelpers.cs} (75%) diff --git a/src/EFCore.Relational/Metadata/ITableMapping.cs b/src/EFCore.Relational/Metadata/ITableMapping.cs index 732c08d3756..3605b5f8d00 100644 --- a/src/EFCore.Relational/Metadata/ITableMapping.cs +++ b/src/EFCore.Relational/Metadata/ITableMapping.cs @@ -22,6 +22,21 @@ public interface ITableMapping : ITableMappingBase /// Gets the properties mapped to columns on the target table. /// new IEnumerable ColumnMappings { get; } + + /// + /// Gets the corresponding insert stored procedure mapping if it exists. + /// + IStoredProcedureMapping? InsertStoredProcedureMapping { get; } + + /// + /// Gets the corresponding insert stored procedure mapping if it exists. + /// + IStoredProcedureMapping? DeleteStoredProcedureMapping { get; } + + /// + /// Gets the corresponding insert stored procedure mapping if it exists. + /// + IStoredProcedureMapping? UpdateStoredProcedureMapping { get; } /// /// diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 11919dc3ffd..3fd102835fc 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -908,7 +908,7 @@ private static void AddStoredProcedures( && m.Table.Schema == mappedType.GetSchema() && m.IsSplitEntityTypePrincipal != false && m.IncludesDerivedTypes == includesDerivedTypes); - var tableMapping = tableMappings.FirstOrDefault(); + var tableMapping = (TableMapping?)tableMappings.FirstOrDefault(); Check.DebugAssert(tableMapping == null || tableMappings.Count() == 1, "Expected table mapping to be unique"); @@ -916,7 +916,7 @@ private static void AddStoredProcedures( if (insertSproc != null && insertStoredProcedureMappings != null) { - CreateStoredProcedureMapping( + var insertProcedureMapping = CreateStoredProcedureMapping( entityType, mappedType, insertSproc, @@ -925,6 +925,11 @@ private static void AddStoredProcedures( insertStoredProcedureMappings, includesDerivedTypes, relationalTypeMappingSource); + + if (tableMapping != null) + { + tableMapping.InsertStoredProcedureMapping = insertProcedureMapping; + } } else if (entityType == mappedType) { @@ -935,7 +940,7 @@ private static void AddStoredProcedures( if (deleteSproc != null && deleteStoredProcedureMappings != null) { - CreateStoredProcedureMapping( + var deleteProcedureMapping = CreateStoredProcedureMapping( entityType, mappedType, deleteSproc, @@ -944,6 +949,11 @@ private static void AddStoredProcedures( deleteStoredProcedureMappings, includesDerivedTypes, relationalTypeMappingSource); + + if (tableMapping != null) + { + tableMapping.InsertStoredProcedureMapping = deleteProcedureMapping; + } } else if (entityType == mappedType) { @@ -954,7 +964,7 @@ private static void AddStoredProcedures( if (updateSproc != null && updateStoredProcedureMappings != null) { - CreateStoredProcedureMapping( + var updateProcedureMapping = CreateStoredProcedureMapping( entityType, mappedType, updateSproc, @@ -963,6 +973,11 @@ private static void AddStoredProcedures( updateStoredProcedureMappings, includesDerivedTypes, relationalTypeMappingSource); + + if (tableMapping != null) + { + tableMapping.InsertStoredProcedureMapping = updateProcedureMapping; + } } else if (entityType == mappedType) { @@ -996,7 +1011,7 @@ private static void AddStoredProcedures( } } - private static void CreateStoredProcedureMapping( + private static StoredProcedureMapping CreateStoredProcedureMapping( IEntityType entityType, IEntityType mappedType, IRuntimeStoredProcedure storedProcedure, @@ -1153,6 +1168,8 @@ private static void CreateStoredProcedureMapping( storedProcedureMappings.Add(storedProcedureMapping); storeStoredProcedure.EntityTypeMappings.Add(storedProcedureMapping); + return storedProcedureMapping; + static StoreStoredProcedure GetOrCreateStoreStoredProcedure( IRuntimeStoredProcedure storedProcedure, RelationalModel model) diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs index 9b7f5affc6a..44a538c4f8b 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs @@ -488,13 +488,7 @@ public virtual bool SetAreRowsAffectedReturned(bool areRowsAffectedReturned) { EnsureMutable(); - if (ResultColumns.Any() || _rowsAffectedResultColumn != null) - { - throw new InvalidOperationException(RelationalStrings.StoredProcedureRowsAffectedReturnConflictingResultColumn( - ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); - } - - if (_rowsAffectedParameter != null) + if (_rowsAffectedParameter != null || _rowsAffectedResultColumn != null) { throw new InvalidOperationException(RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter( ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); @@ -685,12 +679,6 @@ public virtual PropertyStoredProcedureResultColumn AddResultColumn(string proper propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - if (_areRowsAffectedReturned) - { - throw new InvalidOperationException(RelationalStrings.StoredProcedureRowsAffectedReturnResultColumn( - propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); - } - var resultColumn = new PropertyStoredProcedureResultColumn(this, propertyName); _resultColumns.Add(resultColumn); _propertyResultColumns.Add(propertyName, resultColumn); diff --git a/src/EFCore.Relational/Metadata/Internal/TableMapping.cs b/src/EFCore.Relational/Metadata/Internal/TableMapping.cs index 09c8e2a2ec2..a0bb67ba981 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMapping.cs @@ -24,10 +24,39 @@ public TableMapping( : base(entityType, table, includesDerivedTypes) { } - - /// + + /// + /// 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 new virtual ITable Table => (ITable)base.Table; + + /// + /// 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 IStoredProcedureMapping? InsertStoredProcedureMapping { 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 IStoredProcedureMapping? DeleteStoredProcedureMapping { 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 IStoredProcedureMapping? UpdateStoredProcedureMapping { get; set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 5dc8bb8f472..fae4cc27e93 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1374,29 +1374,13 @@ public static string StoredProcedureResultColumnNotGenerated(object? entityType, entityType, property, sproc); /// - /// The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter for this property already exists. + /// The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter or a rows affected result column for this stored procedure already exists. /// public static string StoredProcedureRowsAffectedReturnConflictingParameter(object? sproc) => string.Format( GetString("StoredProcedureRowsAffectedReturnConflictingParameter", nameof(sproc)), sproc); - /// - /// The stored procedure '{sproc}' cannot be configured to return the rows affected because at least one result column was already added. - /// - public static string StoredProcedureRowsAffectedReturnConflictingResultColumn(object? sproc) - => string.Format( - GetString("StoredProcedureRowsAffectedReturnConflictingResultColumn", nameof(sproc)), - sproc); - - /// - /// The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because rows affected are being returned. - /// - public static string StoredProcedureRowsAffectedReturnResultColumn(object? property, object? sproc) - => string.Format( - GetString("StoredProcedureRowsAffectedReturnResultColumn", nameof(property), nameof(sproc)), - 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. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 520058b53a0..76c7fd8231f 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -916,13 +916,7 @@ The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is not configured as store-generated. - The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter for this property already exists. - - - The stored procedure '{sproc}' cannot be configured to return the rows affected because at least one result column was already added. - - - The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because rows affected are being returned. + The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter or a rows affected result column for this stored procedure already exists. 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. diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 012e9d5e58f..6ed728dec13 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -346,7 +346,7 @@ private static void MissingAnnotationCheck( if (!invalidAnnotations.Contains(annotationName)) { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); var metadataItem = createMetadataItem(modelBuilder); metadataItem.SetAnnotation( annotationName, validAnnotations.ContainsKey(annotationName) @@ -442,7 +442,7 @@ public void Snapshot_with_enum_discriminator_uses_converted_values() new CSharpSnapshotGeneratorDependencies( codeHelper, sqlServerTypeMappingSource, sqlServerAnnotationCodeGenerator)))); - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); modelBuilder.Entity( eb => @@ -469,7 +469,7 @@ public void Snapshot_with_enum_discriminator_uses_converted_values() private static void AssertConverter(ValueConverter valueConverter, string expected) { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); var property = modelBuilder.Entity().Property(e => e.Id).Metadata; property.SetMaxLength(1000); property.SetValueConverter(valueConverter); @@ -697,7 +697,7 @@ public void Snapshots_compile() { var generator = CreateMigrationsCodeGenerator(); - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); modelBuilder.Entity( x => @@ -793,7 +793,7 @@ public void Snapshot_with_default_values_are_round_tripped() { var generator = CreateMigrationsCodeGenerator(); - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity( eb => { diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index 9e878820428..fb9bccaaf17 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -71,7 +71,7 @@ var migrationAssembly new FakeDiagnosticsLogger()); var historyRepository = new MockHistoryRepository(); - var services = RelationalTestHelpers.Instance.CreateContextServices(); + var services = FakeRelationalTestHelpers.Instance.CreateContextServices(); var model = new Model().FinalizeModel(); model.AddRuntimeAnnotation(RelationalAnnotationNames.RelationalModel, new RelationalModel(model)); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpModelGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpModelGeneratorTest.cs index 9023d6205a3..3efc541ed9c 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpModelGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpModelGeneratorTest.cs @@ -22,7 +22,7 @@ public void Language_works() public void WriteCode_works() { var generator = CreateGenerator(); - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity("TestEntity").Property("Id"); var result = generator.GenerateModel( diff --git a/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs b/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs index a36b1eab231..a65dc172213 100644 --- a/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs +++ b/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities; -public class DesignTestHelpers : TestHelpers +public class DesignTestHelpers : RelationalTestHelpers { protected DesignTestHelpers() { diff --git a/test/EFCore.Relational.Specification.Tests/DataAnnotationRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/DataAnnotationRelationalTestBase.cs index 33b6d67345f..a11bdc374b6 100644 --- a/test/EFCore.Relational.Specification.Tests/DataAnnotationRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/DataAnnotationRelationalTestBase.cs @@ -14,6 +14,54 @@ protected DataAnnotationRelationalTestBase(TFixture fixture) { } + [ConditionalFact] + public virtual void ForeignKey_to_ForeignKey_on_many_to_many() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + entity => + { + entity.HasMany(d => d.Profile16s) + .WithMany(p => p.Login16s) + .UsingEntity>( + "Login16Profile16", + l => l.HasOne().WithMany().HasForeignKey("Profile16Id"), + r => r.HasOne().WithMany().HasForeignKey("Login16Id"), + j => + { + j.HasKey("Login16Id", "Profile16Id"); + + j.ToTable("Login16Profile16"); + }); + }); + + var model = Validate(modelBuilder); + + var login = modelBuilder.Model.FindEntityType(typeof(Login16)); + var logins = login.FindSkipNavigation(nameof(Login16.Profile16s)); + var join = logins.JoinEntityType; + Assert.Equal(2, join.GetProperties().Count()); + Assert.False(GetProperty(model, "Login16Id").IsForeignKey()); + Assert.False(GetProperty(model, "Profile16Id").IsForeignKey()); + } + + public class Login16 + { + public int Login16Id { get; set; } + + [ForeignKey("Login16Id")] + public virtual ICollection Profile16s { get; set; } + } + + public class Profile16 + { + public int Profile16Id { get; set; } + + [ForeignKey("Profile16Id")] + public virtual ICollection Login16s { get; set; } + } + [ConditionalFact] public virtual void Table_can_configure_TPT_with_Owned() => ExecuteWithStrategyInTransaction( diff --git a/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj b/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj index b45763d16c6..cba1f1ace6a 100644 --- a/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj +++ b/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj @@ -53,6 +53,7 @@ + diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs index e9c185dc0ad..5a94fb821c8 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs @@ -2008,7 +2008,7 @@ protected virtual void AssertSql(params string[] expected) public abstract class MigrationsFixtureBase : SharedStoreFixtureBase { - public abstract TestHelpers TestHelpers { get; } + public abstract RelationalTestHelpers TestHelpers { get; } public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs index 92a9a0369d9..8945f7a2930 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs @@ -230,6 +230,97 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Owned_entity_with_all_null_properties_materializes_when_not_containing_another_owned_entity(bool async) + { + var contextFactory = await InitializeAsync(seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + var query = context.RotRutCases.OrderBy(e => e.Buyer); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Collection(result, + t => + { + Assert.Equal("Buyer1", t.Buyer); + Assert.NotNull(t.Rot); + Assert.Equal(1, t.Rot.ServiceType); + Assert.Equal("1", t.Rot.ApartmentNo); + Assert.NotNull(t.Rut); + Assert.Equal(1, t.Rut.Value); + }, + t => + { + Assert.Equal("Buyer2", t.Buyer); + // Cannot verify owned entities here since they differ between relational/in-memory + }); + } + + protected class MyContext28247 : DbContext + { + public MyContext28247(DbContextOptions options) + : base(options) + { + } + + public DbSet RotRutCases { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(b => + { + b.ToTable("RotRutCases"); + + b.OwnsOne(e => e.Rot); + b.OwnsOne(e => e.Rut); + }); + } + + public void Seed() + { + Add( + new RotRutCase + { + Buyer = "Buyer1", + Rot = new Rot { ServiceType = 1, ApartmentNo = "1" }, + Rut = new Rut { Value = 1 } + }); + + Add( + new RotRutCase + { + Buyer = "Buyer2", + Rot = new Rot { ServiceType = null, ApartmentNo = null }, + Rut = new Rut { Value = null } + }); + + SaveChanges(); + } + } + + public class RotRutCase + { + public int Id { get; set; } + public string Buyer { get; set; } + public Rot Rot { get; set; } + public Rut Rut { get; set; } + } + + public class Rot + { + public int? ServiceType { get; set; } + public string ApartmentNo { get; set; } + } + + public class Rut + { + public int? Value { get; set; } + } + protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) { return base.AddOptions(builder).ConfigureWarnings( diff --git a/test/EFCore.Relational.Specification.Tests/TestUtilities/RelationalTestHelpers.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/RelationalTestHelpers.cs new file mode 100644 index 00000000000..3371c004dee --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/TestUtilities/RelationalTestHelpers.cs @@ -0,0 +1,77 @@ +// 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.Design.Internal; +using Microsoft.EntityFrameworkCore.Internal; + +namespace Microsoft.EntityFrameworkCore.TestUtilities; + +public abstract class RelationalTestHelpers : TestHelpers +{ + protected virtual EntityFrameworkDesignServicesBuilder CreateEntityFrameworkDesignServicesBuilder(IServiceCollection services) + => new(services); + + public IServiceProvider CreateDesignServiceProvider( + IServiceCollection customServices = null, + Action replaceServices = null, + Type additionalDesignTimeServices = null, + IOperationReporter reporter = null) + => CreateDesignServiceProvider( + CreateContext().GetService().Name, + customServices, + replaceServices, + additionalDesignTimeServices, + reporter); + + public IServiceProvider CreateDesignServiceProvider( + string provider, + IServiceCollection customServices = null, + Action replaceServices = null, + Type additionalDesignTimeServices = null, + IOperationReporter reporter = null) + => CreateServiceProvider( + customServices, services => + { + if (replaceServices != null) + { + var builder = CreateEntityFrameworkDesignServicesBuilder(services); + replaceServices(builder); + } + + if (additionalDesignTimeServices != null) + { + ConfigureDesignTimeServices(additionalDesignTimeServices, services); + } + + ConfigureProviderServices(provider, services); + services.AddEntityFrameworkDesignTimeServices(reporter); + + return services; + }); + + private void ConfigureProviderServices(string provider, IServiceCollection services) + { + var providerAssembly = Assembly.Load(new AssemblyName(provider)); + + var providerServicesAttribute = providerAssembly.GetCustomAttribute(); + if (providerServicesAttribute == null) + { + throw new InvalidOperationException(DesignStrings.CannotFindDesignTimeProviderAssemblyAttribute(provider)); + } + + var designTimeServicesType = providerAssembly.GetType( + providerServicesAttribute.TypeName, + throwOnError: true, + ignoreCase: false)!; + + ConfigureDesignTimeServices(designTimeServicesType, services); + } + + private static void ConfigureDesignTimeServices( + Type designTimeServicesType, + IServiceCollection services) + { + var designTimeServices = (IDesignTimeServices)Activator.CreateInstance(designTimeServicesType)!; + designTimeServices.ConfigureDesignTimeServices(services); + } +} diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestOperationReporter.cs similarity index 100% rename from test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs rename to test/EFCore.Relational.Specification.Tests/TestUtilities/TestOperationReporter.cs diff --git a/test/EFCore.Relational.Tests/Design/AnnotationCodeGeneratorTest.cs b/test/EFCore.Relational.Tests/Design/AnnotationCodeGeneratorTest.cs index bb371017233..d390013a875 100644 --- a/test/EFCore.Relational.Tests/Design/AnnotationCodeGeneratorTest.cs +++ b/test/EFCore.Relational.Tests/Design/AnnotationCodeGeneratorTest.cs @@ -45,7 +45,7 @@ public void GenerateFluentApi_IProperty_works_with_collation() } private ModelBuilder CreateModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); private AnnotationCodeGenerator CreateGenerator() => new( diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index a3e7b2df0b5..36a75e7df75 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -3341,5 +3341,5 @@ protected virtual TestHelpers.TestModelBuilder CreateModelBuilderWithoutConventi typeof(T))); protected override TestHelpers TestHelpers - => RelationalTestHelpers.Instance; + => FakeRelationalTestHelpers.Instance; } diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs index c52cdfcb20f..66bd0ea6c1f 100644 --- a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs @@ -95,7 +95,7 @@ public void Throws_when_adding_a_function_returning_a_scalar() } private static TestHelpers.TestModelBuilder CreateModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); private static IModel Finalize(TestHelpers.TestModelBuilder modelBuilder) => modelBuilder.FinalizeModel(); diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/SequenceUniquificationConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/SequenceUniquificationConventionTest.cs index 081d520258e..14b97a8fad0 100644 --- a/test/EFCore.Relational.Tests/Metadata/Conventions/SequenceUniquificationConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/Conventions/SequenceUniquificationConventionTest.cs @@ -73,8 +73,8 @@ private ModelBuilder GetModelBuilder() } private ProviderConventionSetBuilderDependencies CreateDependencies() - => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + => FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); private RelationalConventionSetBuilderDependencies CreateRelationalDependencies() - => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + => FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); } diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs index 2ed3b5c64d7..b567393708e 100644 --- a/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs @@ -202,8 +202,8 @@ private ModelBuilder GetModelBuilder(DbContext dbContext = null) } private ProviderConventionSetBuilderDependencies CreateDependencies() - => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + => FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); private RelationalConventionSetBuilderDependencies CreateRelationalDependencies() - => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + => FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); } diff --git a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs index a60a116d921..d5d03790cc5 100644 --- a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs +++ b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs @@ -841,5 +841,5 @@ public void DbFunction_Queryable_custom_translation() } private TestHelpers.TestModelBuilder GetModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); } diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs index 82a36d3e519..68916ff69f0 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs @@ -1538,7 +1538,7 @@ private void AssertIsGeneric(ReferenceReferenceBuilder _) } protected virtual ModelBuilder CreateConventionModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); private InternalModelBuilder CreateBuilder() => (InternalModelBuilder)CreateConventionModelBuilder().GetInfrastructure(); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalEntityTypeAttributeConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalEntityTypeAttributeConventionTest.cs index ab859f9a276..a69f060b9bd 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalEntityTypeAttributeConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalEntityTypeAttributeConventionTest.cs @@ -106,13 +106,13 @@ private InternalEntityTypeBuilder CreateInternalEntityTypeBuilder() } private ProviderConventionSetBuilderDependencies CreateDependencies() - => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + => FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); private RelationalConventionSetBuilderDependencies CreateRelationalDependencies() - => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + => FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); protected virtual ModelBuilder CreateConventionalModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); [Table("MyTable", Schema = "MySchema")] [Comment("Test table comment")] diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalIndexTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalIndexTest.cs index 816b25b308b..164ee26a200 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalIndexTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalIndexTest.cs @@ -51,7 +51,7 @@ public void IndexAttribute_database_name_can_be_overriden_using_fluent_api() } protected virtual ModelBuilder CreateConventionModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); [Index(nameof(A), nameof(B), Name = "IndexOnAAndB", IsUnique = true)] [Index(nameof(B), nameof(C), Name = "IndexOnBAndC")] diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs index 4561c23cf1c..f12ded84343 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs @@ -618,7 +618,7 @@ public void RemoveCheckConstraint_returns_null_when_constraint_is_missing() } protected virtual ModelBuilder CreateConventionModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); private enum MyEnum : byte { diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 28c1f21f2f0..ceccac9f034 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -3023,7 +3023,7 @@ private static IRelationalModel Finalize(TestHelpers.TestModelBuilder modelBuild => modelBuilder.FinalizeModel(designTime: true).GetRelationalModel(); protected virtual TestHelpers.TestModelBuilder CreateConventionModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder( + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder( configureContext: b => b.ConfigureWarnings(w => w.Default(WarningBehavior.Throw) diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs index 3507b167d20..ba7436d7b8d 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs @@ -146,13 +146,13 @@ private InternalEntityTypeBuilder CreateInternalEntityTypeBuilder() } private ProviderConventionSetBuilderDependencies CreateDependencies() - => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + => FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); private RelationalConventionSetBuilderDependencies CreateRelationalDependencies() - => RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + => FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); protected virtual ModelBuilder CreateConventionalModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); private class A { diff --git a/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs b/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs index 19accbc96e3..093c34be647 100644 --- a/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs @@ -93,7 +93,7 @@ public void RemoveTrigger_returns_null_when_trigger_is_missing() } protected virtual ModelBuilder CreateConventionModelBuilder() - => RelationalTestHelpers.Instance.CreateConventionBuilder(); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); private class Customer { diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index d68920617b0..47c449c5160 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -11986,5 +11986,5 @@ public void Model_differ_does_not_detect_entity_type_mapped_to_TVF() skipSourceConventions: true); protected override TestHelpers TestHelpers - => RelationalTestHelpers.Instance; + => FakeRelationalTestHelpers.Instance; } diff --git a/test/EFCore.Relational.Tests/RelationalDatabaseFacadeExtensionsTest.cs b/test/EFCore.Relational.Tests/RelationalDatabaseFacadeExtensionsTest.cs index 3ff9d43feb3..228fc1c45c2 100644 --- a/test/EFCore.Relational.Tests/RelationalDatabaseFacadeExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/RelationalDatabaseFacadeExtensionsTest.cs @@ -13,7 +13,7 @@ public class RelationalDatabaseFacadeExtensionsTest [ConditionalFact] public void Return_true_if_relational() { - using var context = RelationalTestHelpers.Instance.CreateContext(); + using var context = FakeRelationalTestHelpers.Instance.CreateContext(); Assert.True(context.Database.IsRelational()); } @@ -28,7 +28,7 @@ public void Return_false_if_inMemory() public void GetDbConnection_returns_the_current_connection() { var dbConnection = new FakeDbConnection("A=B"); - var context = RelationalTestHelpers.Instance.CreateContext(); + var context = FakeRelationalTestHelpers.Instance.CreateContext(); ((FakeRelationalConnection)context.GetService()).UseConnection(dbConnection); @@ -55,7 +55,7 @@ public void Relational_specific_methods_throws_when_non_relational_provider_is_i public async Task Can_open_the_underlying_connection(bool async) { var dbConnection = new FakeDbConnection("A=B"); - var context = RelationalTestHelpers.Instance.CreateContext(); + var context = FakeRelationalTestHelpers.Instance.CreateContext(); ((FakeRelationalConnection)context.GetService()).UseConnection(dbConnection); @@ -77,7 +77,7 @@ public async Task Can_open_the_underlying_connection(bool async) public async Task Can_close_the_underlying_connection(bool async) { var dbConnection = new FakeDbConnection("A=B"); - var context = RelationalTestHelpers.Instance.CreateContext(); + var context = FakeRelationalTestHelpers.Instance.CreateContext(); ((FakeRelationalConnection)context.GetService()).UseConnection(dbConnection); @@ -101,7 +101,7 @@ public async Task Can_close_the_underlying_connection(bool async) public async Task Can_begin_transaction_with_isolation_level(bool async) { var dbConnection = new FakeDbConnection("A=B"); - var context = RelationalTestHelpers.Instance.CreateContext(); + var context = FakeRelationalTestHelpers.Instance.CreateContext(); ((FakeRelationalConnection)context.GetService()).UseConnection(dbConnection); var transaction = async @@ -116,7 +116,7 @@ public async Task Can_begin_transaction_with_isolation_level(bool async) public void Can_use_transaction() { var dbConnection = new FakeDbConnection("A=B"); - var context = RelationalTestHelpers.Instance.CreateContext(); + var context = FakeRelationalTestHelpers.Instance.CreateContext(); ((FakeRelationalConnection)context.GetService()).UseConnection(dbConnection); var transaction = new FakeDbTransaction(dbConnection, IsolationLevel.Chaos); @@ -211,7 +211,7 @@ public void GetMigrations_works() var migrationsAssembly = new FakeIMigrationsAssembly { Migrations = migrations.ToDictionary(x => x, x => default(TypeInfo)) }; - var db = RelationalTestHelpers.Instance.CreateContext( + var db = FakeRelationalTestHelpers.Instance.CreateContext( new ServiceCollection().AddSingleton(migrationsAssembly)); Assert.Equal(migrations, db.Database.GetMigrations()); @@ -239,7 +239,7 @@ public async Task GetAppliedMigrations_works(bool async) var repository = new FakeHistoryRepository { AppliedMigrations = migrations.Select(id => new HistoryRow(id, "1.1.0")).ToList() }; - var context = RelationalTestHelpers.Instance.CreateContext( + var context = FakeRelationalTestHelpers.Instance.CreateContext( new ServiceCollection().AddSingleton(repository)); Assert.Equal( @@ -303,7 +303,7 @@ public async Task GetPendingMigrations_works(bool async) AppliedMigrations = appliedMigrations.Select(id => new HistoryRow(id, "1.1.0")).ToList() }; - var context = RelationalTestHelpers.Instance.CreateContext( + var context = FakeRelationalTestHelpers.Instance.CreateContext( new ServiceCollection() .AddSingleton(repository) .AddSingleton(migrationsAssembly)); diff --git a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs index 6dd8397a957..470afda0018 100644 --- a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs +++ b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs @@ -26,7 +26,7 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() var key = entityType.AddKey(property, ConfigurationSource.Convention); var foreignKey = new ForeignKey(new List { property }, key, entityType, entityType, ConfigurationSource.Convention); var index = new Index(new List { property }, "IndexName", entityType, ConfigurationSource.Convention); - var contextServices = RelationalTestHelpers.Instance.CreateContextServices(model.FinalizeModel()); + var contextServices = FakeRelationalTestHelpers.Instance.CreateContextServices(model.FinalizeModel()); var updateEntry = new InternalEntityEntry(contextServices.GetRequiredService(), entityType, new object()); var columnOperation = new AddColumnOperation { diff --git a/test/EFCore.Relational.Tests/Storage/RelationalDatabaseFacadeExtensionsTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalDatabaseFacadeExtensionsTest.cs index b8c172f69df..677513f7a7b 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalDatabaseFacadeExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalDatabaseFacadeExtensionsTest.cs @@ -253,8 +253,8 @@ private class ThudContext : DbContext { public ThudContext() : base( - RelationalTestHelpers.Instance.CreateOptions( - RelationalTestHelpers.Instance.CreateServiceProvider( + FakeRelationalTestHelpers.Instance.CreateOptions( + FakeRelationalTestHelpers.Instance.CreateServiceProvider( new ServiceCollection() .AddScoped()))) { diff --git a/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs index d13992fb384..47bdee99c40 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs @@ -49,7 +49,7 @@ public void Can_add_type_mapped_parameter_by_property(bool nullable) TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()); - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity("MyType").Property("MyProp").IsRequired(!nullable); diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs index 46ad3f4e069..e79abd11916 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs @@ -345,5 +345,5 @@ public static RelationalTypeMapping GetMapping( => typeMappingSource.FindMapping(property); protected override ModelBuilder CreateModelBuilder(Action configure = null) - => RelationalTestHelpers.Instance.CreateConventionBuilder(configureModel: configure); + => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(configureModel: configure); } diff --git a/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs b/test/EFCore.Relational.Tests/TestUtilities/FakeRelationalTestHelpers.cs similarity index 75% rename from test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs rename to test/EFCore.Relational.Tests/TestUtilities/FakeRelationalTestHelpers.cs index 400de7b963e..0b4703206ea 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/FakeRelationalTestHelpers.cs @@ -5,15 +5,15 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities; -public class RelationalTestHelpers : TestHelpers +public class FakeRelationalTestHelpers : TestHelpers { - protected RelationalTestHelpers() + protected FakeRelationalTestHelpers() { } - public static RelationalTestHelpers Instance { get; } = new(); + public static FakeRelationalTestHelpers Instance { get; } = new(); - protected override EntityFrameworkDesignServicesBuilder CreateEntityFrameworkDesignServicesBuilder( + protected virtual EntityFrameworkDesignServicesBuilder CreateEntityFrameworkDesignServicesBuilder( IServiceCollection services) => new EntityFrameworkRelationalDesignServicesBuilder(services); diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs index 16f0247e0d1..99df9f0fd93 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalConventionSetBuilder.cs @@ -13,5 +13,5 @@ public TestRelationalConventionSetBuilder( } public static ConventionSet Build() - => ConventionSet.CreateConventionSet(RelationalTestHelpers.Instance.CreateContext()); + => ConventionSet.CreateConventionSet(FakeRelationalTestHelpers.Instance.CreateContext()); } diff --git a/test/EFCore.Relational.Tests/Update/BatchExecutorTest.cs b/test/EFCore.Relational.Tests/Update/BatchExecutorTest.cs index 4e5cbc3f560..e166f9a05ce 100644 --- a/test/EFCore.Relational.Tests/Update/BatchExecutorTest.cs +++ b/test/EFCore.Relational.Tests/Update/BatchExecutorTest.cs @@ -78,7 +78,7 @@ private static readonly IServiceProvider _serviceProvider .BuildServiceProvider(validateScopes: true); public TestContext() - : base(RelationalTestHelpers.Instance.CreateOptions(_serviceProvider)) + : base(FakeRelationalTestHelpers.Instance.CreateOptions(_serviceProvider)) { } diff --git a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs index 5b4fd46cda7..8d19a4998ba 100644 --- a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs @@ -339,7 +339,7 @@ public void BatchCommands_sorts_entities_while_reassigning_child_tree() [ConditionalFact] public void BatchCommands_creates_batches_lazily() { - var configuration = RelationalTestHelpers.Instance.CreateContextServices( + var configuration = FakeRelationalTestHelpers.Instance.CreateContextServices( new ServiceCollection().AddScoped(), CreateFKOneToManyModelWithGeneratedIds()); @@ -996,7 +996,7 @@ public void BatchCommands_creates_batch_on_incomplete_updates_for_shared_table_n } private static IServiceProvider CreateContextServices(IModel model) - => RelationalTestHelpers.Instance.CreateContextServices(model); + => FakeRelationalTestHelpers.Instance.CreateContextServices(model); public List CreateBatches( IUpdateEntry[] entries, @@ -1012,7 +1012,7 @@ public ICommandBatchPreparer CreateCommandBatchPreparer( bool sensitiveLogging = false) { modificationCommandBatchFactory ??= - RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); + FakeRelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); var loggingOptions = new LoggingOptions(); if (sensitiveLogging) @@ -1033,7 +1033,7 @@ public ICommandBatchPreparer CreateCommandBatchPreparer( private static IModel CreateSimpleFKModel() { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity( b => @@ -1055,7 +1055,7 @@ private static IModel CreateSimpleFKModel() private static IModel CreateFKOneToManyModelWithGeneratedIds() { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity( b => @@ -1077,7 +1077,7 @@ private static IModel CreateFKOneToManyModelWithGeneratedIds() private static IModel CreateCyclicFKModel() { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity( b => @@ -1105,7 +1105,7 @@ private static IModel CreateCyclicFKModel() private static IModel CreateCyclicFkWithTailModel() { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity( b => @@ -1141,7 +1141,7 @@ private static IModel CreateCyclicFkWithTailModel() private static IModel CreateTwoLevelFKModel() { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity(); @@ -1166,7 +1166,7 @@ private static IModel CreateTwoLevelFKModel() private static IModel CreateSharedTableModel() { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity( b => diff --git a/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs b/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs index 6de424935d8..cdc176bb4a3 100644 --- a/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs @@ -12,7 +12,7 @@ public class ModificationCommandComparerTest [ConditionalFact] public void Compare_returns_0_only_for_commands_that_are_equal() { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); var entityType = modelBuilder.Model.AddEntityType(typeof(object)); var key = entityType.AddProperty("Id", typeof(int)); entityType.SetPrimaryKey(key); @@ -163,7 +163,7 @@ public void Compare_returns_0_only_for_entries_that_have_same_key_values() private void Compare_returns_0_only_for_entries_that_have_same_key_values_generic(T value1, T value2) { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); var entityType = modelBuilder.Model.AddEntityType(typeof(object)); var keyProperty = entityType.AddProperty("Id", typeof(T)); diff --git a/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs b/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs index c1e05c40b2a..13047fcda55 100644 --- a/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs +++ b/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs @@ -442,7 +442,7 @@ private class T1 private static IModel BuildModel(bool generateKeyValues, bool computeNonKeyValue) { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); var model = modelBuilder.Model; var entityType = model.AddEntityType(typeof(T1)); @@ -473,7 +473,7 @@ private static InternalEntityEntry CreateEntry( { var model = BuildModel(generateKeyValues, computeNonKeyValue); - return RelationalTestHelpers.Instance.CreateInternalEntry( + return FakeRelationalTestHelpers.Instance.CreateInternalEntry( model, entityState, new diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index 4e065f16c7b..b89d2879671 100644 --- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs +++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs @@ -628,7 +628,7 @@ private class T1 private static IModel BuildModel(bool generateKeyValues, bool computeNonKeyValue) { - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); var entityType = modelBuilder.Entity(); entityType.Property(t => t.Id).HasColumnName("Col1"); @@ -654,7 +654,7 @@ private static InternalEntityEntry CreateEntry( { var model = BuildModel(generateKeyValues, computeNonKeyValue); - return RelationalTestHelpers.Instance.CreateInternalEntry( + return FakeRelationalTestHelpers.Instance.CreateInternalEntry( model, entityState, new T1 { Id = overrideKeyValues ? 1 : default, @@ -692,7 +692,7 @@ private static ModificationCommandBatchFactoryDependencies CreateDependencies( var logger = new FakeRelationalCommandDiagnosticsLogger(); sqlGenerator ??= new FakeSqlGenerator( - RelationalTestHelpers.Instance.CreateContextServices() + FakeRelationalTestHelpers.Instance.CreateContextServices() .GetRequiredService()); return new ModificationCommandBatchFactoryDependencies( diff --git a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs index 34c232b0733..37d1b2a81eb 100644 --- a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs +++ b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs @@ -107,7 +107,7 @@ protected override void AppendUpdateOperation_for_computed_property_verification stringBuilder.ToString()); protected override TestHelpers TestHelpers - => RelationalTestHelpers.Instance; + => FakeRelationalTestHelpers.Instance; protected override string RowsAffected => "provider_specific_rowcount()"; diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index cbacc4a29b6..30e19e30d12 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -1563,54 +1563,6 @@ protected class Profile15 public virtual Profile15 Profile4 { get; set; } } - [ConditionalFact] - public virtual void ForeignKey_to_ForeignKey_on_many_to_many() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity( - entity => - { - entity.HasMany(d => d.Profile16s) - .WithMany(p => p.Login16s) - .UsingEntity>( - "Login16Profile16", - l => l.HasOne().WithMany().HasForeignKey("Profile16Id"), - r => r.HasOne().WithMany().HasForeignKey("Login16Id"), - j => - { - j.HasKey("Login16Id", "Profile16Id"); - - j.ToTable("Login16Profile16"); - }); - }); - - var model = Validate(modelBuilder); - - var login = modelBuilder.Model.FindEntityType(typeof(Login16)); - var logins = login.FindSkipNavigation(nameof(Login16.Profile16s)); - var join = logins.JoinEntityType; - Assert.Equal(2, join.GetProperties().Count()); - Assert.False(GetProperty(model, "Login16Id").IsForeignKey()); - Assert.False(GetProperty(model, "Profile16Id").IsForeignKey()); - } - - public class Login16 - { - public int Login16Id { get; set; } - - [ForeignKey("Login16Id")] - public virtual ICollection Profile16s { get; set; } - } - - public class Profile16 - { - public int Profile16Id { get; set; } - - [ForeignKey("Profile16Id")] - public virtual ICollection Login16s { get; set; } - } - [ConditionalFact] public virtual void ForeignKeyAttribute_configures_relationships_when_inverse_on_derived() { diff --git a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj index c2ee41f6bfa..ca96bd27d4f 100644 --- a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj +++ b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj @@ -45,7 +45,6 @@ - diff --git a/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs index 47095868bce..5293339a155 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs @@ -402,95 +402,4 @@ protected class IntermediateOwnedEntity public CustomerData CustomerData { get; set; } public SupplierData SupplierData { get; set; } } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Owned_entity_with_all_null_properties_materializes_when_not_containing_another_owned_entity(bool async) - { - var contextFactory = await InitializeAsync(seed: c => c.Seed()); - - using var context = contextFactory.CreateContext(); - var query = context.RotRutCases.OrderBy(e => e.Buyer); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Collection(result, - t => - { - Assert.Equal("Buyer1", t.Buyer); - Assert.NotNull(t.Rot); - Assert.Equal(1, t.Rot.ServiceType); - Assert.Equal("1", t.Rot.ApartmentNo); - Assert.NotNull(t.Rut); - Assert.Equal(1, t.Rut.Value); - }, - t => - { - Assert.Equal("Buyer2", t.Buyer); - // Cannot verify owned entities here since they differ between relational/in-memory - }); - } - - protected class MyContext28247 : DbContext - { - public MyContext28247(DbContextOptions options) - : base(options) - { - } - - public DbSet RotRutCases { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(b => - { - b.ToTable("RotRutCases"); - - b.OwnsOne(e => e.Rot); - b.OwnsOne(e => e.Rut); - }); - } - - public void Seed() - { - Add( - new RotRutCase - { - Buyer = "Buyer1", - Rot = new Rot { ServiceType = 1, ApartmentNo = "1" }, - Rut = new Rut { Value = 1 } - }); - - Add( - new RotRutCase - { - Buyer = "Buyer2", - Rot = new Rot { ServiceType = null, ApartmentNo = null }, - Rut = new Rut { Value = null } - }); - - SaveChanges(); - } - } - - public class RotRutCase - { - public int Id { get; set; } - public string Buyer { get; set; } - public Rot Rot { get; set; } - public Rut Rut { get; set; } - } - - public class Rot - { - public int? ServiceType { get; set; } - public string ApartmentNo { get; set; } - } - - public class Rut - { - public int? Value { get; set; } - } } diff --git a/test/EFCore.Specification.Tests/QueryExpressionInterceptionTestBase.cs b/test/EFCore.Specification.Tests/QueryExpressionInterceptionTestBase.cs index 410da6cef9f..91a3babf548 100644 --- a/test/EFCore.Specification.Tests/QueryExpressionInterceptionTestBase.cs +++ b/test/EFCore.Specification.Tests/QueryExpressionInterceptionTestBase.cs @@ -1,8 +1,6 @@ // 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; public abstract class QueryExpressionInterceptionTestBase : InterceptionTestBase diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs index 75a320006b4..4e893901ec9 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs @@ -34,7 +34,7 @@ public DbContextOptions CreateOptions(IServiceProvider serviceProvider = null) public IServiceProvider CreateServiceProvider(IServiceCollection customServices = null) => CreateServiceProvider(customServices, AddProviderServices); - private static IServiceProvider CreateServiceProvider( + protected static IServiceProvider CreateServiceProvider( IServiceCollection customServices, Func addProviderServices) { @@ -52,73 +52,6 @@ private static IServiceProvider CreateServiceProvider( return services.BuildServiceProvider(); // No scope validation; test doubles violate scopes, but only resolved once. } - protected virtual EntityFrameworkDesignServicesBuilder CreateEntityFrameworkDesignServicesBuilder(IServiceCollection services) - => new(services); - - public IServiceProvider CreateDesignServiceProvider( - IServiceCollection customServices = null, - Action replaceServices = null, - Type additionalDesignTimeServices = null, - IOperationReporter reporter = null) - => CreateDesignServiceProvider( - CreateContext().GetService().Name, - customServices, - replaceServices, - additionalDesignTimeServices, - reporter); - - public IServiceProvider CreateDesignServiceProvider( - string provider, - IServiceCollection customServices = null, - Action replaceServices = null, - Type additionalDesignTimeServices = null, - IOperationReporter reporter = null) - => CreateServiceProvider( - customServices, services => - { - if (replaceServices != null) - { - var builder = CreateEntityFrameworkDesignServicesBuilder(services); - replaceServices(builder); - } - - if (additionalDesignTimeServices != null) - { - ConfigureDesignTimeServices(additionalDesignTimeServices, services); - } - - ConfigureProviderServices(provider, services); - services.AddEntityFrameworkDesignTimeServices(reporter); - - return services; - }); - - private void ConfigureProviderServices(string provider, IServiceCollection services) - { - var providerAssembly = Assembly.Load(new AssemblyName(provider)); - - var providerServicesAttribute = providerAssembly.GetCustomAttribute(); - if (providerServicesAttribute == null) - { - throw new InvalidOperationException(DesignStrings.CannotFindDesignTimeProviderAssemblyAttribute(provider)); - } - - var designTimeServicesType = providerAssembly.GetType( - providerServicesAttribute.TypeName, - throwOnError: true, - ignoreCase: false)!; - - ConfigureDesignTimeServices(designTimeServicesType, services); - } - - private static void ConfigureDesignTimeServices( - Type designTimeServicesType, - IServiceCollection services) - { - var designTimeServices = (IDesignTimeServices)Activator.CreateInstance(designTimeServicesType)!; - designTimeServices.ConfigureDesignTimeServices(services); - } - public abstract IServiceCollection AddProviderServices(IServiceCollection services); public DbContextOptionsBuilder AddProviderOptions(DbContextOptionsBuilder optionsBuilder) diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs index c5f29cab1e3..dd27b926947 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs @@ -6980,7 +6980,7 @@ protected override string StoreName protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; - public override TestHelpers TestHelpers + public override RelationalTestHelpers TestHelpers => SqlServerTestHelpers.Instance; protected override IServiceCollection AddServices(IServiceCollection serviceCollection) diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestHelpers.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestHelpers.cs index 2f4d2624ed9..b7b7b7329d2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestHelpers.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestHelpers.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities; -public class SqlServerTestHelpers : TestHelpers +public class SqlServerTestHelpers : RelationalTestHelpers { protected SqlServerTestHelpers() { diff --git a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs index 5c737f568bd..5f8e1ade98f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs @@ -1218,7 +1218,7 @@ protected override string StoreName protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; - public override TestHelpers TestHelpers + public override RelationalTestHelpers TestHelpers => SqliteTestHelpers.Instance; protected override IServiceCollection AddServices(IServiceCollection serviceCollection) diff --git a/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestHelpers.cs b/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestHelpers.cs index 1b0386ab51b..76a591602d0 100644 --- a/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestHelpers.cs +++ b/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestHelpers.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities; -public class SqliteTestHelpers : TestHelpers +public class SqliteTestHelpers : RelationalTestHelpers { protected SqliteTestHelpers() { From 9568a5a5ac7e0250e706387d476af7117e425e15 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 3 Aug 2022 10:40:34 -0700 Subject: [PATCH 4/8] Add position --- .../Metadata/IStoreStoredProcedure.cs | 2 +- .../IStoreStoredProcedureParameter.cs | 5 ++++ .../Metadata/Internal/RelationalModel.cs | 9 ++++++- .../Metadata/Internal/StoreStoredProcedure.cs | 2 +- .../Internal/StoreStoredProcedureParameter.cs | 19 ++++++++++++-- .../CSharpRuntimeModelCodeGeneratorTest.cs | 26 +++++++++---------- .../RelationalModelBuilderTest.cs | 2 +- 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs b/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs index b78c5b8c360..a74c13a01df 100644 --- a/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs @@ -23,7 +23,7 @@ public interface IStoreStoredProcedure : ITableBase /// /// Gets the parameters for this stored procedures. /// - IEnumerable Parameters { get; } + IReadOnlyList Parameters { get; } /// /// Gets the parameter with the given name. Returns diff --git a/src/EFCore.Relational/Metadata/IStoreStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/IStoreStoredProcedureParameter.cs index de157b328e4..e2c3b992ae8 100644 --- a/src/EFCore.Relational/Metadata/IStoreStoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/IStoreStoredProcedureParameter.cs @@ -26,6 +26,11 @@ public interface IStoreStoredProcedureParameter : IColumnBase /// ParameterDirection Direction { get; } + /// + /// Gets the 0-based position of the parameter in the declaring stored procedure. + /// + int Position { get; } + /// /// Returns the property mapping for the given entity type. /// diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 3fd102835fc..698bd1af37f 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -1038,14 +1038,17 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( RelationalAnnotationNames.UpdateStoredProcedureResultColumnMappings), _ => throw new Exception("Unexpected stored procedure type: " + identifier.StoreObjectType) }; - + + var position = -1; foreach (var parameter in storedProcedure.Parameters) { + position++; if (parameter.PropertyName == null) { GetOrCreateStoreStoredProcedureParameter( parameter, null, + position, storeStoredProcedure, identifier, relationalTypeMappingSource); @@ -1069,6 +1072,7 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( GetOrCreateStoreStoredProcedureParameter( parameter, derivedProperty, + position, storeStoredProcedure, identifier, relationalTypeMappingSource); @@ -1083,6 +1087,7 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( var storeParameter = GetOrCreateStoreStoredProcedureParameter( parameter, property, + position, storeStoredProcedure, identifier, relationalTypeMappingSource); @@ -1196,6 +1201,7 @@ static StoreStoredProcedure GetOrCreateStoreStoredProcedure( static StoreStoredProcedureParameter GetOrCreateStoreStoredProcedureParameter( IStoredProcedureParameter parameter, IProperty? property, + int position, StoreStoredProcedure storeStoredProcedure, StoreObjectIdentifier identifier, IRelationalTypeMappingSource relationalTypeMappingSource) @@ -1208,6 +1214,7 @@ static StoreStoredProcedureParameter GetOrCreateStoreStoredProcedureParameter( name, property?.GetColumnType(identifier) ?? relationalTypeMappingSource.FindMapping(typeof(int))!.StoreType, + position, storeStoredProcedure, parameter.Direction) { diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs index 971e0d18a47..7f3dc33d0ba 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs @@ -156,7 +156,7 @@ IEnumerable IStoreStoredProcedure.EntityTypeMappings } /// - IEnumerable IStoreStoredProcedure.Parameters + IReadOnlyList IStoreStoredProcedure.Parameters { [DebuggerStepThrough] get => Parameters; diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs index 4bbe6e9ce9a..ef497dda3aa 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs @@ -23,10 +23,12 @@ public class StoreStoredProcedureParameter public StoreStoredProcedureParameter( string name, string type, + int position, StoreStoredProcedure storedProcedure, ParameterDirection direction) : base(name, type, storedProcedure) { + Position = position; Direction = direction; } @@ -38,9 +40,22 @@ public StoreStoredProcedureParameter( /// public virtual StoreStoredProcedure StoredProcedure => (StoreStoredProcedure)Table; - - /// + + /// + /// 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 ParameterDirection Direction { 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. + /// + public int Position { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 768c8ccfab6..26a64d52965 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -2596,11 +2596,11 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) true); var principalBaseId = insertSproc.AddParameter( - ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", true); + ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", false); var principalDerivedId = insertSproc.AddParameter( - ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", true); + ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", false); var baseId = insertSproc.AddParameter( - ""BaseId"", ParameterDirection.Output, false, ""Id"", true); + ""BaseId"", ParameterDirection.Output, false, ""Id"", false); baseId.AddAnnotation(""foo"", ""bar""); insertSproc.AddAnnotation(""foo"", ""bar1""); runtimeEntityType.AddAnnotation(""Relational:InsertStoredProcedure"", insertSproc); @@ -2613,7 +2613,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) false); var id = deleteSproc.AddParameter( - ""Id"", ParameterDirection.Input, false, ""Id"", true); + ""Id"", ParameterDirection.Input, false, ""Id"", false); runtimeEntityType.AddAnnotation(""Relational:DeleteStoredProcedure"", deleteSproc); var updateSproc = new RuntimeStoredProcedure( @@ -2624,11 +2624,11 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) false); var principalBaseId0 = updateSproc.AddParameter( - ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", true); + ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", false); var principalDerivedId0 = updateSproc.AddParameter( - ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", true); + ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", false); var id0 = updateSproc.AddParameter( - ""Id"", ParameterDirection.Input, false, ""Id"", true); + ""Id"", ParameterDirection.Input, false, ""Id"", false); runtimeEntityType.AddAnnotation(""Relational:UpdateStoredProcedure"", updateSproc); runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); @@ -2683,9 +2683,9 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) false); var principalBaseId = insertSproc.AddParameter( - ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", true); + ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", false); var principalDerivedId = insertSproc.AddParameter( - ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", true); + ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", false); var derivedId = insertSproc.AddResultColumn( ""DerivedId"", false, ""Id""); derivedId.AddAnnotation(""foo"", ""bar3""); @@ -2699,7 +2699,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) false); var id = deleteSproc.AddParameter( - ""Id"", ParameterDirection.Input, false, ""Id"", true); + ""Id"", ParameterDirection.Input, false, ""Id"", false); runtimeEntityType.AddAnnotation(""Relational:DeleteStoredProcedure"", deleteSproc); var updateSproc = new RuntimeStoredProcedure( @@ -2710,11 +2710,11 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) false); var principalBaseId0 = updateSproc.AddParameter( - ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", true); + ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", false); var principalDerivedId0 = updateSproc.AddParameter( - ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", true); + ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", false); var id0 = updateSproc.AddParameter( - ""Id"", ParameterDirection.Input, false, ""Id"", true); + ""Id"", ParameterDirection.Input, false, ""Id"", false); runtimeEntityType.AddAnnotation(""Relational:UpdateStoredProcedure"", updateSproc); runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 1b227335de6..56db52064d5 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -252,7 +252,7 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() Assert.Equal("mySchema", insertSproc.Schema); Assert.Equal(new[] { "BookId", "Discriminator" }, insertSproc.Parameters.Select(p => p.PropertyName)); Assert.Equal(new[] { "Id" }, insertSproc.ResultColumns.Select(p => p.PropertyName)); - Assert.True(insertSproc.FindParameter("Discriminator")!.ForOriginalValue); + Assert.False(insertSproc.FindParameter("Discriminator")!.ForOriginalValue); Assert.Null(insertSproc.FindParameter("Id")); Assert.Null(insertSproc.FindResultColumn("Discriminator")); Assert.False(insertSproc.FindResultColumn("Id")!.ForRowsAffected); From 9e948f58481ae644681977f01c6b74b6b1d5f06f Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 3 Aug 2022 10:58:00 -0700 Subject: [PATCH 5/8] Fix StoreParameter mapping --- src/EFCore.Relational/Metadata/Internal/RelationalModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 698bd1af37f..42e4b35ac6f 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -1220,6 +1220,8 @@ static StoreStoredProcedureParameter GetOrCreateStoreStoredProcedureParameter( { IsNullable = property?.IsColumnNullable(identifier) ?? false }; + + ((IRuntimeStoredProcedureParameter)parameter).StoreParameter = storeParameter; storeStoredProcedure.AddParameter(storeParameter); } else if (property?.IsColumnNullable(identifier) == false) From 01a4ccf44de4cf2437d804c92be37db805f9a979 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 3 Aug 2022 12:03:51 -0700 Subject: [PATCH 6/8] Add sproc return to the relational model --- .../Metadata/IStoreStoredProcedure.cs | 9 ++- .../Metadata/IStoreStoredProcedureReturn.cs | 55 ++++++++++++++++ .../Metadata/Internal/RelationalModel.cs | 12 +++- .../Metadata/Internal/StoreStoredProcedure.cs | 8 +++ .../Internal/StoreStoredProcedureParameter.cs | 2 +- .../Internal/StoreStoredProcedureReturn.cs | 64 +++++++++++++++++++ 6 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 src/EFCore.Relational/Metadata/IStoreStoredProcedureReturn.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs diff --git a/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs b/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs index a74c13a01df..c4f8c692c6a 100644 --- a/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs @@ -19,9 +19,14 @@ public interface IStoreStoredProcedure : ITableBase /// Gets the entity type mappings. /// new IEnumerable EntityTypeMappings { get; } - + + /// + /// Gets the return for this stored procedure. + /// + IStoreStoredProcedureReturn? Return { get; } + /// - /// Gets the parameters for this stored procedures. + /// Gets the parameters for this stored procedure. /// IReadOnlyList Parameters { get; } diff --git a/src/EFCore.Relational/Metadata/IStoreStoredProcedureReturn.cs b/src/EFCore.Relational/Metadata/IStoreStoredProcedureReturn.cs new file mode 100644 index 00000000000..6674dcfde6c --- /dev/null +++ b/src/EFCore.Relational/Metadata/IStoreStoredProcedureReturn.cs @@ -0,0 +1,55 @@ +// 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 the return value of a stored procedure. +/// +public interface IStoreStoredProcedureReturn : IColumnBase +{ + /// + /// Gets the containing stored procedure. + /// + IStoreStoredProcedure StoredProcedure { get; } + + /// + /// + /// 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); + + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"StoreStoredProcedureReturn: {Table.Name}."); + } + + builder.Append(Name).Append(" ("); + builder.Append(StoreType).Append(')'); + builder.Append(IsNullable ? " Nullable" : " NonNullable"); + builder.Append(')'); + + if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } + + return builder.ToString(); + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 42e4b35ac6f..a3eb2605ff1 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -1021,7 +1021,7 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( bool includesDerivedTypes, IRelationalTypeMappingSource relationalTypeMappingSource) { - var storeStoredProcedure = GetOrCreateStoreStoredProcedure(storedProcedure, model); + var storeStoredProcedure = GetOrCreateStoreStoredProcedure(storedProcedure, model, relationalTypeMappingSource); var identifier = storedProcedure.GetStoreIdentifier(); var storedProcedureMapping = new StoredProcedureMapping( @@ -1177,7 +1177,8 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( static StoreStoredProcedure GetOrCreateStoreStoredProcedure( IRuntimeStoredProcedure storedProcedure, - RelationalModel model) + RelationalModel model, + IRelationalTypeMappingSource relationalTypeMappingSource) { var storeStoredProcedure = (StoreStoredProcedure?)storedProcedure.StoreStoredProcedure; if (storeStoredProcedure == null) @@ -1186,6 +1187,13 @@ static StoreStoredProcedure GetOrCreateStoreStoredProcedure( if (storeStoredProcedure == null) { storeStoredProcedure = new StoreStoredProcedure(storedProcedure, model); + if (storedProcedure.AreRowsAffectedReturned) + { + storeStoredProcedure.Return = new StoreStoredProcedureReturn( + "", + relationalTypeMappingSource.FindMapping(typeof(int))!.StoreType, + storeStoredProcedure); + } model.StoredProcedures.Add((storeStoredProcedure.Name, storeStoredProcedure.Schema), storeStoredProcedure); } else diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs index 7f3dc33d0ba..3f23ac7f4d1 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs @@ -36,6 +36,14 @@ public StoreStoredProcedure(IRuntimeStoredProcedure sproc, RelationalModel model /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual SortedSet StoredProcedures { 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. + /// + public virtual IStoreStoredProcedureReturn? Return { get; set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs index ef497dda3aa..846a90545ce 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs @@ -55,7 +55,7 @@ public virtual StoreStoredProcedure StoredProcedure /// 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 int Position { get; } + public virtual int Position { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs new file mode 100644 index 00000000000..33c34977855 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs @@ -0,0 +1,64 @@ +// 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 StoreStoredProcedureReturn : ColumnBase, IStoreStoredProcedureReturn +{ + /// + /// 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 StoreStoredProcedureReturn( + string name, + string type, + StoreStoredProcedure storedProcedure) + : base(name, type, storedProcedure) + { + } + + /// + /// 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 StoreStoredProcedure StoredProcedure + => (StoreStoredProcedure)Table; + + /// + /// 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() + => ((IStoreStoredProcedureReturn)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( + () => ((IStoreStoredProcedureReturn)this).ToDebugString(), + () => ((IStoreStoredProcedureReturn)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IStoreStoredProcedure IStoreStoredProcedureReturn.StoredProcedure + { + [DebuggerStepThrough] + get => StoredProcedure; + } +} From 23345b26be9af06adc967f82e4dff8f1ac28671c Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 4 Aug 2022 00:32:45 -0700 Subject: [PATCH 7/8] Add more validation Add more tests Consolidate implementations --- .../RelationalModelValidator.cs | 125 ++++++-- .../StoredProcedureParameterBuilder.cs | 4 +- .../StoredProcedureResultColumnBuilder.cs | 4 +- .../Metadata/IConventionStoredProcedure.cs | 4 +- .../Metadata/IMutableStoredProcedure.cs | 4 +- .../Metadata/IReadOnlyStoredProcedure.cs | 4 +- .../Metadata/IStoredProcedure.cs | 4 +- ...InternalStoredProcedureParameterBuilder.cs | 52 ---- ...ernalStoredProcedureResultColumnBuilder.cs | 40 --- ...PropertyStoredProcedureParameterBuilder.cs | 148 --------- ...AffectedStoredProcedureParameterBuilder.cs | 146 --------- ...ectedStoredProcedureResultColumnBuilder.cs | 92 ------ .../InternalStoredProcedureBuilder.cs | 16 +- ...nternalStoredProcedureParameterBuilder.cs} | 42 +-- ...ernalStoredProcedureResultColumnBuilder.cs | 26 +- ...alValuePropertyStoredProcedureParameter.cs | 285 ------------------ .../Metadata/Internal/RelationalModel.cs | 2 +- .../RowsAffectedStoredProcedureParameter.cs | 238 --------------- ...RowsAffectedStoredProcedureResultColumn.cs | 203 ------------- .../Metadata/Internal/StoredProcedure.cs | 76 ++--- ...rameter.cs => StoredProcedureParameter.cs} | 47 ++- ...lumn.cs => StoredProcedureResultColumn.cs} | 41 ++- .../Metadata/RuntimeStoredProcedure.cs | 8 +- .../Properties/RelationalStrings.Designer.cs | 48 +++ .../Properties/RelationalStrings.resx | 18 ++ .../RelationalModelValidatorTest.cs | 96 +++++- .../RelationalModelBuilderTest.cs | 214 ++++++++++++- 27 files changed, 607 insertions(+), 1380 deletions(-) delete mode 100644 src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureParameterBuilder.cs delete mode 100644 src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureResultColumnBuilder.cs delete mode 100644 src/EFCore.Relational/Metadata/Internal/InternalOriginalValuePropertyStoredProcedureParameterBuilder.cs delete mode 100644 src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureParameterBuilder.cs delete mode 100644 src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureResultColumnBuilder.cs rename src/EFCore.Relational/Metadata/Internal/{InternalCurrentValuePropertyStoredProcedureParameterBuilder.cs => InternalStoredProcedureParameterBuilder.cs} (76%) delete mode 100644 src/EFCore.Relational/Metadata/Internal/OriginalValuePropertyStoredProcedureParameter.cs delete mode 100644 src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureParameter.cs delete mode 100644 src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureResultColumn.cs rename src/EFCore.Relational/Metadata/Internal/{CurrentValuePropertyStoredProcedureParameter.cs => StoredProcedureParameter.cs} (91%) rename src/EFCore.Relational/Metadata/Internal/{PropertyStoredProcedureResultColumn.cs => StoredProcedureResultColumn.cs} (90%) diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 12b6426a166..04e6106aa95 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -214,11 +214,14 @@ protected virtual void ValidateStoredProcedures( foreach (var entityType in model.GetEntityTypes()) { var mappingStrategy = entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy; + + var sprocCount = 0; var deleteStoredProcedure = entityType.GetDeleteStoredProcedure(); if (deleteStoredProcedure != null) { AddSproc(StoreObjectType.DeleteStoredProcedure, entityType, storedProcedures); ValidateSproc(deleteStoredProcedure, mappingStrategy); + sprocCount++; } var insertStoredProcedure = entityType.GetInsertStoredProcedure(); @@ -226,6 +229,7 @@ protected virtual void ValidateStoredProcedures( { AddSproc(StoreObjectType.InsertStoredProcedure, entityType, storedProcedures); ValidateSproc(insertStoredProcedure, mappingStrategy); + sprocCount++; } var updateStoredProcedure = entityType.GetUpdateStoredProcedure(); @@ -233,6 +237,14 @@ protected virtual void ValidateStoredProcedures( { AddSproc(StoreObjectType.UpdateStoredProcedure, entityType, storedProcedures); ValidateSproc(updateStoredProcedure, mappingStrategy); + sprocCount++; + } + + if (sprocCount > 0 + && sprocCount < 3 + && entityType.GetTableName() == null) + { + throw new InvalidOperationException(RelationalStrings.StoredProcedureUnmapped(entityType.DisplayName())); } } @@ -337,6 +349,8 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy } } + var originalValueProperties = new Dictionary(properties); + var storeGeneratedProperties = storeObjectIdentifier.StoreObjectType switch { StoreObjectType.InsertStoredProcedure @@ -346,25 +360,35 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy _ => new Dictionary() }; + var resultColumnNames = new HashSet(); foreach (var resultColumn in sproc.ResultColumns) { - if (resultColumn.PropertyName == null) + IProperty? property = null!; + if (resultColumn.PropertyName != null + && !properties.TryGetValue(resultColumn.PropertyName, out property)) { - continue; + throw new InvalidOperationException( + RelationalStrings.StoredProcedureResultColumnNotFound( + resultColumn.PropertyName, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); } - if (!properties.TryGetValue(resultColumn.PropertyName, out var property)) + if (!resultColumnNames.Add(resultColumn.Name)) { throw new InvalidOperationException( - RelationalStrings.StoredProcedureResultColumnNotFound( - resultColumn.PropertyName, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); + RelationalStrings.StoredProcedureDuplicateResultColumnName( + resultColumn.Name, storeObjectIdentifier.DisplayName())); + } + + if (resultColumn.PropertyName == null) + { + continue; } switch (storeObjectIdentifier.StoreObjectType) { case StoreObjectType.InsertStoredProcedure: case StoreObjectType.UpdateStoredProcedure: - if (!storeGeneratedProperties.Remove(property.Name)) + if (!storeGeneratedProperties.ContainsKey(property.Name)) { throw new InvalidOperationException( RelationalStrings.StoredProcedureResultColumnNotGenerated( @@ -381,19 +405,40 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy break; } } - + + var parameterNames = new HashSet(); foreach (var parameter in sproc.Parameters) { - if (parameter.PropertyName == null) + IProperty property = null!; + if (parameter.PropertyName != null) { - continue; + if (parameter.ForOriginalValue == true) + { + if (!originalValueProperties.TryGetAndRemove(parameter.PropertyName, out property)) + { + throw new InvalidOperationException( + RelationalStrings.StoredProcedureParameterNotFound( + parameter.PropertyName, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); + } + } + else if (!properties.TryGetAndRemove(parameter.PropertyName, out property)) + { + throw new InvalidOperationException( + RelationalStrings.StoredProcedureParameterNotFound( + parameter.PropertyName, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); + } } - if (!properties.TryGetAndRemove(parameter.PropertyName, out IProperty property)) + if (!parameterNames.Add(parameter.Name)) { throw new InvalidOperationException( - RelationalStrings.StoredProcedureParameterNotFound( - parameter.PropertyName, entityType.DisplayName(), storeObjectIdentifier.DisplayName())); + RelationalStrings.StoredProcedureDuplicateParameterName( + parameter.Name, storeObjectIdentifier.DisplayName())); + } + + if (parameter.PropertyName == null) + { + continue; } switch (storeObjectIdentifier.StoreObjectType) @@ -403,6 +448,15 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy if (parameter.Direction != ParameterDirection.Input && !storeGeneratedProperties.Remove(property.Name)) { + if (sproc.Parameters.Any(p => p.PropertyName == property.Name + && p.ForOriginalValue != parameter.ForOriginalValue + && p.Direction != ParameterDirection.Input)) + { + throw new InvalidOperationException( + RelationalStrings.StoredProcedureOutputParameterConflict( + entityType.DisplayName(), parameter.PropertyName, storeObjectIdentifier.DisplayName())); + } + throw new InvalidOperationException( RelationalStrings.StoredProcedureOutputParameterNotGenerated( entityType.DisplayName(), parameter.PropertyName, storeObjectIdentifier.DisplayName())); @@ -424,15 +478,6 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy break; } } - - if (storeGeneratedProperties.Count > 0) - { - throw new InvalidOperationException( - RelationalStrings.StoredProcedureGeneratedPropertiesNotMapped( - entityType.DisplayName(), - storeObjectIdentifier.DisplayName(), - storeGeneratedProperties.Values.Format())); - } foreach (var resultColumn in sproc.ResultColumns) { @@ -442,6 +487,22 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy } properties.Remove(resultColumn.PropertyName); + + if (!storeGeneratedProperties.Remove(resultColumn.PropertyName)) + { + throw new InvalidOperationException( + RelationalStrings.StoredProcedureResultColumnParameterConflict( + entityType.DisplayName(), resultColumn.PropertyName, storeObjectIdentifier.DisplayName())); + } + } + + if (storeGeneratedProperties.Count > 0) + { + throw new InvalidOperationException( + RelationalStrings.StoredProcedureGeneratedPropertiesNotMapped( + entityType.DisplayName(), + storeObjectIdentifier.DisplayName(), + storeGeneratedProperties.Values.Format())); } if (properties.Count > 0) @@ -479,6 +540,14 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy } } + foreach (var property in properties.Keys.ToList()) + { + if (!originalValueProperties.ContainsKey(property)) + { + properties.Remove(property); + } + } + if (properties.Count > 0) { throw new InvalidOperationException( @@ -488,6 +557,20 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy properties.Values.Format(false))); } } + + var missedConcurrencyToken = originalValueProperties.Values.FirstOrDefault(p => p.IsConcurrencyToken); + if (missedConcurrencyToken != null + && storeObjectIdentifier.StoreObjectType != StoreObjectType.InsertStoredProcedure + && (sproc.AreRowsAffectedReturned + || sproc.FindRowsAffectedParameter() != null + || sproc.FindRowsAffectedResultColumn() != null)) + { + throw new InvalidOperationException( + RelationalStrings.StoredProcedureConcurrencyTokenNotMapped( + entityType.DisplayName(), + storeObjectIdentifier.DisplayName(), + missedConcurrencyToken.Name)); + } } /// diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs index 3fe7f3baff5..0d276a1b3cf 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureParameterBuilder.cs @@ -24,7 +24,7 @@ public class StoredProcedureParameterBuilder : IInfrastructure [EntityFrameworkInternal] public StoredProcedureParameterBuilder( - IInternalStoredProcedureParameterBuilder builder, PropertyBuilder? propertyBuilder) + InternalStoredProcedureParameterBuilder builder, PropertyBuilder? propertyBuilder) { Builder = builder; PropertyBuilder = propertyBuilder; @@ -43,7 +43,7 @@ public virtual IMutableStoredProcedureParameter Metadata /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual IInternalStoredProcedureParameterBuilder Builder { get; } + protected virtual InternalStoredProcedureParameterBuilder Builder { get; } private PropertyBuilder? PropertyBuilder { get; } diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs index 406a10decbc..e8fffca8776 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureResultColumnBuilder.cs @@ -23,7 +23,7 @@ public class StoredProcedureResultColumnBuilder : IInfrastructure [EntityFrameworkInternal] public StoredProcedureResultColumnBuilder( - IInternalStoredProcedureResultColumnBuilder builder, PropertyBuilder? propertyBuilder) + InternalStoredProcedureResultColumnBuilder builder, PropertyBuilder? propertyBuilder) { Builder = builder; PropertyBuilder = propertyBuilder; @@ -42,7 +42,7 @@ public virtual IMutableStoredProcedureResultColumn Metadata /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual IInternalStoredProcedureResultColumnBuilder Builder { get; } + protected virtual InternalStoredProcedureResultColumnBuilder Builder { get; } private PropertyBuilder? PropertyBuilder { get; } diff --git a/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs b/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs index d3840b83ed8..e50a9e60e21 100644 --- a/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs @@ -57,7 +57,7 @@ public interface IConventionStoredProcedure : IReadOnlyStoredProcedure, IConvent /// /// Gets the parameters for this stored procedure. /// - new IEnumerable Parameters { get; } + new IReadOnlyList Parameters { get; } /// /// Returns the parameter corresponding to the given property. @@ -109,7 +109,7 @@ public interface IConventionStoredProcedure : IReadOnlyStoredProcedure, IConvent /// /// Gets the columns of the result for this stored procedure. /// - new IEnumerable ResultColumns { get; } + new IReadOnlyList ResultColumns { get; } /// /// Returns the result column corresponding to the given property. diff --git a/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs b/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs index 0780a082f2b..f2779df60f4 100644 --- a/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs @@ -40,7 +40,7 @@ public interface IMutableStoredProcedure : IReadOnlyStoredProcedure, IMutableAnn /// /// Gets the parameters for this stored procedure. /// - new IEnumerable Parameters { get; } + new IReadOnlyList Parameters { get; } /// /// Returns the parameter corresponding to the given property. @@ -89,7 +89,7 @@ public interface IMutableStoredProcedure : IReadOnlyStoredProcedure, IMutableAnn /// /// Gets the columns of the result for this stored procedure. /// - new IEnumerable ResultColumns { get; } + new IReadOnlyList ResultColumns { get; } /// /// Returns the result column corresponding to the given property. diff --git a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs index 3304300cf83..b8a30347cd4 100644 --- a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs @@ -68,7 +68,7 @@ public interface IReadOnlyStoredProcedure : IReadOnlyAnnotatable /// /// Gets the parameters for this stored procedure. /// - IEnumerable Parameters { get; } + IReadOnlyList Parameters { get; } /// /// Returns the parameter corresponding to the given property. @@ -97,7 +97,7 @@ public interface IReadOnlyStoredProcedure : IReadOnlyAnnotatable /// /// Gets the columns of the result for this stored procedure. /// - IEnumerable ResultColumns { get; } + IReadOnlyList ResultColumns { get; } /// /// Returns the result column corresponding to the given property. diff --git a/src/EFCore.Relational/Metadata/IStoredProcedure.cs b/src/EFCore.Relational/Metadata/IStoredProcedure.cs index 76caa9587c2..12d36a6ec54 100644 --- a/src/EFCore.Relational/Metadata/IStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IStoredProcedure.cs @@ -26,7 +26,7 @@ public interface IStoredProcedure : IReadOnlyStoredProcedure, IAnnotatable /// /// Gets the parameters for this stored procedure. /// - new IEnumerable Parameters { get; } + new IReadOnlyList Parameters { get; } /// /// Returns the parameter corresponding to the given property. @@ -55,7 +55,7 @@ public interface IStoredProcedure : IReadOnlyStoredProcedure, IAnnotatable /// /// Gets the columns of the result for this stored procedure. /// - new IEnumerable ResultColumns { get; } + new IReadOnlyList ResultColumns { get; } /// /// Returns the result column corresponding to the given property. diff --git a/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureParameterBuilder.cs deleted file mode 100644 index 54d1e416edd..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureParameterBuilder.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Data; - -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 interface IInternalStoredProcedureParameterBuilder -{ - /// - /// 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. - /// - IMutableStoredProcedureParameter Metadata { 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. - /// - IInternalStoredProcedureParameterBuilder? HasName(string name, ConfigurationSource 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. - /// - IInternalStoredProcedureParameterBuilder? HasDirection( - ParameterDirection direction, - ConfigurationSource 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. - /// - IInternalStoredProcedureParameterBuilder? HasAnnotation( - string name, - object? value, - ConfigurationSource configurationSource); -} diff --git a/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureResultColumnBuilder.cs deleted file mode 100644 index f2a0e1ba3cb..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/IInternalStoredProcedureResultColumnBuilder.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 interface IInternalStoredProcedureResultColumnBuilder -{ - /// - /// 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. - /// - IMutableStoredProcedureResultColumn Metadata { 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. - /// - IInternalStoredProcedureResultColumnBuilder? HasName(string name, ConfigurationSource 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. - /// - IInternalStoredProcedureResultColumnBuilder? HasAnnotation( - string name, - object? value, - ConfigurationSource configurationSource); -} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalOriginalValuePropertyStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalOriginalValuePropertyStoredProcedureParameterBuilder.cs deleted file mode 100644 index 1335e9da55a..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/InternalOriginalValuePropertyStoredProcedureParameterBuilder.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Data; - -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 InternalOriginalValuePropertyStoredProcedureParameterBuilder : - AnnotatableBuilder, - IConventionStoredProcedureParameterBuilder, - IInternalStoredProcedureParameterBuilder -{ - /// - /// 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 InternalOriginalValuePropertyStoredProcedureParameterBuilder( - OriginalValuePropertyStoredProcedureParameter parameter, IConventionModelBuilder modelBuilder) - : base(parameter, 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 virtual InternalOriginalValuePropertyStoredProcedureParameterBuilder? HasName( - string name, - ConfigurationSource configurationSource) - { - if (!CanSetName(name, configurationSource)) - { - return null; - } - - Metadata.SetName(name, 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 CanSetName( - string? name, - ConfigurationSource configurationSource) - => 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 InternalOriginalValuePropertyStoredProcedureParameterBuilder? HasDirection( - ParameterDirection direction, - ConfigurationSource configurationSource) - { - if (!CanSetDirection(direction, configurationSource)) - { - return null; - } - - Metadata.SetDirection(direction, 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 CanSetDirection( - ParameterDirection direction, - ConfigurationSource configurationSource) - => configurationSource == ConfigurationSource.Explicit - || Metadata.Direction == direction - || (configurationSource.Overrides(Metadata.GetDirectionConfigurationSource()) && Metadata.IsValid(direction)); - - /// - IConventionStoredProcedureParameter IConventionStoredProcedureParameterBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } - - /// - IMutableStoredProcedureParameter IInternalStoredProcedureParameterBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } - - /// - [DebuggerStepThrough] - IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasName(string name, bool fromDataAnnotation) - => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasName( - string name, ConfigurationSource configurationSource) - => HasName(name, configurationSource); - - /// - [DebuggerStepThrough] - bool IConventionStoredProcedureParameterBuilder.CanSetName(string? name, bool fromDataAnnotation) - => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasDirection( - ParameterDirection direction, bool fromDataAnnotation) - => HasDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasDirection( - ParameterDirection direction, ConfigurationSource configurationSource) - => HasDirection(direction, configurationSource); - - /// - [DebuggerStepThrough] - bool IConventionStoredProcedureParameterBuilder.CanSetDirection(ParameterDirection direction, bool fromDataAnnotation) - => CanSetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasAnnotation( - string name, object? value, ConfigurationSource configurationSource) - => (IInternalStoredProcedureParameterBuilder?)HasAnnotation(name, value, configurationSource); -} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureParameterBuilder.cs deleted file mode 100644 index 95362da92e1..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureParameterBuilder.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Data; - -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 InternalRowsAffectedStoredProcedureParameterBuilder : - AnnotatableBuilder, - IConventionStoredProcedureParameterBuilder, - IInternalStoredProcedureParameterBuilder -{ - /// - /// 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 InternalRowsAffectedStoredProcedureParameterBuilder( - RowsAffectedStoredProcedureParameter parameter, IConventionModelBuilder modelBuilder) - : base(parameter, 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 virtual InternalRowsAffectedStoredProcedureParameterBuilder? HasName( - string name, - ConfigurationSource configurationSource) - { - if (!CanSetName(name, configurationSource)) - { - return null; - } - - Metadata.Name = name; - - 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 CanSetName( - string? name, - ConfigurationSource configurationSource) - => 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 InternalRowsAffectedStoredProcedureParameterBuilder? HasDirection( - ParameterDirection direction, - ConfigurationSource configurationSource) - { - if (!CanSetDirection(direction, configurationSource)) - { - return null; - } - - Metadata.Direction = direction; - - 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 CanSetDirection( - ParameterDirection direction, - ConfigurationSource configurationSource) - => configurationSource == ConfigurationSource.Explicit; - - /// - IConventionStoredProcedureParameter IConventionStoredProcedureParameterBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } - - /// - IMutableStoredProcedureParameter IInternalStoredProcedureParameterBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } - - /// - [DebuggerStepThrough] - IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasName(string name, bool fromDataAnnotation) - => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasName( - string name, ConfigurationSource configurationSource) - => HasName(name, configurationSource); - - /// - [DebuggerStepThrough] - bool IConventionStoredProcedureParameterBuilder.CanSetName(string? name, bool fromDataAnnotation) - => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasDirection( - ParameterDirection direction, bool fromDataAnnotation) - => HasDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasDirection( - ParameterDirection direction, ConfigurationSource configurationSource) - => HasDirection(direction, configurationSource); - - /// - [DebuggerStepThrough] - bool IConventionStoredProcedureParameterBuilder.CanSetDirection(ParameterDirection direction, bool fromDataAnnotation) - => CanSetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasAnnotation( - string name, object? value, ConfigurationSource configurationSource) - => (IInternalStoredProcedureParameterBuilder?)HasAnnotation(name, value, configurationSource); -} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureResultColumnBuilder.cs deleted file mode 100644 index 559898869a5..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/InternalRowsAffectedStoredProcedureResultColumnBuilder.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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 InternalRowsAffectedStoredProcedureResultColumnBuilder : - AnnotatableBuilder, - IConventionStoredProcedureResultColumnBuilder, - IInternalStoredProcedureResultColumnBuilder -{ - /// - /// 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 InternalRowsAffectedStoredProcedureResultColumnBuilder( - RowsAffectedStoredProcedureResultColumn resultColumn, IConventionModelBuilder modelBuilder) - : base(resultColumn, 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 virtual InternalRowsAffectedStoredProcedureResultColumnBuilder? HasName( - string name, - ConfigurationSource configurationSource) - { - if (!CanSetName(name, configurationSource)) - { - return null; - } - - Metadata.Name = name; - - 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 CanSetName( - string? name, - ConfigurationSource configurationSource) - => configurationSource.Overrides(Metadata.GetNameConfigurationSource()) - || Metadata.Name == name; - - /// - IConventionStoredProcedureResultColumn IConventionStoredProcedureResultColumnBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } - - /// - IMutableStoredProcedureResultColumn IInternalStoredProcedureResultColumnBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } - - /// - [DebuggerStepThrough] - IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasName(string name, bool fromDataAnnotation) - => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - IInternalStoredProcedureResultColumnBuilder? IInternalStoredProcedureResultColumnBuilder.HasName(string name, ConfigurationSource configurationSource) - => HasName(name, configurationSource); - - /// - [DebuggerStepThrough] - bool IConventionStoredProcedureResultColumnBuilder.CanSetName(string? name, bool fromDataAnnotation) - => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - IInternalStoredProcedureResultColumnBuilder? IInternalStoredProcedureResultColumnBuilder.HasAnnotation(string name, object? value, ConfigurationSource configurationSource) - => (IInternalStoredProcedureResultColumnBuilder?)HasAnnotation(name, value, configurationSource); -} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs index 5adbb5ebb31..a734c2cb3a4 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs @@ -161,7 +161,7 @@ public virtual bool CanSetSchema(string? schema, ConfigurationSource configurati /// 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder? HasParameter( + public virtual InternalStoredProcedureParameterBuilder? HasParameter( string propertyName, ConfigurationSource configurationSource) { var parameter = Metadata.FindParameter(propertyName); @@ -185,7 +185,7 @@ public virtual bool CanSetSchema(string? schema, ConfigurationSource configurati /// 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder? HasParameter( + public virtual InternalStoredProcedureParameterBuilder? HasParameter( Expression> propertyExpression, ConfigurationSource configurationSource) where TDerivedEntity : class @@ -207,7 +207,7 @@ public virtual bool CanHaveParameter(string propertyName, ConfigurationSource co /// 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 InternalOriginalValuePropertyStoredProcedureParameterBuilder? HasOriginalValueParameter( + public virtual InternalStoredProcedureParameterBuilder? HasOriginalValueParameter( string propertyName, ConfigurationSource configurationSource) { var parameter = Metadata.FindOriginalValueParameter(propertyName); @@ -231,7 +231,7 @@ public virtual bool CanHaveParameter(string propertyName, ConfigurationSource co /// 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 InternalOriginalValuePropertyStoredProcedureParameterBuilder? HasOriginalValueParameter( + public virtual InternalStoredProcedureParameterBuilder? HasOriginalValueParameter( Expression> propertyExpression, ConfigurationSource configurationSource) where TDerivedEntity : class @@ -253,7 +253,7 @@ public virtual bool CanHaveOriginalValueParameter(string propertyName, Configura /// 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 InternalRowsAffectedStoredProcedureParameterBuilder? HasRowsAffectedParameter( + public virtual InternalStoredProcedureParameterBuilder? HasRowsAffectedParameter( ConfigurationSource configurationSource) { var parameter = Metadata.FindRowsAffectedParameter(); @@ -277,7 +277,7 @@ public virtual bool CanHaveOriginalValueParameter(string propertyName, Configura /// 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 InternalRowsAffectedStoredProcedureParameterBuilder? HasRowsAffectedParameter( + public virtual InternalStoredProcedureParameterBuilder? HasRowsAffectedParameter( ConfigurationSource configurationSource) where TDerivedEntity : class => HasRowsAffectedParameter(configurationSource); @@ -344,7 +344,7 @@ public virtual bool CanHaveResultColumn(string propertyName, ConfigurationSource /// 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 InternalRowsAffectedStoredProcedureResultColumnBuilder? HasRowsAffectedResultColumn( + public virtual InternalStoredProcedureResultColumnBuilder? HasRowsAffectedResultColumn( ConfigurationSource configurationSource) { var resultColumn = Metadata.FindRowsAffectedResultColumn(); @@ -368,7 +368,7 @@ public virtual bool CanHaveResultColumn(string propertyName, ConfigurationSource /// 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 InternalRowsAffectedStoredProcedureResultColumnBuilder? HasRowsAffectedResultColumn( + public virtual InternalStoredProcedureResultColumnBuilder? HasRowsAffectedResultColumn( ConfigurationSource configurationSource) where TDerivedEntity : class => HasRowsAffectedResultColumn(configurationSource); diff --git a/src/EFCore.Relational/Metadata/Internal/InternalCurrentValuePropertyStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs similarity index 76% rename from src/EFCore.Relational/Metadata/Internal/InternalCurrentValuePropertyStoredProcedureParameterBuilder.cs rename to src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs index 9d8e3a0b9b9..f6bcac84a17 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalCurrentValuePropertyStoredProcedureParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs @@ -11,10 +11,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder : - AnnotatableBuilder, - IConventionStoredProcedureParameterBuilder, - IInternalStoredProcedureParameterBuilder +public class InternalStoredProcedureParameterBuilder : + AnnotatableBuilder, + IConventionStoredProcedureParameterBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -22,8 +21,8 @@ public class InternalCurrentValuePropertyStoredProcedureParameterBuilder : /// 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder( - CurrentValuePropertyStoredProcedureParameter parameter, IConventionModelBuilder modelBuilder) + public InternalStoredProcedureParameterBuilder( + StoredProcedureParameter parameter, IConventionModelBuilder modelBuilder) : base(parameter, modelBuilder) { } @@ -34,7 +33,7 @@ public InternalCurrentValuePropertyStoredProcedureParameterBuilder( /// 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder? HasName( + public virtual InternalStoredProcedureParameterBuilder? HasName( string name, ConfigurationSource configurationSource) { @@ -66,7 +65,7 @@ public virtual bool CanSetName( /// 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder? HasDirection( + public virtual InternalStoredProcedureParameterBuilder? HasDirection( ParameterDirection direction, ConfigurationSource configurationSource) { @@ -99,24 +98,11 @@ IConventionStoredProcedureParameter IConventionStoredProcedureParameterBuilder.M [DebuggerStepThrough] get => Metadata; } - - /// - IMutableStoredProcedureParameter IInternalStoredProcedureParameterBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } /// [DebuggerStepThrough] IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasName(string name, bool fromDataAnnotation) => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasName( - string name, ConfigurationSource configurationSource) - => HasName(name, configurationSource); /// [DebuggerStepThrough] @@ -128,21 +114,9 @@ bool IConventionStoredProcedureParameterBuilder.CanSetName(string? name, bool fr IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasDirection( ParameterDirection direction, bool fromDataAnnotation) => HasDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasDirection( - ParameterDirection direction, ConfigurationSource configurationSource) - => HasDirection(direction, configurationSource); - + /// [DebuggerStepThrough] bool IConventionStoredProcedureParameterBuilder.CanSetDirection(ParameterDirection direction, bool fromDataAnnotation) => CanSetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureParameterBuilder? IInternalStoredProcedureParameterBuilder.HasAnnotation( - string name, object? value, ConfigurationSource configurationSource) - => (IInternalStoredProcedureParameterBuilder?)HasAnnotation(name, value, configurationSource); } diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs index 67191180a26..897ad864ea4 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs @@ -10,9 +10,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public class InternalStoredProcedureResultColumnBuilder : - AnnotatableBuilder, - IConventionStoredProcedureResultColumnBuilder, - IInternalStoredProcedureResultColumnBuilder + AnnotatableBuilder, + IConventionStoredProcedureResultColumnBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -21,7 +20,7 @@ public class InternalStoredProcedureResultColumnBuilder : /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public InternalStoredProcedureResultColumnBuilder( - PropertyStoredProcedureResultColumn resultColumn, IConventionModelBuilder modelBuilder) + StoredProcedureResultColumn resultColumn, IConventionModelBuilder modelBuilder) : base(resultColumn, modelBuilder) { } @@ -64,33 +63,14 @@ IConventionStoredProcedureResultColumn IConventionStoredProcedureResultColumnBui [DebuggerStepThrough] get => Metadata; } - - /// - IMutableStoredProcedureResultColumn IInternalStoredProcedureResultColumnBuilder.Metadata - { - [DebuggerStepThrough] - get => Metadata; - } /// [DebuggerStepThrough] IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasName(string name, bool fromDataAnnotation) => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureResultColumnBuilder? IInternalStoredProcedureResultColumnBuilder.HasName( - string name, ConfigurationSource configurationSource) - => HasName(name, configurationSource); /// [DebuggerStepThrough] bool IConventionStoredProcedureResultColumnBuilder.CanSetName(string? name, bool fromDataAnnotation) => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IInternalStoredProcedureResultColumnBuilder? IInternalStoredProcedureResultColumnBuilder.HasAnnotation( - string name, object? value, ConfigurationSource configurationSource) - => (IInternalStoredProcedureResultColumnBuilder?)HasAnnotation(name, value, configurationSource); } diff --git a/src/EFCore.Relational/Metadata/Internal/OriginalValuePropertyStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/OriginalValuePropertyStoredProcedureParameter.cs deleted file mode 100644 index fa4ecbe89be..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/OriginalValuePropertyStoredProcedureParameter.cs +++ /dev/null @@ -1,285 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Data; - -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 OriginalValuePropertyStoredProcedureParameter : - ConventionAnnotatable, - IMutableStoredProcedureParameter, - IConventionStoredProcedureParameter, - IRuntimeStoredProcedureParameter -{ - private string? _name; - private ParameterDirection? _direction; - - private ConfigurationSource? _nameConfigurationSource; - private ConfigurationSource? _directionConfigurationSource; - private InternalOriginalValuePropertyStoredProcedureParameterBuilder? _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 OriginalValuePropertyStoredProcedureParameter( - StoredProcedure storedProcedure, - string propertyName) - { - StoredProcedure = storedProcedure; - PropertyName = propertyName; - _builder = new(this, storedProcedure.Builder.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 virtual InternalOriginalValuePropertyStoredProcedureParameterBuilder 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; - - /// - /// 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 bool IsReadOnly - => ((Annotatable)StoredProcedure.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 virtual StoredProcedure StoredProcedure { 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. - /// - public virtual IStoreStoredProcedureParameter StoreParameter { get; set; } = default!; - - /// - /// 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 PropertyName { 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. - /// - public virtual bool? ForOriginalValue - => true; - - /// - /// 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 ForRowsAffected - => 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. - /// - public virtual string Name - { - get => _name ?? GetProperty().GetDefaultColumnName( - ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value)!; - set => SetName(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 SetName(string name, ConfigurationSource configurationSource) - { - _name = name; - - _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); - - 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 ConfigurationSource? GetNameConfigurationSource() - => _nameConfigurationSource; - - /// - /// 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 ParameterDirection Direction - { - get => _direction ?? ParameterDirection.Input; - set => SetDirection(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 ParameterDirection SetDirection(ParameterDirection direction, ConfigurationSource configurationSource) - { - if (!IsValid(direction)) - { - throw new InvalidOperationException(RelationalStrings.StoredProcedureParameterInvalidDirection( - direction, Name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()?.DisplayName())); - } - - _direction = direction; - - _directionConfigurationSource = configurationSource.Max(_directionConfigurationSource); - - return direction; - } - - /// - /// 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 IsValid(ParameterDirection direction) => direction switch - { - ParameterDirection.Output => false, - ParameterDirection.ReturnValue => false, - _ => true - }; - - /// - /// 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? GetDirectionConfigurationSource() - => _directionConfigurationSource; - - private IMutableProperty GetProperty() - => StoredProcedure.EntityType.FindProperty(PropertyName) - ?? StoredProcedure.EntityType.GetDerivedTypes().Select(t => t.FindDeclaredProperty(PropertyName)!) - .First(n => n != 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 override string ToString() - => ((IStoredProcedureParameter)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( - () => ((IStoredProcedureParameter)this).ToDebugString(), - () => ((IStoredProcedureParameter)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); - - /// - IReadOnlyStoredProcedure IReadOnlyStoredProcedureParameter.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IMutableStoredProcedure IMutableStoredProcedureParameter.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IConventionStoredProcedure IConventionStoredProcedureParameter.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IStoredProcedure IStoredProcedureParameter.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IConventionStoredProcedureParameterBuilder IConventionStoredProcedureParameter.Builder - { - [DebuggerStepThrough] - get => Builder; - } - - /// - [DebuggerStepThrough] - string IConventionStoredProcedureParameter.SetName(string name, bool fromDataAnnotation) - => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - ParameterDirection IConventionStoredProcedureParameter.SetDirection(ParameterDirection direction, bool fromDataAnnotation) - => SetDirection(direction, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); -} diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index a3eb2605ff1..b7cc69b2b8a 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -1228,7 +1228,7 @@ static StoreStoredProcedureParameter GetOrCreateStoreStoredProcedureParameter( { IsNullable = property?.IsColumnNullable(identifier) ?? false }; - + ((IRuntimeStoredProcedureParameter)parameter).StoreParameter = storeParameter; storeStoredProcedure.AddParameter(storeParameter); } diff --git a/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureParameter.cs deleted file mode 100644 index 51ba8a5b0ee..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureParameter.cs +++ /dev/null @@ -1,238 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Data; - -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 RowsAffectedStoredProcedureParameter : - ConventionAnnotatable, - IMutableStoredProcedureParameter, - IConventionStoredProcedureParameter, - IRuntimeStoredProcedureParameter -{ - private string _name = "RowsAffected"; - - private ConfigurationSource? _nameConfigurationSource; - private InternalRowsAffectedStoredProcedureParameterBuilder? _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 RowsAffectedStoredProcedureParameter(StoredProcedure storedProcedure) - { - StoredProcedure = storedProcedure; - _builder = new(this, storedProcedure.Builder.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 virtual InternalRowsAffectedStoredProcedureParameterBuilder 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; - - /// - /// 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 bool IsReadOnly - => ((Annotatable)StoredProcedure.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 virtual StoredProcedure StoredProcedure { 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. - /// - public virtual IStoreStoredProcedureParameter StoreParameter { get; set; } = default!; - - /// - /// 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? ForOriginalValue - => 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 ForRowsAffected - => true; - - /// - /// 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? PropertyName => 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 string Name - { - get => _name; - set => SetName(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 SetName(string name, ConfigurationSource configurationSource) - { - _name = name; - - _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); - - 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 ConfigurationSource? GetNameConfigurationSource() - => _nameConfigurationSource; - - /// - /// 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 ParameterDirection Direction - { - get => ParameterDirection.Output; - set => throw new InvalidOperationException( - RelationalStrings.StoredProcedureParameterInvalidConfiguration( - nameof(Direction), Name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()?.DisplayName())); - } - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - public virtual ConfigurationSource? GetDirectionConfigurationSource() - => 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 override string ToString() - => ((IStoredProcedureParameter)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( - () => ((IStoredProcedureParameter)this).ToDebugString(), - () => ((IStoredProcedureParameter)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); - - /// - IReadOnlyStoredProcedure IReadOnlyStoredProcedureParameter.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IMutableStoredProcedure IMutableStoredProcedureParameter.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IConventionStoredProcedure IConventionStoredProcedureParameter.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IStoredProcedure IStoredProcedureParameter.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IConventionStoredProcedureParameterBuilder IConventionStoredProcedureParameter.Builder - { - [DebuggerStepThrough] - get => Builder; - } - - /// - string IConventionStoredProcedureParameter.SetName(string name, bool fromDataAnnotation) - => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - ParameterDirection IConventionStoredProcedureParameter.SetDirection(ParameterDirection direction, bool fromDataAnnotation) - => Direction = direction; -} diff --git a/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureResultColumn.cs deleted file mode 100644 index 62e2178af8f..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/RowsAffectedStoredProcedureResultColumn.cs +++ /dev/null @@ -1,203 +0,0 @@ -// 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 RowsAffectedStoredProcedureResultColumn : - ConventionAnnotatable, - IMutableStoredProcedureResultColumn, - IConventionStoredProcedureResultColumn, - IRuntimeStoredProcedureResultColumn -{ - private string _name = "RowsAffected"; - - private ConfigurationSource? _nameConfigurationSource; - private InternalRowsAffectedStoredProcedureResultColumnBuilder? _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 RowsAffectedStoredProcedureResultColumn(StoredProcedure storedProcedure) - { - StoredProcedure = storedProcedure; - _builder = new(this, storedProcedure.Builder.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 virtual InternalRowsAffectedStoredProcedureResultColumnBuilder 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; - - /// - /// 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 bool IsReadOnly - => ((Annotatable)StoredProcedure.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 virtual StoredProcedure StoredProcedure { 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. - /// - public virtual IStoreStoredProcedureResultColumn StoreResultColumn { get; set; } = default!; - - /// - /// 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? PropertyName - => 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 ForRowsAffected - => true; - - /// - /// 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 Name - { - get => _name; - set => SetName(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 SetName(string name, ConfigurationSource configurationSource) - { - _name = name; - - _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); - - 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 ConfigurationSource? GetNameConfigurationSource() - => _nameConfigurationSource; - - /// - /// 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() - => ((IStoredProcedureResultColumn)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( - () => ((IStoredProcedureResultColumn)this).ToDebugString(), - () => ((IStoredProcedureResultColumn)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); - - /// - IReadOnlyStoredProcedure IReadOnlyStoredProcedureResultColumn.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IMutableStoredProcedure IMutableStoredProcedureResultColumn.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IConventionStoredProcedure IConventionStoredProcedureResultColumn.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IStoredProcedure IStoredProcedureResultColumn.StoredProcedure - { - [DebuggerStepThrough] - get => StoredProcedure; - } - - /// - IConventionStoredProcedureResultColumnBuilder IConventionStoredProcedureResultColumn.Builder - { - [DebuggerStepThrough] - get => Builder; - } - - /// - string IConventionStoredProcedureResultColumn.SetName(string name, bool fromDataAnnotation) - => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); -} diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs index 44a538c4f8b..cb99405a6d5 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs @@ -12,13 +12,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; public class StoredProcedure : ConventionAnnotatable, IRuntimeStoredProcedure, IMutableStoredProcedure, IConventionStoredProcedure { - private readonly List _parameters = new(); - private readonly Dictionary _currentValueParameters = new(); - private readonly Dictionary _originalValueParameters = new(); - private RowsAffectedStoredProcedureParameter? _rowsAffectedParameter; - private readonly List _resultColumns = new(); - private RowsAffectedStoredProcedureResultColumn? _rowsAffectedResultColumn; - private readonly Dictionary _propertyResultColumns = new(); + private readonly List _parameters = new(); + private readonly Dictionary _currentValueParameters = new(); + private readonly Dictionary _originalValueParameters = new(); + private StoredProcedureParameter? _rowsAffectedParameter; + private readonly List _resultColumns = new(); + private StoredProcedureResultColumn? _rowsAffectedResultColumn; + private readonly Dictionary _propertyResultColumns = new(); private string? _schema; private string? _name; private InternalStoredProcedureBuilder? _builder; @@ -540,7 +540,7 @@ private static void UpdateOverrides( /// 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 IReadOnlyList Parameters + public virtual IReadOnlyList Parameters { [DebuggerStepThrough] get => _parameters; @@ -552,7 +552,7 @@ public virtual IReadOnlyList Parameters /// 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 CurrentValuePropertyStoredProcedureParameter? FindParameter(string propertyName) + public virtual StoredProcedureParameter? FindParameter(string propertyName) => _currentValueParameters.TryGetValue(propertyName, out var parameter) ? parameter : null; @@ -563,7 +563,7 @@ public virtual IReadOnlyList Parameters /// 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 CurrentValuePropertyStoredProcedureParameter AddParameter(string propertyName) + public virtual StoredProcedureParameter AddParameter(string propertyName) { if (_currentValueParameters.ContainsKey(propertyName)) { @@ -571,7 +571,7 @@ public virtual CurrentValuePropertyStoredProcedureParameter AddParameter(string propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - var parameter = new CurrentValuePropertyStoredProcedureParameter(this, propertyName); + var parameter = new StoredProcedureParameter(this, rowsAffected: false, propertyName, originalValue: false); _parameters.Add(parameter); _currentValueParameters.Add(propertyName, parameter); @@ -584,7 +584,7 @@ public virtual CurrentValuePropertyStoredProcedureParameter AddParameter(string /// 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 OriginalValuePropertyStoredProcedureParameter? FindOriginalValueParameter(string propertyName) + public virtual StoredProcedureParameter? FindOriginalValueParameter(string propertyName) => _originalValueParameters.TryGetValue(propertyName, out var parameter) ? parameter : null; @@ -595,15 +595,15 @@ public virtual CurrentValuePropertyStoredProcedureParameter AddParameter(string /// 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 OriginalValuePropertyStoredProcedureParameter AddOriginalValueParameter(string propertyName) + public virtual StoredProcedureParameter AddOriginalValueParameter(string propertyName) { if (_originalValueParameters.ContainsKey(propertyName)) { throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateOriginalValueParameter( propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - - var parameter = new OriginalValuePropertyStoredProcedureParameter(this, propertyName); + + var parameter = new StoredProcedureParameter(this, rowsAffected: false, propertyName, originalValue: true); _parameters.Add(parameter); _originalValueParameters.Add(propertyName, parameter); @@ -616,7 +616,7 @@ public virtual OriginalValuePropertyStoredProcedureParameter AddOriginalValuePar /// 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 RowsAffectedStoredProcedureParameter? FindRowsAffectedParameter() + public virtual StoredProcedureParameter? FindRowsAffectedParameter() => _rowsAffectedParameter; /// @@ -625,7 +625,7 @@ public virtual OriginalValuePropertyStoredProcedureParameter AddOriginalValuePar /// 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 RowsAffectedStoredProcedureParameter AddRowsAffectedParameter() + public virtual StoredProcedureParameter AddRowsAffectedParameter() { if (_rowsAffectedParameter != null || _rowsAffectedResultColumn != null @@ -634,8 +634,8 @@ public virtual RowsAffectedStoredProcedureParameter AddRowsAffectedParameter() throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter( ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - - var parameter = new RowsAffectedStoredProcedureParameter(this); + + var parameter = new StoredProcedureParameter(this, rowsAffected: true, propertyName: null, originalValue: null); _parameters.Add(parameter); _rowsAffectedParameter = parameter; @@ -648,7 +648,7 @@ public virtual RowsAffectedStoredProcedureParameter AddRowsAffectedParameter() /// 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 IReadOnlyList ResultColumns + public virtual IReadOnlyList ResultColumns { [DebuggerStepThrough] get => _resultColumns; @@ -660,7 +660,7 @@ public virtual IReadOnlyList ResultColumns /// 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 PropertyStoredProcedureResultColumn? FindResultColumn(string propertyName) + public virtual StoredProcedureResultColumn? FindResultColumn(string propertyName) => _propertyResultColumns.TryGetValue(propertyName, out var resultColumn) ? resultColumn : null; @@ -671,7 +671,7 @@ public virtual IReadOnlyList ResultColumns /// 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 PropertyStoredProcedureResultColumn AddResultColumn(string propertyName) + public virtual StoredProcedureResultColumn AddResultColumn(string propertyName) { if (_propertyResultColumns.ContainsKey(propertyName)) { @@ -679,7 +679,7 @@ public virtual PropertyStoredProcedureResultColumn AddResultColumn(string proper propertyName, ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - var resultColumn = new PropertyStoredProcedureResultColumn(this, propertyName); + var resultColumn = new StoredProcedureResultColumn(this, forRowsAffected: false, propertyName); _resultColumns.Add(resultColumn); _propertyResultColumns.Add(propertyName, resultColumn); @@ -692,7 +692,7 @@ public virtual PropertyStoredProcedureResultColumn AddResultColumn(string proper /// 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 RowsAffectedStoredProcedureResultColumn? FindRowsAffectedResultColumn() + public virtual StoredProcedureResultColumn? FindRowsAffectedResultColumn() => _rowsAffectedResultColumn; /// @@ -701,7 +701,7 @@ public virtual PropertyStoredProcedureResultColumn AddResultColumn(string proper /// 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 RowsAffectedStoredProcedureResultColumn AddRowsAffectedResultColumn() + public virtual StoredProcedureResultColumn AddRowsAffectedResultColumn() { if (_rowsAffectedResultColumn != null || _rowsAffectedParameter != null @@ -711,7 +711,7 @@ public virtual RowsAffectedStoredProcedureResultColumn AddRowsAffectedResultColu ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - var resultColumn = new RowsAffectedStoredProcedureResultColumn(this); + var resultColumn = new StoredProcedureResultColumn(this, forRowsAffected: true, propertyName: null); _resultColumns.Add(resultColumn); _rowsAffectedResultColumn = resultColumn; @@ -786,59 +786,59 @@ IStoreStoredProcedure IRuntimeStoredProcedure.StoreStoredProcedure } /// - IEnumerable IReadOnlyStoredProcedure.Parameters + IReadOnlyList IReadOnlyStoredProcedure.Parameters { [DebuggerStepThrough] get => Parameters; } /// - IEnumerable IMutableStoredProcedure.Parameters + IReadOnlyList IMutableStoredProcedure.Parameters { [DebuggerStepThrough] get => Parameters; } /// - IEnumerable IConventionStoredProcedure.Parameters + IReadOnlyList IConventionStoredProcedure.Parameters { [DebuggerStepThrough] - get => Parameters.Cast(); + get => Parameters; } /// - IEnumerable IStoredProcedure.Parameters + IReadOnlyList IStoredProcedure.Parameters { [DebuggerStepThrough] - get => Parameters.Cast(); + get => Parameters; } /// - IEnumerable IReadOnlyStoredProcedure.ResultColumns + IReadOnlyList IReadOnlyStoredProcedure.ResultColumns { [DebuggerStepThrough] get => ResultColumns; } /// - IEnumerable IMutableStoredProcedure.ResultColumns + IReadOnlyList IMutableStoredProcedure.ResultColumns { [DebuggerStepThrough] get => ResultColumns; } /// - IEnumerable IConventionStoredProcedure.ResultColumns + IReadOnlyList IConventionStoredProcedure.ResultColumns { [DebuggerStepThrough] - get => ResultColumns.Cast(); + get => ResultColumns; } /// - IEnumerable IStoredProcedure.ResultColumns + IReadOnlyList IStoredProcedure.ResultColumns { [DebuggerStepThrough] - get => ResultColumns.Cast(); + get => ResultColumns; } /// diff --git a/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs similarity index 91% rename from src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs rename to src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs index 710b39c819c..9980b1681d7 100644 --- a/src/EFCore.Relational/Metadata/Internal/CurrentValuePropertyStoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// 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 CurrentValuePropertyStoredProcedureParameter : +public class StoredProcedureParameter : ConventionAnnotatable, IMutableStoredProcedureParameter, IConventionStoredProcedureParameter, @@ -22,7 +22,7 @@ public class CurrentValuePropertyStoredProcedureParameter : private ConfigurationSource? _nameConfigurationSource; private ConfigurationSource? _directionConfigurationSource; - private InternalCurrentValuePropertyStoredProcedureParameterBuilder? _builder; + private InternalStoredProcedureParameterBuilder? _builder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -30,12 +30,21 @@ public class CurrentValuePropertyStoredProcedureParameter : /// 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 CurrentValuePropertyStoredProcedureParameter( + public StoredProcedureParameter( StoredProcedure storedProcedure, - string propertyName) + bool rowsAffected, + string? propertyName, + bool? originalValue) { StoredProcedure = storedProcedure; + ForRowsAffected = rowsAffected; PropertyName = propertyName; + ForOriginalValue = originalValue; + if (rowsAffected) + { + _direction = ParameterDirection.Output; + _directionConfigurationSource = ConfigurationSource.Explicit; + } _builder = new(this, storedProcedure.Builder.ModelBuilder); } @@ -45,7 +54,7 @@ public CurrentValuePropertyStoredProcedureParameter( /// 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 InternalCurrentValuePropertyStoredProcedureParameterBuilder Builder + public virtual InternalStoredProcedureParameterBuilder Builder { [DebuggerStepThrough] get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); @@ -100,7 +109,7 @@ public override bool IsReadOnly /// 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 PropertyName { get; } + public virtual string? PropertyName { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -108,17 +117,15 @@ public override bool IsReadOnly /// 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? ForOriginalValue - => false; - + public virtual bool? ForOriginalValue { 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. /// - public virtual bool ForRowsAffected - => false; + public virtual bool ForRowsAffected { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -128,8 +135,10 @@ public virtual bool ForRowsAffected /// public virtual string Name { - get => _name ?? GetProperty().GetDefaultColumnName( - ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value); + get => _name ?? (ForRowsAffected + ? "RowsAffected" + : GetProperty().GetDefaultColumnName( + ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value)); set => SetName(value, ConfigurationSource.Explicit); } @@ -177,6 +186,13 @@ public virtual ParameterDirection Direction /// public virtual ParameterDirection SetDirection(ParameterDirection direction, ConfigurationSource configurationSource) { + if (ForRowsAffected) + { + throw new InvalidOperationException( + RelationalStrings.StoredProcedureParameterInvalidConfiguration( + nameof(Direction), Name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()?.DisplayName())); + } + if (!IsValid(direction)) { throw new InvalidOperationException(RelationalStrings.StoredProcedureParameterInvalidDirection( @@ -198,6 +214,7 @@ public virtual ParameterDirection SetDirection(ParameterDirection direction, Con /// public virtual bool IsValid(ParameterDirection direction) => direction switch { + ParameterDirection.Output => ForOriginalValue != true, ParameterDirection.ReturnValue => false, _ => true }; @@ -212,8 +229,8 @@ public virtual ParameterDirection SetDirection(ParameterDirection direction, Con => _directionConfigurationSource; private IMutableProperty GetProperty() - => StoredProcedure.EntityType.FindProperty(PropertyName) - ?? StoredProcedure.EntityType.GetDerivedTypes().Select(t => t.FindDeclaredProperty(PropertyName)!) + => StoredProcedure.EntityType.FindProperty(PropertyName!) + ?? StoredProcedure.EntityType.GetDerivedTypes().Select(t => t.FindDeclaredProperty(PropertyName!)!) .First(n => n != null); /// diff --git a/src/EFCore.Relational/Metadata/Internal/PropertyStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumn.cs similarity index 90% rename from src/EFCore.Relational/Metadata/Internal/PropertyStoredProcedureResultColumn.cs rename to src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumn.cs index 9991cc53dab..626edeea3a2 100644 --- a/src/EFCore.Relational/Metadata/Internal/PropertyStoredProcedureResultColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumn.cs @@ -9,12 +9,15 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// 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 PropertyStoredProcedureResultColumn : +public class StoredProcedureResultColumn : ConventionAnnotatable, IMutableStoredProcedureResultColumn, IConventionStoredProcedureResultColumn, IRuntimeStoredProcedureResultColumn { + private string _name = "RowsAffected"; + + private ConfigurationSource? _nameConfigurationSource; private InternalStoredProcedureResultColumnBuilder? _builder; /// @@ -23,11 +26,13 @@ public class PropertyStoredProcedureResultColumn : /// 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 PropertyStoredProcedureResultColumn( + public StoredProcedureResultColumn( StoredProcedure storedProcedure, - string propertyName) + bool forRowsAffected, + string? propertyName) { StoredProcedure = storedProcedure; + ForRowsAffected = forRowsAffected; PropertyName = propertyName; _builder = new(this, storedProcedure.Builder.ModelBuilder); } @@ -93,7 +98,7 @@ public override bool IsReadOnly /// 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 PropertyName { get; } + public virtual string? PropertyName { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -101,8 +106,7 @@ public override bool IsReadOnly /// 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 ForRowsAffected - => false; + public virtual bool ForRowsAffected { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -112,8 +116,10 @@ public virtual bool ForRowsAffected /// public virtual string Name { - get => GetProperty().GetColumnName( - ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value)!; + get => ForRowsAffected + ? _name + : GetProperty().GetColumnName( + ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value)!; set => SetName(value, ConfigurationSource.Explicit); } @@ -125,6 +131,15 @@ public virtual string Name /// public virtual string? SetName(string name, ConfigurationSource configurationSource) { + if (ForRowsAffected) + { + _name = name; + + _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); + + return name; + } + if (configurationSource == ConfigurationSource.Explicit) { GetProperty().SetColumnName(name, ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value); @@ -143,12 +158,14 @@ public virtual string Name /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ConfigurationSource? GetNameConfigurationSource() - => ((IConventionProperty)GetProperty()!) - .GetColumnNameConfigurationSource(((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value); + => ForRowsAffected + ? _nameConfigurationSource + : ((IConventionProperty)GetProperty()!) + .GetColumnNameConfigurationSource(((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value); private IMutableProperty GetProperty() - => StoredProcedure.EntityType.FindProperty(PropertyName) - ?? StoredProcedure.EntityType.GetDerivedTypes().Select(t => t.FindDeclaredProperty(PropertyName)!) + => StoredProcedure.EntityType.FindProperty(PropertyName!) + ?? StoredProcedure.EntityType.GetDerivedTypes().Select(t => t.FindDeclaredProperty(PropertyName!)!) .First(n => n != null); /// diff --git a/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs b/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs index 662c79d8a6a..b60a8c5c1ee 100644 --- a/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs @@ -166,14 +166,14 @@ bool IReadOnlyStoredProcedure.AreRowsAffectedReturned } /// - IEnumerable IReadOnlyStoredProcedure.Parameters + IReadOnlyList IReadOnlyStoredProcedure.Parameters { [DebuggerStepThrough] get => _parameters; } /// - IEnumerable IStoredProcedure.Parameters + IReadOnlyList IStoredProcedure.Parameters { [DebuggerStepThrough] get => _parameters; @@ -208,14 +208,14 @@ IEnumerable IStoredProcedure.Parameters => (IStoredProcedureParameter?)((IReadOnlyStoredProcedure)this).FindRowsAffectedParameter(); /// - IEnumerable IReadOnlyStoredProcedure.ResultColumns + IReadOnlyList IReadOnlyStoredProcedure.ResultColumns { [DebuggerStepThrough] get => _resultColumns; } /// - IEnumerable IStoredProcedure.ResultColumns + IReadOnlyList IStoredProcedure.ResultColumns { [DebuggerStepThrough] get => _resultColumns; diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index fae4cc27e93..d9ced6c30bc 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1229,6 +1229,14 @@ public static string SqlQueryOverrideMismatch(object? propertySpecification, obj GetString("SqlQueryOverrideMismatch", nameof(propertySpecification), nameof(query)), propertySpecification, query); + /// + /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the concurrency token '{token}' are not mapped to any original value parameter. + /// + public static string StoredProcedureConcurrencyTokenNotMapped(object? entityType, object? sproc, object? token) + => string.Format( + GetString("StoredProcedureConcurrencyTokenNotMapped", nameof(entityType), nameof(sproc), nameof(token)), + entityType, sproc, token); + /// /// The property '{entityType}.{property}' is mapped to a parameter of the stored procedure '{sproc}', but only concurrency token and key properties are supported for Delete stored procedures. /// @@ -1253,6 +1261,14 @@ public static string StoredProcedureDuplicateParameter(object? property, object? GetString("StoredProcedureDuplicateParameter", nameof(property), nameof(sproc)), property, sproc); + /// + /// The parameter '{parameter}' cannot be added to the stored procedure '{sproc}' because another parameter with this name already exists. + /// + public static string StoredProcedureDuplicateParameterName(object? parameter, object? sproc) + => string.Format( + GetString("StoredProcedureDuplicateParameterName", nameof(parameter), nameof(sproc)), + parameter, sproc); + /// /// The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because another result column for this property already exists. /// @@ -1261,6 +1277,14 @@ public static string StoredProcedureDuplicateResultColumn(object? property, obje GetString("StoredProcedureDuplicateResultColumn", nameof(property), nameof(sproc)), property, sproc); + /// + /// The result column '{column}' cannot be added to the stored procedure '{sproc}' because another result column with this name already exists. + /// + public static string StoredProcedureDuplicateResultColumnName(object? column, object? sproc) + => string.Format( + GetString("StoredProcedureDuplicateResultColumnName", nameof(column), nameof(sproc)), + column, sproc); + /// /// The rows affected parameter cannot be added to the stored procedure '{sproc}' because the rows affected are already returned via another parameter, via the stored procedure return value or via a result column. /// @@ -1301,6 +1325,14 @@ public static string StoredProcedureNoName(object? entityType, object? sproc) GetString("StoredProcedureNoName", nameof(entityType), nameof(sproc)), entityType, sproc); + /// + /// The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is also mapped to an output original value parameter. A store-generated property can only be mapped to one output parameter. + /// + public static string StoredProcedureOutputParameterConflict(object? entityType, object? property, object? sproc) + => string.Format( + GetString("StoredProcedureOutputParameterConflict", nameof(entityType), nameof(property), nameof(sproc)), + entityType, property, sproc); + /// /// The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is not configured as store-generated. Either configure it as store-generated or don't configure the parameter as output. /// @@ -1373,6 +1405,14 @@ public static string StoredProcedureResultColumnNotGenerated(object? entityType, GetString("StoredProcedureResultColumnNotGenerated", nameof(entityType), nameof(property), nameof(sproc)), entityType, property, sproc); + /// + /// The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is also mapped to an output parameter. A store-generated property can only be mapped to one of these. + /// + public static string StoredProcedureResultColumnParameterConflict(object? entityType, object? property, object? sproc) + => string.Format( + GetString("StoredProcedureResultColumnParameterConflict", nameof(entityType), nameof(property), nameof(sproc)), + entityType, property, sproc); + /// /// The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter or a rows affected result column for this stored procedure already exists. /// @@ -1397,6 +1437,14 @@ public static string StoredProcedureTphDuplicate(object? entityType, object? oth GetString("StoredProcedureTphDuplicate", nameof(entityType), nameof(otherEntityType), nameof(sproc)), entityType, otherEntityType, sproc); + /// + /// The entity type '{entityType}' was configured to use stored procedures and it's not mapped to any table. An unmapped entity type requires to be mapped to insert, update and delete stored procedures. + /// + public static string StoredProcedureUnmapped(object? entityType) + => string.Format( + GetString("StoredProcedureUnmapped", nameof(entityType)), + entityType); + /// /// The entity type '{entityType}' is not mapped to the store object '{table}'. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 76c7fd8231f..66beb6ce62d 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -861,6 +861,9 @@ 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 entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the concurrency token '{token}' are not mapped to any original value parameter. + The property '{entityType}.{property}' is mapped to a parameter of the stored procedure '{sproc}', but only concurrency token and key properties are supported for Delete stored procedures. @@ -870,9 +873,15 @@ The parameter for the property '{property}' cannot be added to the stored procedure '{sproc}' because another parameter for this property already exists. + + The parameter '{parameter}' cannot be added to the stored procedure '{sproc}' because another parameter with this name already exists. + The result column for the property '{property}' cannot be added to the stored procedure '{sproc}' because another result column for this property already exists. + + The result column '{column}' cannot be added to the stored procedure '{sproc}' because another result column with this name already exists. + The rows affected parameter cannot be added to the stored procedure '{sproc}' because the rows affected are already returned via another parameter, via the stored procedure return value or via a result column. @@ -888,6 +897,9 @@ The entity type '{entityType}' was configured to use '{sproc}', but the store name was not specified. Configure the stored procedure name explicitly. + + The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is also mapped to an output original value parameter. A store-generated property can only be mapped to one output parameter. + The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is not configured as store-generated. Either configure it as store-generated or don't configure the parameter as output. @@ -915,6 +927,9 @@ The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is not configured as store-generated. + + The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is also mapped to an output parameter. A store-generated property can only be mapped to one of these. + The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter or a rows affected result column for this stored procedure already exists. @@ -924,6 +939,9 @@ 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}' was configured to use stored procedures and it's not mapped to any table. An unmapped entity type requires to be mapped to insert, update and delete stored procedures. + The entity type '{entityType}' is not mapped to the store object '{table}'. diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 36a75e7df75..49082442738 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -2619,6 +2619,21 @@ public virtual void Detects_keyless_entity_type_mapped_to_a_stored_procedure() RelationalStrings.StoredProcedureKeyless(nameof(Animal), "Animal_Insert"), modelBuilder); } + + [ConditionalFact] + public virtual void Detects_tableless_entity_type_mapped_to_some_stored_procedures() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity() + .Ignore(a => a.FavoritePerson) + .ToTable((string)null) + .InsertUsingStoredProcedure(s => s.HasParameter(c => c.Id, p => p.IsOutput()).HasParameter(c => c.Name)) + .UpdateUsingStoredProcedure(s => s.HasParameter(c => c.Id).HasParameter(c => c.Name)); + + VerifyError( + RelationalStrings.StoredProcedureUnmapped(nameof(Animal)), + modelBuilder); + } [ConditionalFact] public virtual void Detects_derived_entity_type_mapped_to_a_stored_procedure_in_TPH() @@ -2713,6 +2728,36 @@ public virtual void Detects_unmatched_stored_procedure_result_columns_in_TPH() RelationalStrings.StoredProcedureResultColumnNotFound("Missing", nameof(Animal), "dbo.Update"), modelBuilder); } + + [ConditionalFact] + public virtual void Detects_duplicate_parameter() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity() + .InsertUsingStoredProcedure(s => s + .HasParameter(a => a.Id, p => p.IsOutput()) + .HasRowsAffectedParameter(c => c.HasName("Id")) + .HasParameter("FavoritePersonId")); + + VerifyError( + RelationalStrings.StoredProcedureDuplicateParameterName("Id", "Animal_Insert"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_duplicate_result_column() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity() + .InsertUsingStoredProcedure(s => s + .HasResultColumn(a => a.Id, c => c.HasName("Id")) + .HasRowsAffectedResultColumn(c => c.HasName("Id")) + .HasParameter("FavoritePersonId")); + + VerifyError( + RelationalStrings.StoredProcedureDuplicateResultColumnName("Id", "Animal_Insert"), + modelBuilder); + } [ConditionalFact] public virtual void Detects_non_generated_insert_stored_procedure_result_columns_in_TPH() @@ -2782,6 +2827,55 @@ public virtual void Detects_delete_stored_procedure_result_columns_in_TPH() RelationalStrings.StoredProcedureResultColumnDelete(nameof(Animal), nameof(Animal.Name), "Delete"), modelBuilder); } + + [ConditionalFact] + public virtual void Detects_generated_properties_mapped_to_result_and_parameter() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity() + .UpdateUsingStoredProcedure(s => s + .HasParameter(a => a.Id) + .HasParameter(a => a.Name, p => p.IsInputOutput()) + .HasResultColumn(a => a.Name)) + .Property(a => a.Name).ValueGeneratedOnUpdate(); + + VerifyError( + RelationalStrings.StoredProcedureResultColumnParameterConflict(nameof(Animal), nameof(Animal.Name), "Animal_Update"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_generated_properties_mapped_to_original_and_current_parameter() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity() + .UpdateUsingStoredProcedure(s => s + .HasParameter(a => a.Id) + .HasParameter(a => a.Name, p => p.IsOutput()) + .HasOriginalValueParameter(a => a.Name, p => p.IsInputOutput().HasName("OriginalName"))) + .Property(a => a.Name).ValueGeneratedOnUpdate(); + + VerifyError( + RelationalStrings.StoredProcedureOutputParameterConflict(nameof(Animal), nameof(Animal.Name), "Animal_Update"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_unmapped_concurrency_token() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity() + .UpdateUsingStoredProcedure(s => s + .HasParameter(a => a.Id) + .HasParameter("FavoritePersonId") + .HasParameter(a => a.Name, p => p.IsOutput()) + .HasRowsAffectedReturnValue()) + .Property(a => a.Name).IsRowVersion(); + + VerifyError( + RelationalStrings.StoredProcedureConcurrencyTokenNotMapped(nameof(Animal), "Animal_Update", nameof(Animal.Name)), + modelBuilder); + } [ConditionalFact] public virtual void Passes_on_valid_UsingDeleteStoredProcedure_in_TPT() @@ -2853,7 +2947,7 @@ 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)); + .UpdateUsingStoredProcedure("Update", s => s.HasOriginalValueParameter((Cat c) => c.Breed)); modelBuilder.Entity(); VerifyError( diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 56db52064d5..1f82efb2ed0 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Data; + #nullable enable // ReSharper disable InconsistentNaming @@ -154,6 +156,171 @@ public virtual void Can_use_view_splitting_with_schema() Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails", "sch"))); Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_return_and_parameter_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter() + .HasRowsAffectedReturnValue())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_return_and_result_column_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn() + .HasRowsAffectedReturnValue())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_parameter_and_return_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedReturnValue() + .HasRowsAffectedParameter())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_result_column_and_return_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedReturnValue() + .HasRowsAffectedResultColumn())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_result_column_and_parameter_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter() + .HasRowsAffectedResultColumn())) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_rows_affected_result_column_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn()).Metadata.GetUpdateStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws(() => sproc.AddRowsAffectedResultColumn()) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_parameter_and_result_column_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn() + .HasRowsAffectedParameter())) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_rows_affected_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter()).Metadata.GetUpdateStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws(() => sproc.AddRowsAffectedParameter()) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateParameter("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddParameter("Id")) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_original_value_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasOriginalValueParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateOriginalValueParameter("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddOriginalValueParameter("Id")) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_result_column_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasResultColumn(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateResultColumn("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddResultColumn("Id")) + .Message); + } } public abstract class RelationalInheritanceTestBase : InheritanceTestBase @@ -208,6 +375,7 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() modelBuilder.Ignore(); modelBuilder.Ignore(); + modelBuilder.Entity().Property("RowVersion").IsRowVersion(); modelBuilder.Entity() .Ignore(s => s.SpecialBookLabel) .InsertUsingStoredProcedure( @@ -220,7 +388,9 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() var resultColumnBuilder = p.HasName("InsertId"); var nonGenericBuilder = (IInfrastructure)resultColumnBuilder; Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); - })) + }) + .HasResultColumn("RowVersion") + .HasRowsAffectedReturnValue()) .UpdateUsingStoredProcedure( s => s.SuppressTransactions().HasAnnotation("foo", "bar2") .HasParameter( @@ -230,10 +400,15 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() var nonGenericBuilder = (IInfrastructure)parameterBuilder; Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); }) - .HasParameter(b => b.BookId)) + .HasParameter(b => b.BookId) + .HasRowsAffectedParameter() + .HasOriginalValueParameter("RowVersion", p => p.HasName("OriginalRowVersion")) + .HasParameter("RowVersion", p => p.IsOutput())) .DeleteUsingStoredProcedure( s => s.SuppressTransactions().HasAnnotation("foo", "bar3") - .HasParameter(b => b.Id, p => p.HasName("DeleteId"))); + .HasParameter(b => b.Id, p => p.HasName("DeleteId")) + .HasRowsAffectedResultColumn() + .HasOriginalValueParameter("RowVersion")); modelBuilder.Entity() .Ignore(s => s.BookLabel); @@ -251,32 +426,57 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() Assert.Equal("Insert", insertSproc.Name); Assert.Equal("mySchema", insertSproc.Schema); Assert.Equal(new[] { "BookId", "Discriminator" }, insertSproc.Parameters.Select(p => p.PropertyName)); - Assert.Equal(new[] { "Id" }, insertSproc.ResultColumns.Select(p => p.PropertyName)); + Assert.Equal(new[] { "Id", "RowVersion" }, insertSproc.ResultColumns.Select(p => p.PropertyName)); Assert.False(insertSproc.FindParameter("Discriminator")!.ForOriginalValue); Assert.Null(insertSproc.FindParameter("Id")); Assert.Null(insertSproc.FindResultColumn("Discriminator")); Assert.False(insertSproc.FindResultColumn("Id")!.ForRowsAffected); Assert.True(insertSproc.AreTransactionsSuppressed); + Assert.True(insertSproc.AreRowsAffectedReturned); 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.Select(p => p.PropertyName)); + Assert.Equal(new[] { "Id", "BookId", null, "RowVersion", "RowVersion" }, updateSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(updateSproc.ResultColumns); Assert.True(updateSproc.AreTransactionsSuppressed); Assert.Equal("bar2", updateSproc["foo"]); Assert.Same(bookLabel, updateSproc.EntityType); + Assert.False(updateSproc.AreRowsAffectedReturned); + + var rowsAffectedParameter = updateSproc.Parameters[2]; + Assert.Null(rowsAffectedParameter.ForOriginalValue); + Assert.True(rowsAffectedParameter.ForRowsAffected); + Assert.Equal(ParameterDirection.Output, rowsAffectedParameter.Direction); + Assert.Same(updateSproc, rowsAffectedParameter.StoredProcedure); + + var originalRowVersionParameter = updateSproc.Parameters[3]; + Assert.True(originalRowVersionParameter.ForOriginalValue); + Assert.False(originalRowVersionParameter.ForRowsAffected); + Assert.Equal(ParameterDirection.Input, originalRowVersionParameter.Direction); + Assert.Same(updateSproc, originalRowVersionParameter.StoredProcedure); + + var currentRowVersionParameter = updateSproc.Parameters[4]; + Assert.False(currentRowVersionParameter.ForOriginalValue); + Assert.False(currentRowVersionParameter.ForRowsAffected); + Assert.Equal(ParameterDirection.Output, currentRowVersionParameter.Direction); + Assert.Same(updateSproc, currentRowVersionParameter.StoredProcedure); var deleteSproc = bookLabel.GetDeleteStoredProcedure()!; Assert.Equal("BookLabel_Delete", deleteSproc.Name); Assert.Equal("mySchema", deleteSproc.Schema); - Assert.Equal(new[] { "Id" }, deleteSproc.Parameters.Select(p => p.PropertyName)); - Assert.Empty(deleteSproc.ResultColumns); + Assert.Equal(new[] { "Id", "RowVersion" }, deleteSproc.Parameters.Select(p => p.PropertyName)); + Assert.Equal(new[] { "RowsAffected" }, deleteSproc.ResultColumns.Select(p => p.Name)); Assert.True(deleteSproc.AreTransactionsSuppressed); Assert.Equal("bar3", deleteSproc["foo"]); Assert.Same(bookLabel, deleteSproc.EntityType); + Assert.False(deleteSproc.AreRowsAffectedReturned); + + var rowsAffectedResultColumn = deleteSproc.ResultColumns[0]; + Assert.True(rowsAffectedResultColumn.ForRowsAffected); + Assert.Same(deleteSproc, rowsAffectedResultColumn.StoredProcedure); var id = bookLabel.FindProperty(nameof(BookLabel.Id))!; Assert.Single(id.GetOverrides()); From 0821b27470189c349b41409d6d02d513c26493d9 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Thu, 4 Aug 2022 14:21:11 -0700 Subject: [PATCH 8/8] Finishing touches --- .../CosmosRelationshipDiscoveryConvention.cs | 2 - src/EFCore.Relational/Metadata/IColumnBase.cs | 5 ++ .../IMutableStoredProcedureParameter.cs | 2 +- .../Metadata/Internal/ColumnBase.cs | 11 ++- .../Metadata/Internal/RelationalModel.cs | 77 +++++++++++++------ .../Internal/StoreStoredProcedureParameter.cs | 15 +++- .../StoreStoredProcedureResultColumn.cs | 15 +++- .../Internal/StoreStoredProcedureReturn.cs | 15 +++- .../StoredProcedureParameterMapping.cs | 18 ++++- .../StoredProcedureResultColumnMapping.cs | 14 +++- .../Internal/MigrationsModelDiffer.cs | 7 +- .../Properties/RelationalStrings.Designer.cs | 6 +- .../Properties/RelationalStrings.resx | 6 +- .../CSharpRuntimeModelCodeGeneratorTest.cs | 5 +- .../RelationalModelValidatorTest.cs | 5 +- .../Metadata/RelationalModelTest.cs | 51 +++++++----- .../RelationalModelBuilderTest.cs | 15 ++++ 17 files changed, 198 insertions(+), 71 deletions(-) diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosRelationshipDiscoveryConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosRelationshipDiscoveryConvention.cs index b409aa58f01..2063d27add5 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosRelationshipDiscoveryConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosRelationshipDiscoveryConvention.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - - // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; diff --git a/src/EFCore.Relational/Metadata/IColumnBase.cs b/src/EFCore.Relational/Metadata/IColumnBase.cs index af8da463f5a..725636e9e08 100644 --- a/src/EFCore.Relational/Metadata/IColumnBase.cs +++ b/src/EFCore.Relational/Metadata/IColumnBase.cs @@ -26,6 +26,11 @@ public interface IColumnBase : IAnnotatable /// Type ProviderClrType { get; } + /// + /// Gets the type mapping for the column-like object. + /// + RelationalTypeMapping StoreTypeMapping { get; } + /// /// Gets the value indicating whether the column can contain NULL. /// diff --git a/src/EFCore.Relational/Metadata/IMutableStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/IMutableStoredProcedureParameter.cs index c22a4aee8c2..06a4a32a9c8 100644 --- a/src/EFCore.Relational/Metadata/IMutableStoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/IMutableStoredProcedureParameter.cs @@ -23,5 +23,5 @@ public interface IMutableStoredProcedureParameter : IReadOnlyStoredProcedurePara /// /// Gets or sets the direction of the parameter. /// - new static ParameterDirection Direction { get; set; } + new ParameterDirection Direction { get; set; } } diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs b/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs index 8e59938a61b..42ebec03d57 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs @@ -83,12 +83,21 @@ public virtual Type ProviderClrType return _providerClrType; } - var typeMapping = PropertyMappings.First().TypeMapping; + var typeMapping = StoreTypeMapping; var providerType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType; return _providerClrType = providerType; } } + + /// + /// 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 RelationalTypeMapping StoreTypeMapping + => PropertyMappings.First().TypeMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index b7cc69b2b8a..2b25ebee5e0 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -952,7 +952,7 @@ private static void AddStoredProcedures( if (tableMapping != null) { - tableMapping.InsertStoredProcedureMapping = deleteProcedureMapping; + tableMapping.DeleteStoredProcedureMapping = deleteProcedureMapping; } } else if (entityType == mappedType) @@ -976,7 +976,7 @@ private static void AddStoredProcedures( if (tableMapping != null) { - tableMapping.InsertStoredProcedureMapping = updateProcedureMapping; + tableMapping.UpdateStoredProcedureMapping = updateProcedureMapping; } } else if (entityType == mappedType) @@ -1038,7 +1038,7 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( RelationalAnnotationNames.UpdateStoredProcedureResultColumnMappings), _ => throw new Exception("Unexpected stored procedure type: " + identifier.StoreObjectType) }; - + var position = -1; foreach (var parameter in storedProcedure.Parameters) { @@ -1055,7 +1055,7 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( continue; } - + var property = mappedType.FindProperty(parameter.PropertyName); if (property == null) { @@ -1120,7 +1120,7 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( continue; } - + var property = mappedType.FindProperty(resultColumn.PropertyName); if (property == null) { @@ -1189,10 +1189,12 @@ static StoreStoredProcedure GetOrCreateStoreStoredProcedure( storeStoredProcedure = new StoreStoredProcedure(storedProcedure, model); if (storedProcedure.AreRowsAffectedReturned) { + var typeMapping = relationalTypeMappingSource.FindMapping(typeof(int))!; storeStoredProcedure.Return = new StoreStoredProcedureReturn( "", - relationalTypeMappingSource.FindMapping(typeof(int))!.StoreType, - storeStoredProcedure); + typeMapping.StoreType, + storeStoredProcedure, + typeMapping); } model.StoredProcedures.Add((storeStoredProcedure.Name, storeStoredProcedure.Schema), storeStoredProcedure); } @@ -1218,25 +1220,38 @@ static StoreStoredProcedureParameter GetOrCreateStoreStoredProcedureParameter( var storeParameter = (StoreStoredProcedureParameter?)storeStoredProcedure.FindParameter(name); if (storeParameter == null) { - storeParameter = new StoreStoredProcedureParameter( - name, - property?.GetColumnType(identifier) - ?? relationalTypeMappingSource.FindMapping(typeof(int))!.StoreType, - position, - storeStoredProcedure, - parameter.Direction) + if (property == null) { - IsNullable = property?.IsColumnNullable(identifier) ?? false - }; + var typeMapping = relationalTypeMappingSource.FindMapping(typeof(int))!; + storeParameter = new StoreStoredProcedureParameter( + name, + typeMapping.StoreType, + position, + storeStoredProcedure, + parameter.Direction, + typeMapping); + } + else + { + storeParameter = new StoreStoredProcedureParameter( + name, + property.GetColumnType(identifier), + position, + storeStoredProcedure, + parameter.Direction) + { + IsNullable = property.IsColumnNullable(identifier) + }; + } - ((IRuntimeStoredProcedureParameter)parameter).StoreParameter = storeParameter; storeStoredProcedure.AddParameter(storeParameter); } else if (property?.IsColumnNullable(identifier) == false) { storeParameter.IsNullable = false; } - + + ((IRuntimeStoredProcedureParameter)parameter).StoreParameter = storeParameter; return storeParameter; } @@ -1251,14 +1266,25 @@ static StoreStoredProcedureResultColumn GetOrCreateStoreStoredProcedureResultCol var column = (StoreStoredProcedureResultColumn?)storeStoredProcedure.FindResultColumn(name); if (column == null) { - column = new StoreStoredProcedureResultColumn( - name, - property?.GetColumnType(identifier) - ?? relationalTypeMappingSource.FindMapping(typeof(int))!.StoreType, - storeStoredProcedure) + if (property == null) { - IsNullable = property?.IsColumnNullable(identifier) ?? false - }; + var typeMapping = relationalTypeMappingSource.FindMapping(typeof(int))!; + column = new StoreStoredProcedureResultColumn( + name, + typeMapping.StoreType, + storeStoredProcedure); + } + else + { + column = new StoreStoredProcedureResultColumn( + name, + property.GetColumnType(identifier), + storeStoredProcedure) + { + IsNullable = property.IsColumnNullable(identifier) + }; + } + storeStoredProcedure.AddResultColumn(column); } else if (property?.IsColumnNullable(identifier) == false) @@ -1266,6 +1292,7 @@ static StoreStoredProcedureResultColumn GetOrCreateStoreStoredProcedureResultCol column.IsNullable = false; } + ((IRuntimeStoredProcedureResultColumn)resultColumn).StoreResultColumn = column; return column; } } diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs index 846a90545ce..7bba844d50f 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureParameter.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; public class StoreStoredProcedureParameter : ColumnBase, IStoreStoredProcedureParameter { + private readonly RelationalTypeMapping? _storeTypeMapping; + /// /// 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 @@ -25,11 +27,13 @@ public StoreStoredProcedureParameter( string type, int position, StoreStoredProcedure storedProcedure, - ParameterDirection direction) + ParameterDirection direction, + RelationalTypeMapping? storeTypeMapping = null) : base(name, type, storedProcedure) { Position = position; Direction = direction; + _storeTypeMapping = storeTypeMapping; } /// @@ -56,6 +60,15 @@ public virtual StoreStoredProcedure StoredProcedure /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual int Position { 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. + /// + public override RelationalTypeMapping StoreTypeMapping + => _storeTypeMapping ?? base.StoreTypeMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureResultColumn.cs index 1e7211dda09..aaab2217f96 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureResultColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureResultColumn.cs @@ -12,6 +12,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; public class StoreStoredProcedureResultColumn : ColumnBase, IStoreStoredProcedureResultColumn { + private readonly RelationalTypeMapping? _storeTypeMapping; + /// /// 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 @@ -21,9 +23,11 @@ public class StoreStoredProcedureResultColumn public StoreStoredProcedureResultColumn( string name, string type, - StoreStoredProcedure storedProcedure) + StoreStoredProcedure storedProcedure, + RelationalTypeMapping? storeTypeMapping = null) : base(name, type, storedProcedure) { + _storeTypeMapping = storeTypeMapping; } /// @@ -34,6 +38,15 @@ public StoreStoredProcedureResultColumn( /// public virtual StoreStoredProcedure StoredProcedure => (StoreStoredProcedure)Table; + + /// + /// 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 RelationalTypeMapping StoreTypeMapping + => _storeTypeMapping ?? base.StoreTypeMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs index 33c34977855..cfac9fdf733 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs @@ -11,6 +11,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public class StoreStoredProcedureReturn : ColumnBase, IStoreStoredProcedureReturn { + private readonly RelationalTypeMapping? _storeTypeMapping; + /// /// 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 @@ -20,9 +22,11 @@ public class StoreStoredProcedureReturn : ColumnBase, IStoreS public StoreStoredProcedureReturn( string name, string type, - StoreStoredProcedure storedProcedure) + StoreStoredProcedure storedProcedure, + RelationalTypeMapping? storeTypeMapping = null) : base(name, type, storedProcedure) { + _storeTypeMapping = storeTypeMapping; } /// @@ -33,6 +37,15 @@ public StoreStoredProcedureReturn( /// public virtual StoreStoredProcedure StoredProcedure => (StoreStoredProcedure)Table; + + /// + /// 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 RelationalTypeMapping StoreTypeMapping + => _storeTypeMapping ?? base.StoreTypeMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameterMapping.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameterMapping.cs index 78bb03d3604..7f2e62b9335 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameterMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameterMapping.cs @@ -25,12 +25,22 @@ public StoredProcedureParameterMapping( : base(property, storeParameter, storedProcedureMapping) { Parameter = parameter; - } - - /// + } + + /// + /// 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 IStoredProcedureParameter Parameter { 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. + /// public virtual IStoredProcedureMapping StoredProcedureMapping => (IStoredProcedureMapping)TableMapping; diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumnMapping.cs index eab7c006067..a8c300038b8 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumnMapping.cs @@ -28,11 +28,19 @@ public StoredProcedureResultColumnMapping( } /// - /// Gets the associated stored procedure result column. + /// 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 IStoredProcedureResultColumn ResultColumn { 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. + /// public virtual IStoredProcedureMapping StoredProcedureMapping => (IStoredProcedureMapping)TableMapping; diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 41a6029ba1a..bf74375f8a2 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1011,8 +1011,8 @@ protected virtual IEnumerable Diff( IsDestructiveChange = isDestructiveChange }; - var sourceTypeMapping = source.PropertyMappings.First().TypeMapping; - var targetTypeMapping = target.PropertyMappings.First().TypeMapping; + var sourceTypeMapping = source.StoreTypeMapping; + var targetTypeMapping = target.StoreTypeMapping; Initialize( alterColumnOperation, target, targetTypeMapping, @@ -1059,8 +1059,7 @@ protected virtual IEnumerable Add( Name = target.Name }; - var targetMapping = target.PropertyMappings.First(); - var targetTypeMapping = targetMapping.TypeMapping; + var targetTypeMapping = target.StoreTypeMapping; Initialize( operation, target, targetTypeMapping, target.IsNullable, diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index d9ced6c30bc..1200a7e5f32 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1230,7 +1230,7 @@ public static string SqlQueryOverrideMismatch(object? propertySpecification, obj propertySpecification, query); /// - /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the concurrency token '{token}' are not mapped to any original value parameter. + /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', but the concurrency token '{token}' is not mapped to any original value parameter. /// public static string StoredProcedureConcurrencyTokenNotMapped(object? entityType, object? sproc, object? token) => string.Format( @@ -1326,7 +1326,7 @@ public static string StoredProcedureNoName(object? entityType, object? sproc) entityType, sproc); /// - /// The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is also mapped to an output original value parameter. A store-generated property can only be mapped to one output parameter. + /// The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is also mapped to an output original value output parameter. A store-generated property can only be mapped to one output parameter. /// public static string StoredProcedureOutputParameterConflict(object? entityType, object? property, object? sproc) => string.Format( @@ -1438,7 +1438,7 @@ public static string StoredProcedureTphDuplicate(object? entityType, object? oth entityType, otherEntityType, sproc); /// - /// The entity type '{entityType}' was configured to use stored procedures and it's not mapped to any table. An unmapped entity type requires to be mapped to insert, update and delete stored procedures. + /// The entity type '{entityType}' was configured to use some stored procedures and is not mapped to any table. An entity type that isn't mapped to a table must be mapped to insert, update and delete stored procedures. /// public static string StoredProcedureUnmapped(object? entityType) => string.Format( diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 66beb6ce62d..5ea22837654 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -862,7 +862,7 @@ 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 entity type '{entityType}' is mapped to the stored procedure '{sproc}', however the concurrency token '{token}' are not mapped to any original value parameter. + The entity type '{entityType}' is mapped to the stored procedure '{sproc}', but the concurrency token '{token}' is not mapped to any original value parameter. The property '{entityType}.{property}' is mapped to a parameter of the stored procedure '{sproc}', but only concurrency token and key properties are supported for Delete stored procedures. @@ -898,7 +898,7 @@ The entity type '{entityType}' was configured to use '{sproc}', but the store name was not specified. Configure the stored procedure name explicitly. - The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is also mapped to an output original value parameter. A store-generated property can only be mapped to one output parameter. + The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is also mapped to an output original value output parameter. A store-generated property can only be mapped to one output parameter. The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is not configured as store-generated. Either configure it as store-generated or don't configure the parameter as output. @@ -940,7 +940,7 @@ 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}' was configured to use stored procedures and it's not mapped to any table. An unmapped entity type requires to be mapped to insert, update and delete stored procedures. + The entity type '{entityType}' was configured to use some stored procedures and is not mapped to any table. An entity type that isn't mapped to a table must be mapped to insert, update and delete stored procedures. The entity type '{entityType}' is not mapped to the store object '{table}'. diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 26a64d52965..32081cbbe29 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -2609,7 +2609,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) runtimeEntityType, ""PrincipalBase_Delete"", ""TPC"", - false, + true, false); var id = deleteSproc.AddParameter( @@ -2782,7 +2782,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(new[] { "Id" }, deleteSproc.Parameters.Select(p => p.Name)); Assert.Empty(deleteSproc.ResultColumns); Assert.False(deleteSproc.AreTransactionsSuppressed); - Assert.False(deleteSproc.AreRowsAffectedReturned); + Assert.True(deleteSproc.AreRowsAffectedReturned); Assert.Same(principalBase, deleteSproc.EntityType); Assert.Equal("Id", deleteSproc.Parameters.Last().Name); Assert.Null(id.FindOverrides(StoreObjectIdentifier.Create(principalBase, StoreObjectType.DeleteStoredProcedure).Value)); @@ -2919,6 +2919,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasParameter("PrincipalDerivedId") .HasParameter(p => p.Id)); eb.DeleteUsingStoredProcedure(s => s + .HasRowsAffectedReturnValue() .HasParameter(p => p.Id)); }); diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 49082442738..2ef844b692b 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -2627,8 +2627,9 @@ public virtual void Detects_tableless_entity_type_mapped_to_some_stored_procedur modelBuilder.Entity() .Ignore(a => a.FavoritePerson) .ToTable((string)null) - .InsertUsingStoredProcedure(s => s.HasParameter(c => c.Id, p => p.IsOutput()).HasParameter(c => c.Name)) - .UpdateUsingStoredProcedure(s => s.HasParameter(c => c.Id).HasParameter(c => c.Name)); + .InsertUsingStoredProcedure(s => s.HasParameter(c => c.Id).HasParameter(c => c.Name)) + .UpdateUsingStoredProcedure(s => s.HasParameter(c => c.Id).HasParameter(c => c.Name)) + .Property(a => a.Id).ValueGeneratedNever(); VerifyError( RelationalStrings.StoredProcedureUnmapped(nameof(Animal)), diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index ceccac9f034..f481a54fdba 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -77,7 +77,7 @@ public void Can_use_relational_model_with_views(Mapping mapping) [InlineData(false, Mapping.TPC)] public void Can_use_relational_model_with_sprocs(bool mapToTables, Mapping mapping) { - var model = CreateTestModel(mapToTables: mapToTables, mapToSprocs:true, mapping: mapping); + var model = CreateTestModel(mapToTables: mapToTables, mapToSprocs: true, mapping: mapping); Assert.Equal(11, model.Model.GetEntityTypes().Count()); Assert.Equal( @@ -664,12 +664,12 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) var billingAddressOwnership = orderDetailsType.FindNavigation(nameof(OrderDetails.BillingAddress)).ForeignKey; Assert.True(billingAddressOwnership.IsRequiredDependent); - + var billingAddressType = billingAddressOwnership.DeclaringEntityType; var shippingAddressOwnership = orderDetailsType.FindNavigation(nameof(OrderDetails.ShippingAddress)).ForeignKey; Assert.True(shippingAddressOwnership.IsRequiredDependent); - + var shippingAddressType = shippingAddressOwnership.DeclaringEntityType; Assert.Equal( @@ -698,7 +698,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal("IX_Order_CustomerId", ordersCustomerIndex.GetDefaultDatabaseName()); Assert.Equal("IX_Order_CustomerId", ordersCustomerIndex.GetDefaultDatabaseName( StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema))); - + Assert.Equal("PK_Order", orderPk.GetName()); Assert.Equal("PK_Order", orderPk.GetName( StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema))); @@ -1079,6 +1079,10 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping, bool m Assert.Equal( new[] { nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate) }, ordersInsertSproc.Parameters.Select(m => m.Name)); + + Assert.Equal( + new[] { 0, 1, 2 }, + ordersInsertSproc.Parameters.Select(m => m.Position)); Assert.Equal( new[] { nameof(Order.Id) }, @@ -1130,6 +1134,17 @@ private static void AssertSprocs(IRelationalModel model, Mapping mapping, bool m RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersInsertSproc.Name), Assert.Throws( () => ordersInsertSproc.IsOptional(specialCustomerType)).Message); + + var tableMapping = orderInsertMapping.TableMapping; + if (mappedToTables) + { + Assert.Equal("Order", tableMapping.Table.Name); + Assert.Same(orderInsertMapping, tableMapping.InsertStoredProcedureMapping); + } + else + { + Assert.Null(tableMapping); + } var billingAddressOwnership = orderDetailsType.FindNavigation(nameof(OrderDetails.BillingAddress)).ForeignKey; Assert.True(billingAddressOwnership.IsRequiredDependent); @@ -1969,7 +1984,7 @@ private IRelationalModel CreateTestModel( { cb.ToTable("Customer"); } - + if (mapToSprocs) { cb @@ -1987,7 +2002,7 @@ private IRelationalModel CreateTestModel( .HasParameter(c => c.SomeShort)) .DeleteUsingStoredProcedure( s => s.HasParameter(b => b.Id, p => p.HasName("DeleteId"))); - + if (mapping == Mapping.TPC) { cb.InsertUsingStoredProcedure(s => s.HasParameter("SpecialtyAk")); @@ -2108,7 +2123,7 @@ private IRelationalModel CreateTestModel( cb.OwnsOne(c => c.Details, cdb => cdb.ToTable("SpecialCustomer", "SpecialSchema")); } } - + if (mapToSprocs) { cb.OwnsOne( @@ -2194,7 +2209,7 @@ private IRelationalModel CreateTestModel( { cb.OwnsOne(c => c.Details, cdb => cdb.ToTable("ExtraSpecialCustomer", "ExtraSpecialSchema")); } - + if (mapToSprocs) { cb.OwnsOne( @@ -2367,7 +2382,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o { cb.Ignore(c => c.Orders); cb.Ignore(c => c.RelatedCustomer); - + if (mapToViews) { cb.ToView("CustomerView", tb => @@ -2388,7 +2403,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o { tb.Property(c => c.AbstractString); }); - + cb.SplitToTable("CustomerDetails", tb => { tb.Property(c => c.AbstractString); @@ -2402,7 +2417,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o if (mapToViews) { db.ToView("CustomerView"); - + db.SplitToView("CustomerDetailsView", tb => { tb.Property(d => d.BirthDay); @@ -2433,9 +2448,9 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o Assert.Equal(2, model.Views.Count()); var customerView = model.Views.Single(t => t.Name == "CustomerView"); - + Assert.Equal(2, customerView.EntityTypeMappings.Count()); - + var customerMapping = customerView.EntityTypeMappings.First(); Assert.True(customerMapping.IsSharedTablePrincipal); Assert.True(customerMapping.IsSplitEntityTypePrincipal); @@ -2483,7 +2498,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o Assert.True(detailsMapping.IsSplitEntityTypePrincipal); var customerDetailsTable = model.Tables.Single(t => t.Name == "CustomerDetails"); - + Assert.Equal(new[] { customerTable, customerDetailsTable }, customerType.GetTableMappings().Select(m => m.Table)); @@ -2494,7 +2509,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o Assert.False(customerSplitMapping.IsSplitEntityTypePrincipal); var detailsSplitMapping = customerDetailsTable.EntityTypeMappings.Last(); Assert.False(detailsSplitMapping.IsSharedTablePrincipal); - Assert.False(detailsSplitMapping.IsSplitEntityTypePrincipal); + Assert.False(detailsSplitMapping.IsSplitEntityTypePrincipal); Assert.Equal(new[] { customerTable, customerDetailsTable }, detailsType.GetTableMappings().Select(m => m.Table)); @@ -2593,7 +2608,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o Assert.Equal( new[] { customerTable, customerDetailsTable }, detailsType.GetTableMappings().Select(m => m.Table)); - + var detailsSplitMapping = customerDetailsTable.EntityTypeMappings.Single(); Assert.Null(detailsSplitMapping.IsSharedTablePrincipal); Assert.False(detailsSplitMapping.IsSplitEntityTypePrincipal); @@ -2678,7 +2693,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o c => c.Details, db => { db.ToTable("CustomerDetails"); - + db.SplitToTable( "Details", tb => { @@ -2762,7 +2777,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o Assert.True(customerDetailsFk.IsRequired); Assert.True(customerDetailsFk.IsRequiredDependent); Assert.Same(detailsType, customerDetailsFk.DeclaringEntityType); - + Assert.Single(detailsTable.UniqueConstraints); var detailsFkConstraint = detailsTable.ForeignKeyConstraints.Single(); Assert.Empty(detailsTable.Indexes); diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 1f82efb2ed0..3589c1e1527 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -321,6 +321,21 @@ public virtual void Duplicate_sproc_result_column_throws() Assert.Throws(() => sproc.AddResultColumn("Id")) .Message); } + + [ConditionalFact] + public virtual void Configuring_direction_on_RowsAffectedParameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var param = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasRowsAffectedParameter()).Metadata.GetInsertStoredProcedure()!.Parameters.Single(); + + Assert.Equal( + RelationalStrings.StoredProcedureParameterInvalidConfiguration("Direction", "RowsAffected", "BookLabel_Insert"), + Assert.Throws(() => param.Direction = ParameterDirection.Input) + .Message); + } } public abstract class RelationalInheritanceTestBase : InheritanceTestBase