From 78ba70e2fa3a06962c72e2ac7dc22a4d0da74cda Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 29 Jul 2022 11:37:35 +0100 Subject: [PATCH] Add virtual methods to abstract base classes that implement interfaces using default member implementations Fixes #28516 --- .../Internal/ModelCodeGeneratorSelector.cs | 4 +- .../Design/AnnotationCodeGenerator.cs | 99 +++++++++++++++++++ .../Design/IAnnotationCodeGenerator.cs | 3 + .../Diagnostics/DbCommandInterceptor.cs | 12 +++ .../Diagnostics/DbConnectionInterceptor.cs | 30 ++++++ .../Diagnostics/DbTransactionInterceptor.cs | 5 +- src/EFCore.Relational/Metadata/IColumn.cs | 18 ++-- src/EFCore.Relational/Metadata/IColumnBase.cs | 2 +- .../IProviderConfigurationCodeGenerator.cs | 1 + .../Scaffolding/ProviderCodeGenerator.cs | 16 +++ .../Update/IUpdateSqlGenerator.cs | 3 + .../Update/UpdateSqlGenerator.cs | 24 +++++ .../Diagnostics/SaveChangesInterceptor.cs | 29 +++--- src/EFCore/Internal/DbContextFactory.cs | 9 ++ .../RelationalApiConsistencyTest.cs | 12 ++- 15 files changed, 239 insertions(+), 28 deletions(-) diff --git a/src/EFCore.Design/Scaffolding/Internal/ModelCodeGeneratorSelector.cs b/src/EFCore.Design/Scaffolding/Internal/ModelCodeGeneratorSelector.cs index 5eaff106a3c..69e0476b7e8 100644 --- a/src/EFCore.Design/Scaffolding/Internal/ModelCodeGeneratorSelector.cs +++ b/src/EFCore.Design/Scaffolding/Internal/ModelCodeGeneratorSelector.cs @@ -27,8 +27,6 @@ public ModelCodeGeneratorSelector(IEnumerable services) /// public virtual IModelCodeGenerator Select(ModelCodeGenerationOptions options) - => _templatedModelGenerators - .Where(g => options.ProjectDir != null && g.HasTemplates(options.ProjectDir)) - .LastOrDefault() + => _templatedModelGenerators.LastOrDefault(g => options.ProjectDir != null && g.HasTemplates(options.ProjectDir)) ?? Select(options.Language); } diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 4329a6da959..e5370165c98 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -246,6 +246,69 @@ public virtual void RemoveAnnotationsHandledByConventions( ISequence sequence, IDictionary annotations) => RemoveConventionalAnnotationsHelper(sequence, annotations, IsHandledByConvention); + // Issue #28537. Remember to update both the class and the interface implementation. + /// + public virtual void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary annotations) + { + switch (annotatable) + { + case IModel model: + RemoveAnnotationsHandledByConventions(model, annotations); + return; + + case IEntityType entityType: + RemoveAnnotationsHandledByConventions(entityType, annotations); + return; + + case IEntityTypeMappingFragment fragment: + RemoveAnnotationsHandledByConventions(fragment, annotations); + return; + + case IProperty property: + RemoveAnnotationsHandledByConventions(property, annotations); + return; + + case IKey key: + RemoveAnnotationsHandledByConventions(key, annotations); + return; + + case IForeignKey foreignKey: + RemoveAnnotationsHandledByConventions(foreignKey, annotations); + return; + + case INavigation navigation: + RemoveAnnotationsHandledByConventions(navigation, annotations); + return; + + case ISkipNavigation skipNavigation: + RemoveAnnotationsHandledByConventions(skipNavigation, annotations); + return; + + case ICheckConstraint checkConstraint: + RemoveAnnotationsHandledByConventions(checkConstraint, annotations); + return; + + case IIndex index: + RemoveAnnotationsHandledByConventions(index, annotations); + return; + + case ITrigger trigger: + RemoveAnnotationsHandledByConventions(trigger, annotations); + return; + + case IRelationalPropertyOverrides overrides: + RemoveAnnotationsHandledByConventions(overrides, annotations); + return; + + case ISequence sequence: + RemoveAnnotationsHandledByConventions(sequence, annotations); + return; + + default: + throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType())); + } + } + /// public virtual IReadOnlyList GenerateFluentApiCalls( IModel model, @@ -504,6 +567,29 @@ public virtual IReadOnlyList GenerateFluentApiCalls( return methodCallCodeFragments; } + // Issue #28537. Remember to update both the class and the interface implementation. + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IAnnotatable annotatable, IDictionary annotations) + => annotatable switch + { + IModel model => GenerateFluentApiCalls(model, annotations), + IEntityType entityType => GenerateFluentApiCalls(entityType, annotations), + IEntityTypeMappingFragment fragment => GenerateFluentApiCalls(fragment, annotations), + IProperty property => GenerateFluentApiCalls(property, annotations), + IRelationalPropertyOverrides overrides => GenerateFluentApiCalls(overrides, annotations), + IKey key => GenerateFluentApiCalls(key, annotations), + IForeignKey foreignKey => GenerateFluentApiCalls(foreignKey, annotations), + INavigation navigation => GenerateFluentApiCalls(navigation, annotations), + ISkipNavigation skipNavigation => GenerateFluentApiCalls(skipNavigation, annotations), + IIndex index => GenerateFluentApiCalls(index, annotations), + ICheckConstraint checkConstraint => GenerateFluentApiCalls(checkConstraint, annotations), + ITrigger trigger => GenerateFluentApiCalls(trigger, annotations), + ISequence sequence => GenerateFluentApiCalls(sequence, annotations), + + _ => throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType())) + }; + /// public virtual IReadOnlyList GenerateDataAnnotationAttributes( IEntityType entityType, @@ -538,6 +624,19 @@ public virtual IReadOnlyList GenerateDataAnnotationAttrib return attributeCodeFragments; } + + // Issue #28537. Remember to update both the class and the interface implementation. + /// + public virtual IReadOnlyList GenerateDataAnnotationAttributes( + IAnnotatable annotatable, + IDictionary annotations) + => annotatable switch + { + IEntityType entityType => GenerateDataAnnotationAttributes(entityType, annotations), + IProperty property => GenerateDataAnnotationAttributes(property, annotations), + _ => throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType())) + }; + /// /// Checks if the given is handled by convention when /// applied to the given . diff --git a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs index 87fdb3aa92f..088e6850772 100644 --- a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs @@ -144,6 +144,7 @@ void RemoveAnnotationsHandledByConventions(ISequence sequence, IDictionary /// The annotatable to which the annotations are applied. /// The set of annotations from which to generate fluent API calls. + // Issue #28537. Remember to update both the class and the interface implementation. void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary annotations) { switch (annotatable) @@ -354,6 +355,7 @@ IReadOnlyList GenerateFluentApiCalls( /// /// The annotatable to which the annotations are applied. /// The set of annotations from which to generate fluent API calls. + // Issue #28537. Remember to update both the class and the interface implementation. IReadOnlyList GenerateFluentApiCalls(IAnnotatable annotatable, IDictionary annotations) => annotatable switch { @@ -391,6 +393,7 @@ IReadOnlyList GenerateDataAnnotationAttributes( /// /// The property to which the annotations are applied. /// The set of annotations from which to generate fluent API calls. + // Issue #28537. Remember to update both the class and the interface implementation. IReadOnlyList GenerateDataAnnotationAttributes( IProperty property, IDictionary annotations) diff --git a/src/EFCore.Relational/Diagnostics/DbCommandInterceptor.cs b/src/EFCore.Relational/Diagnostics/DbCommandInterceptor.cs index 165cde4038f..c32ca347aa5 100644 --- a/src/EFCore.Relational/Diagnostics/DbCommandInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/DbCommandInterceptor.cs @@ -20,6 +20,10 @@ public virtual InterceptionResult CommandCreating(CommandCorrelatedEv public virtual DbCommand CommandCreated(CommandEndEventData eventData, DbCommand result) => result; + /// + public virtual DbCommand CommandInitialized(CommandEndEventData eventData, DbCommand result) + => result; + /// public virtual InterceptionResult ReaderExecuting( DbCommand command, @@ -122,6 +126,14 @@ public virtual Task CommandFailedAsync( CancellationToken cancellationToken = default) => Task.CompletedTask; + /// + public virtual InterceptionResult DataReaderClosing(DbCommand command, DataReaderClosingEventData eventData, InterceptionResult result) + => result; + + /// + public virtual ValueTask DataReaderClosingAsync(DbCommand command, DataReaderClosingEventData eventData, InterceptionResult result) + => new(result); + /// public virtual InterceptionResult DataReaderDisposing( DbCommand command, diff --git a/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs b/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs index 135e4c01cc4..a320c514b1f 100644 --- a/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs @@ -12,6 +12,16 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics; /// public abstract class DbConnectionInterceptor : IDbConnectionInterceptor { + /// + public virtual InterceptionResult ConnectionCreating( + ConnectionCreatingEventData eventData, + InterceptionResult result) + => result; + + /// + public virtual DbConnection ConnectionCreated(ConnectionCreatedEventData eventData, DbConnection result) + => result; + /// public virtual InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) => result; @@ -56,6 +66,26 @@ public virtual void ConnectionClosed(DbConnection connection, ConnectionEndEvent public virtual Task ConnectionClosedAsync(DbConnection connection, ConnectionEndEventData eventData) => Task.CompletedTask; + /// + public virtual InterceptionResult ConnectionDisposing(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) + => result; + + /// + public virtual ValueTask ConnectionDisposingAsync( + DbConnection connection, + ConnectionEventData eventData, + InterceptionResult result) + => new(result); + + /// + public virtual void ConnectionDisposed(DbConnection connection, ConnectionEndEventData eventData) + { + } + + /// + public virtual Task ConnectionDisposedAsync(DbConnection connection, ConnectionEndEventData eventData) + => Task.CompletedTask; + /// public virtual void ConnectionFailed(DbConnection connection, ConnectionErrorEventData eventData) { diff --git a/src/EFCore.Relational/Diagnostics/DbTransactionInterceptor.cs b/src/EFCore.Relational/Diagnostics/DbTransactionInterceptor.cs index 801330c4a95..853f9134cad 100644 --- a/src/EFCore.Relational/Diagnostics/DbTransactionInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/DbTransactionInterceptor.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 System.Data; - namespace Microsoft.EntityFrameworkCore.Diagnostics; /// @@ -194,7 +192,8 @@ public virtual void TransactionFailed(DbTransaction transaction, TransactionErro } /// - public virtual Task TransactionFailedAsync(DbTransaction transaction, + public virtual Task TransactionFailedAsync( + DbTransaction transaction, TransactionErrorEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; diff --git a/src/EFCore.Relational/Metadata/IColumn.cs b/src/EFCore.Relational/Metadata/IColumn.cs index dd54cc67a9f..685a92af79b 100644 --- a/src/EFCore.Relational/Metadata/IColumn.cs +++ b/src/EFCore.Relational/Metadata/IColumn.cs @@ -68,13 +68,13 @@ bool IsRowVersion /// Gets the column order. /// /// The column order. - public virtual int? Order + int? Order => PropertyMappings.First().Property.GetColumnOrder(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// Returns the object that is used as the default value for this column. /// - public virtual object? DefaultValue + object? DefaultValue { get { @@ -88,7 +88,7 @@ public virtual object? DefaultValue /// /// The default value. /// True if the default value was explicitly set; false otherwise. - public virtual bool TryGetDefaultValue(out object? defaultValue) + bool TryGetDefaultValue(out object? defaultValue) { foreach (var mapping in PropertyMappings) { @@ -114,14 +114,14 @@ public virtual bool TryGetDefaultValue(out object? defaultValue) /// /// Returns the SQL expression that is used as the default value for this column. /// - public virtual string? DefaultValueSql + string? DefaultValueSql => PropertyMappings.First().Property .GetDefaultValueSql(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// Returns the SQL expression that is used as the computed value for this column. /// - public virtual string? ComputedColumnSql + string? ComputedColumnSql => PropertyMappings.First().Property .GetComputedColumnSql(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); @@ -129,21 +129,21 @@ public virtual string? ComputedColumnSql /// Returns whether the value of the computed column this property is mapped to is stored in the database, or calculated when /// it is read. /// - public virtual bool? IsStored + bool? IsStored => PropertyMappings.First().Property .GetIsStored(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// Comment for this column /// - public virtual string? Comment + string? Comment => PropertyMappings.First().Property .GetComment(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// Collation for this column /// - public virtual string? Collation + string? Collation => PropertyMappings.First().Property .GetCollation(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); @@ -151,7 +151,7 @@ public virtual string? Collation /// Gets the for this column. /// /// The comparer. - public virtual ValueComparer ProviderValueComparer + ValueComparer ProviderValueComparer => PropertyMappings.First().Property .GetProviderValueComparer(); diff --git a/src/EFCore.Relational/Metadata/IColumnBase.cs b/src/EFCore.Relational/Metadata/IColumnBase.cs index 565bee56ee4..af8da463f5a 100644 --- a/src/EFCore.Relational/Metadata/IColumnBase.cs +++ b/src/EFCore.Relational/Metadata/IColumnBase.cs @@ -46,7 +46,7 @@ public interface IColumnBase : IAnnotatable /// /// An entity type. /// The property mapping or if not found. - public virtual IColumnMappingBase? FindColumnMapping(IReadOnlyEntityType entityType) + IColumnMappingBase? FindColumnMapping(IReadOnlyEntityType entityType) { for (var i = 0; i < PropertyMappings.Count; i++) { diff --git a/src/EFCore.Relational/Scaffolding/IProviderConfigurationCodeGenerator.cs b/src/EFCore.Relational/Scaffolding/IProviderConfigurationCodeGenerator.cs index e2ccd82b1da..8989ab1b9f8 100644 --- a/src/EFCore.Relational/Scaffolding/IProviderConfigurationCodeGenerator.cs +++ b/src/EFCore.Relational/Scaffolding/IProviderConfigurationCodeGenerator.cs @@ -49,6 +49,7 @@ MethodCallCodeFragment GenerateUseProvider( /// /// The connection string to include in the code fragment. /// The code fragment. + // Issue #28537. Remember to update both the class and the interface implementation. MethodCallCodeFragment GenerateUseProvider(string connectionString) { var useProviderCall = GenerateUseProvider( diff --git a/src/EFCore.Relational/Scaffolding/ProviderCodeGenerator.cs b/src/EFCore.Relational/Scaffolding/ProviderCodeGenerator.cs index 235a9d3b91e..f7b20cd7081 100644 --- a/src/EFCore.Relational/Scaffolding/ProviderCodeGenerator.cs +++ b/src/EFCore.Relational/Scaffolding/ProviderCodeGenerator.cs @@ -87,4 +87,20 @@ public abstract MethodCallCodeFragment GenerateUseProvider( return contextOptions; } + + // Issue #28537. Remember to update both the class and the interface implementation. + /// + public virtual MethodCallCodeFragment GenerateUseProvider(string connectionString) + { + var useProviderCall = GenerateUseProvider( + connectionString, + GenerateProviderOptions()); + var contextOptions = GenerateContextOptions(); + if (contextOptions != null) + { + useProviderCall = useProviderCall.Chain(contextOptions); + } + + return useProviderCall; + } } diff --git a/src/EFCore.Relational/Update/IUpdateSqlGenerator.cs b/src/EFCore.Relational/Update/IUpdateSqlGenerator.cs index 76d1524eeec..8d8481c4fa8 100644 --- a/src/EFCore.Relational/Update/IUpdateSqlGenerator.cs +++ b/src/EFCore.Relational/Update/IUpdateSqlGenerator.cs @@ -101,6 +101,7 @@ ResultSetMapping AppendDeleteOperation( /// The command that represents the delete operation. /// The ordinal of this command in the batch. /// The for the command. + // Issue #28537. Remember to update both the class and the interface implementation. ResultSetMapping AppendDeleteOperation( StringBuilder commandStringBuilder, IReadOnlyModificationCommand command, @@ -128,6 +129,7 @@ ResultSetMapping AppendInsertOperation( /// The command that represents the delete operation. /// The ordinal of this command in the batch. /// The for the command. + // Issue #28537. Remember to update both the class and the interface implementation. ResultSetMapping AppendInsertOperation( StringBuilder commandStringBuilder, IReadOnlyModificationCommand command, @@ -155,6 +157,7 @@ ResultSetMapping AppendUpdateOperation( /// The command that represents the delete operation. /// The ordinal of this command in the batch. /// The for the command. + // Issue #28537. Remember to update both the class and the interface implementation. ResultSetMapping AppendUpdateOperation( StringBuilder commandStringBuilder, IReadOnlyModificationCommand command, diff --git a/src/EFCore.Relational/Update/UpdateSqlGenerator.cs b/src/EFCore.Relational/Update/UpdateSqlGenerator.cs index f81b001b8f7..8de6881c422 100644 --- a/src/EFCore.Relational/Update/UpdateSqlGenerator.cs +++ b/src/EFCore.Relational/Update/UpdateSqlGenerator.cs @@ -62,6 +62,14 @@ public virtual ResultSetMapping AppendInsertOperation( out bool requiresTransaction) => AppendInsertReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + // Issue #28537. Remember to update both the class and the interface implementation. + /// + public virtual ResultSetMapping AppendInsertOperation( + StringBuilder commandStringBuilder, + IReadOnlyModificationCommand command, + int commandPosition) + => AppendInsertOperation(commandStringBuilder, command, commandPosition, out _); + /// /// Appends SQL for inserting a row to the commands being built, via an INSERT containing an optional RETURNING clause to retrieve /// any database-generated values. @@ -106,6 +114,14 @@ public virtual ResultSetMapping AppendUpdateOperation( out bool requiresTransaction) => AppendUpdateReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + // Issue #28537. Remember to update both the class and the interface implementation. + /// + public virtual ResultSetMapping AppendUpdateOperation( + StringBuilder commandStringBuilder, + IReadOnlyModificationCommand command, + int commandPosition) + => AppendUpdateOperation(commandStringBuilder, command, commandPosition, out _); + /// /// Appends SQL for updating a row to the commands being built, via an UPDATE containing an RETURNING clause to retrieve any /// database-generated values or for concurrency checking. @@ -153,6 +169,14 @@ public virtual ResultSetMapping AppendDeleteOperation( out bool requiresTransaction) => AppendDeleteReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + // Issue #28537. Remember to update both the class and the interface implementation. + /// + public virtual ResultSetMapping AppendDeleteOperation( + StringBuilder commandStringBuilder, + IReadOnlyModificationCommand command, + int commandPosition) + => AppendDeleteOperation(commandStringBuilder, command, commandPosition, out _); + /// /// Appends SQL for deleting a row to the commands being built, via a DELETE containing a RETURNING clause for concurrency checking. /// diff --git a/src/EFCore/Diagnostics/SaveChangesInterceptor.cs b/src/EFCore/Diagnostics/SaveChangesInterceptor.cs index b190b96539c..61b58f46bd5 100644 --- a/src/EFCore/Diagnostics/SaveChangesInterceptor.cs +++ b/src/EFCore/Diagnostics/SaveChangesInterceptor.cs @@ -25,11 +25,6 @@ public virtual void SaveChangesFailed(DbContextErrorEventData eventData) { } - /// - public virtual void SaveChangesCanceled(DbContextEventData eventData) - { - } - /// public virtual ValueTask> SavingChangesAsync( DbContextEventData eventData, @@ -45,14 +40,26 @@ public virtual ValueTask SavedChangesAsync( => new(result); /// - public virtual Task SaveChangesFailedAsync( - DbContextErrorEventData eventData, - CancellationToken cancellationToken = default) + public virtual Task SaveChangesFailedAsync(DbContextErrorEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; /// - public virtual Task SaveChangesCanceledAsync( - DbContextEventData eventData, - CancellationToken cancellationToken = default) + public virtual void SaveChangesCanceled(DbContextEventData eventData) + { + } + + /// + public virtual Task SaveChangesCanceledAsync(DbContextEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; + + /// + public virtual InterceptionResult ThrowingConcurrencyException(ConcurrencyExceptionEventData eventData, InterceptionResult result) + => result; + + /// + public virtual ValueTask ThrowingConcurrencyExceptionAsync( + ConcurrencyExceptionEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + => new(result); } diff --git a/src/EFCore/Internal/DbContextFactory.cs b/src/EFCore/Internal/DbContextFactory.cs index b5b861f7dc1..f3c8dca9815 100644 --- a/src/EFCore/Internal/DbContextFactory.cs +++ b/src/EFCore/Internal/DbContextFactory.cs @@ -40,4 +40,13 @@ public DbContextFactory( /// public virtual TContext CreateDbContext() => _factory(_serviceProvider, _options); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your 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 Task CreateDbContextAsync(CancellationToken cancellationToken = default) + => Task.FromResult(CreateDbContext()); } diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index fb491b4f501..8a8f77c70f6 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -19,6 +19,16 @@ protected override void AddServices(ServiceCollection serviceCollection) protected override Assembly TargetAssembly => typeof(RelationalDatabase).Assembly; + protected override HashSet NonCancellableAsyncMethods + { + get + { + var methods = base.NonCancellableAsyncMethods; + methods.Add(typeof(DbConnectionInterceptor).GetMethod(nameof(DbConnectionInterceptor.ConnectionDisposedAsync))); + return methods; + } + } + [ConditionalFact] public void Readonly_relational_metadata_methods_have_expected_name() { @@ -319,7 +329,7 @@ public override typeof(RelationalConnectionDiagnosticsLogger).GetMethod( nameof(IRelationalConnectionDiagnosticsLogger.ConnectionDisposedAsync)) }; - + public override HashSet MetadataMethodExceptions { get; } = new() { typeof(IMutableStoredProcedure).GetMethod(nameof(IMutableStoredProcedure.AddParameter)),