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..07409031d2f 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -246,6 +246,10 @@ public virtual void RemoveAnnotationsHandledByConventions( ISequence sequence, IDictionary annotations) => RemoveConventionalAnnotationsHelper(sequence, annotations, IsHandledByConvention); + /// + public virtual void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary annotations) + => ((IAnnotationCodeGenerator)this).RemoveAnnotationsHandledByConventionsInternal(annotatable, annotations); + /// public virtual IReadOnlyList GenerateFluentApiCalls( IModel model, @@ -504,6 +508,11 @@ public virtual IReadOnlyList GenerateFluentApiCalls( return methodCallCodeFragments; } + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IAnnotatable annotatable, IDictionary annotations) + => ((IAnnotationCodeGenerator)this).GenerateFluentApiCallsInternal(annotatable, annotations); + /// public virtual IReadOnlyList GenerateDataAnnotationAttributes( IEntityType entityType, @@ -538,6 +547,12 @@ public virtual IReadOnlyList GenerateDataAnnotationAttrib return attributeCodeFragments; } + /// + public virtual IReadOnlyList GenerateDataAnnotationAttributes( + IAnnotatable annotatable, + IDictionary annotations) + => ((IAnnotationCodeGenerator)this).GenerateDataAnnotationAttributesInternal(annotatable, annotations); + /// /// 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..9370ffa3ff0 100644 --- a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs @@ -145,6 +145,11 @@ void RemoveAnnotationsHandledByConventions(ISequence sequence, IDictionaryThe annotatable to which the annotations are applied. /// The set of annotations from which to generate fluent API calls. void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary annotations) + => RemoveAnnotationsHandledByConventionsInternal(annotatable, annotations); + + // Issue #28537. + internal sealed void RemoveAnnotationsHandledByConventionsInternal( + IAnnotatable annotatable, IDictionary annotations) { switch (annotatable) { @@ -159,7 +164,7 @@ void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary case IEntityTypeMappingFragment fragment: RemoveAnnotationsHandledByConventions(fragment, annotations); return; - + case IProperty property: RemoveAnnotationsHandledByConventions(property, annotations); return; @@ -355,6 +360,11 @@ IReadOnlyList GenerateFluentApiCalls( /// The annotatable to which the annotations are applied. /// The set of annotations from which to generate fluent API calls. IReadOnlyList GenerateFluentApiCalls(IAnnotatable annotatable, IDictionary annotations) + => GenerateFluentApiCallsInternal(annotatable, annotations); + + // Issue #28537. + internal sealed IReadOnlyList GenerateFluentApiCallsInternal( + IAnnotatable annotatable, IDictionary annotations) => annotatable switch { IModel model => GenerateFluentApiCalls(model, annotations), @@ -405,6 +415,11 @@ IReadOnlyList GenerateDataAnnotationAttributes( IReadOnlyList GenerateDataAnnotationAttributes( IAnnotatable annotatable, IDictionary annotations) + => GenerateDataAnnotationAttributesInternal(annotatable, annotations); + + // Issue #28537. + internal sealed IReadOnlyList GenerateDataAnnotationAttributesInternal( + IAnnotatable annotatable, IDictionary annotations) => annotatable switch { IEntityType entityType => GenerateDataAnnotationAttributes(entityType, 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..c512db56faa 100644 --- a/src/EFCore.Relational/Scaffolding/IProviderConfigurationCodeGenerator.cs +++ b/src/EFCore.Relational/Scaffolding/IProviderConfigurationCodeGenerator.cs @@ -50,6 +50,10 @@ MethodCallCodeFragment GenerateUseProvider( /// The connection string to include in the code fragment. /// The code fragment. MethodCallCodeFragment GenerateUseProvider(string connectionString) + => GenerateUseProviderInternal(connectionString); + + // Issue #28537. + internal sealed MethodCallCodeFragment GenerateUseProviderInternal(string connectionString) { var useProviderCall = GenerateUseProvider( connectionString, diff --git a/src/EFCore.Relational/Scaffolding/ProviderCodeGenerator.cs b/src/EFCore.Relational/Scaffolding/ProviderCodeGenerator.cs index 235a9d3b91e..ffdbcabdc70 100644 --- a/src/EFCore.Relational/Scaffolding/ProviderCodeGenerator.cs +++ b/src/EFCore.Relational/Scaffolding/ProviderCodeGenerator.cs @@ -87,4 +87,8 @@ public abstract MethodCallCodeFragment GenerateUseProvider( return contextOptions; } + + /// + public virtual MethodCallCodeFragment GenerateUseProvider(string connectionString) + => ((IProviderConfigurationCodeGenerator)this).GenerateUseProviderInternal(connectionString); } diff --git a/src/EFCore.Relational/Update/UpdateSqlGenerator.cs b/src/EFCore.Relational/Update/UpdateSqlGenerator.cs index f81b001b8f7..7bbda5ca66b 100644 --- a/src/EFCore.Relational/Update/UpdateSqlGenerator.cs +++ b/src/EFCore.Relational/Update/UpdateSqlGenerator.cs @@ -62,6 +62,13 @@ public virtual ResultSetMapping AppendInsertOperation( out bool requiresTransaction) => AppendInsertReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + /// + 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 +113,13 @@ public virtual ResultSetMapping AppendUpdateOperation( out bool requiresTransaction) => AppendUpdateReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + /// + 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 +167,13 @@ public virtual ResultSetMapping AppendDeleteOperation( out bool requiresTransaction) => AppendDeleteReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + /// + 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)),