From 65510bd7f5c4bf6f7aa767334930bb6a88273ba7 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 14 Apr 2017 09:27:04 -0700 Subject: [PATCH] Update EventIds to be unique and be consistent with ILogger/DiagnosticSource Issues #218 #6802 (Doesn't cover #6946 - Template-based format strings.) This change introduces unique event IDs and a pattern such that every time an event is logged it is sent to both ILogger and DiagnosticSource. Specifically: * EventIds are globally unique across core/relational/any given provider. (As discussed in #218) * The same EventIds are used for ILogger and DiagnosticSource (ILogger uses the int/name tuple, DiagnosticSource uses just the name) * EventIds align with logger categories * Automated tests for the EventId/LoggerExtensions pattern--every EventId has a LoggerExtension method that works as expected. * Warnings configuration is updated to use the new event ids * Updated IsEnabled to handle warnings as errors and ignored warnings more transparently. --- .../Internal/DesignTimeServicesBuilder.cs | 3 + .../Design/Internal/OperationLogger.cs | 5 +- .../Infrastructure/DesignEventId.cs | 145 ++- .../Internal/DesignLoggerExtensions.cs | 462 ++++++++- .../Migrations/Design/MigrationsScaffolder.cs | 65 +- .../ScaffoldingServiceCollectionExtensions.cs | 3 + .../Internal/InMemoryLoggerExtensions.cs | 62 +- .../Infrastructure/InMemoryEventId.cs | 36 +- .../Storage/Internal/IInMemoryStore.cs | 2 +- .../Storage/Internal/InMemoryDatabase.cs | 4 +- .../Storage/Internal/InMemoryStore.cs | 9 +- .../Internal/InMemoryTransactionManager.cs | 19 +- .../WarningConfigurationBuilderExtensions.cs | 89 -- .../ReverseEngineering/E2ETestBase.cs | 4 +- .../Infrastructure/RelationalDesignEventId.cs | 260 ++++- .../RelationalDesignLoggerExtensions.cs | 930 ++++++++++++++++- .../Metadata/SequenceModel.cs | 3 + .../RelationalDesignStrings.Designer.cs | 172 +++- .../Properties/RelationalDesignStrings.resx | 68 +- .../RelationalScaffoldingModelFactory.cs | 87 +- .../QueryNoClientEvalTestBase.cs | 36 +- .../WarningsTestBase.cs | 8 +- .../EFCore.Relational.csproj | 1 - ...ntityFrameworkRelationalServicesBuilder.cs | 5 - .../Infrastructure/RelationalEventId.cs | 202 +++- .../RelationalModelValidator.cs | 11 +- .../Internal/RelationalDiagnostics.cs | 362 ------- .../Internal/RelationalLoggerExtensions.cs | 935 ++++++++++++++++++ .../Migrations/Internal/Migrator.cs | 31 +- .../Properties/RelationalStrings.Designer.cs | 74 +- .../Properties/RelationalStrings.resx | 30 +- .../Internal/EqualsTranslator.cs | 11 +- ...mpositeMethodCallTranslatorDependencies.cs | 6 +- .../Query/RelationalQueryModelVisitor.cs | 21 +- .../Storage/Internal/RelationalCommand.cs | 42 +- .../Internal/RelationalCommandBuilder.cs | 27 +- .../RelationalCommandBuilderFactory.cs | 27 +- .../Internal/RelationalLoggerExtensions.cs | 152 --- .../Storage/RelationalConnection.cs | 94 +- .../RelationalConnectionDependencies.cs | 42 +- .../Storage/RelationalDataReader.cs | 17 +- .../Storage/RelationalTransaction.cs | 48 +- .../WarningConfigurationBuilderExtensions.cs | 91 -- src/EFCore.Specification.Tests/TestHelpers.cs | 148 +++ .../Infrastructure/SqlServerDesignEventId.cs | 89 +- .../Internal/SqlServerDatabaseModelFactory.cs | 116 +-- .../SqlServerDesignLoggerExtensions.cs | 187 +++- .../SqlServerScaffoldingModelFactory.cs | 8 +- .../SqlServerDesignStrings.Designer.cs | 176 +--- .../Properties/SqlServerDesignStrings.resx | 67 +- .../Infrastructure/SqlServerEventId.cs | 35 +- .../Internal/SqlServerModelValidator.cs | 42 +- .../Internal/SqlServerLoggerExtensions.cs | 77 +- .../Infrastructure/SqliteEventId.cs | 35 +- .../Internal/SqliteModelValidator.cs | 10 +- .../Internal/SqliteLoggerExtensions.cs | 67 +- .../Infrastructure/SqliteDesignEventId.cs | 44 +- .../Internal/SqliteDatabaseModelFactory.cs | 50 +- .../Internal/SqliteDesignLoggerExtensions.cs | 107 +- .../Internal/SqliteScaffoldingModelFactory.cs | 6 +- .../SqliteDesignStrings.Designer.cs | 62 +- .../Properties/SqliteDesignStrings.resx | 25 +- src/EFCore/DbContext.cs | 17 +- src/EFCore/EFCore.csproj | 1 + .../Internal/CoreLoggerExtensions.cs | 429 +++++++- src/EFCore/Infrastructure/CoreEventId.cs | 150 ++- .../Infrastructure/CoreModelValidator.cs | 12 +- .../EntityFrameworkServicesBuilder.cs | 6 + .../Infrastructure/IDiagnosticsLogger.cs | 27 + .../Infrastructure/IInterceptingLogger.cs | 39 +- .../ModelValidatorDependencies.cs | 6 +- .../Infrastructure/WarningsConfiguration.cs | 23 +- .../WarningsConfigurationBuilder.cs | 51 +- src/EFCore/Internal/DiagnosticsLogger.cs | 45 + src/EFCore/Internal/InterceptingLogger.cs | 69 +- src/EFCore/Internal/ServiceProviderCache.cs | 10 +- src/EFCore/LoggerCategory.cs | 11 +- src/EFCore/Properties/CoreStrings.Designer.cs | 32 - src/EFCore/Properties/CoreStrings.resx | 15 - src/EFCore/Query/EntityQueryModelVisitor.cs | 43 +- .../ParameterExtractingExpressionVisitor.cs | 8 +- .../Internal/AsyncLinqOperatorProvider.cs | 14 +- src/EFCore/Query/Internal/IncludeCompiler.cs | 11 +- .../Query/Internal/LinqOperatorProvider.cs | 14 +- .../QueryCompilationContextDependencies.cs | 58 +- src/EFCore/Query/Internal/QueryCompiler.cs | 24 +- src/EFCore/Query/QueryCompilationContext.cs | 2 +- src/EFCore/Storage/ExecutionStrategy.cs | 2 +- .../Storage/ExecutionStrategyContext.cs | 2 +- .../ExecutionStrategyContextDependencies.cs | 6 +- .../Design/Internal/OperationLoggerTest.cs | 5 +- test/EFCore.Design.Tests/DesignEventIdTest.cs | 53 + .../Design/MigrationScaffolderTest.cs | 5 +- .../WarningsTest.cs | 10 +- .../InMemoryDatabaseCreatorTest.cs | 13 +- .../InMemoryDatabaseTest.cs | 4 +- .../InMemoryEventIdTest.cs | 30 + .../InMemoryTransactionManagerTest.cs | 30 +- .../RelationalDesignEventIdTest.cs | 30 + .../RelationalScaffoldingModelFactoryTest.cs | 24 +- .../TestLogger.cs | 4 +- .../MigrationCommandExecutorTest.cs | 9 +- .../MigrationCommandListBuilderTest.cs | 9 +- .../RelationalEventIdTest.cs | 198 ++++ .../RelationalModelValidatorTest.cs | 9 +- .../Storage/RawSqlCommandBuilderTest.cs | 25 +- .../Storage/RelationalCommandBuilderTest.cs | 17 +- .../Storage/RelationalCommandTest.cs | 149 +-- .../RelationalTransactionExtensionsTest.cs | 7 +- .../TestUtilities/FakeInterceptingLogger.cs | 4 +- .../FakeProvider/FakeRelationalConnection.cs | 13 +- .../ReaderModificationCommandBatchTest.cs | 8 +- .../ReverseEngineering/SqlServerE2ETests.cs | 6 + .../SqlServerDatabaseModelFactoryTest.cs | 2 +- .../SqlServerDatabaseModelFixture.cs | 11 +- .../SqlServerDesignEventIdTest.cs | 29 + .../DataAnnotationSqlServerTest.cs | 2 +- .../Utilities/SqlServerDatabaseCleaner.cs | 9 +- .../TestRelationalCommandBuilderFactory.cs | 34 +- .../Utilities/TestRelationalTransaction.cs | 5 +- .../SqlServerHistoryRepositoryTest.cs | 9 +- .../SqlServerConnectionTest.cs | 13 +- .../SqlServerDatabaseCreatorTest.cs | 18 +- .../SqlServerEventIdTest.cs | 48 + .../SqlServerModelValidatorTest.cs | 9 +- ...rverModificationCommandBatchFactoryTest.cs | 17 +- .../SqlServerModificationCommandBatchTest.cs | 9 +- .../SqliteDatabaseModelFactoryTest.cs | 8 +- .../SqliteScaffoldingModelFactoryTest.cs | 13 +- .../SqliteDesignEventIdTest.cs | 29 + .../BadDataSqliteTest.cs | 31 +- .../SqliteDatabaseCleaner.cs | 9 +- .../Migrations/SqliteHistoryRepositoryTest.cs | 8 +- test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs | 49 + .../SqliteModelValidatorTest.cs | 9 +- test/EFCore.Tests/DbContextTest.cs | 4 +- .../Infrastructure/CoreEventIdTest.cs | 49 + .../Infrastructure/InterceptingLoggerTest.cs | 18 +- .../Infrastructure/LoggerCategoryTest.cs | 2 + .../Infrastructure/ModelValidatorTest.cs | 11 +- .../Internal/InternalEntityTypeBuilderTest.cs | 9 +- .../Metadata/Internal/PropertyBaseTest.cs | 11 +- .../ModelBuilderTest/ModelBuilderTestBase.cs | 8 +- test/EFCore.Tests/ModelSourceTest.cs | 9 +- .../TestInMemoryTransactionManager.cs | 2 +- 145 files changed, 5907 insertions(+), 2428 deletions(-) delete mode 100644 src/EFCore.InMemory/WarningConfigurationBuilderExtensions.cs delete mode 100644 src/EFCore.Relational/Internal/RelationalDiagnostics.cs create mode 100644 src/EFCore.Relational/Internal/RelationalLoggerExtensions.cs delete mode 100644 src/EFCore.Relational/Storage/Internal/RelationalLoggerExtensions.cs delete mode 100644 src/EFCore.Relational/WarningConfigurationBuilderExtensions.cs create mode 100644 src/EFCore/Infrastructure/IDiagnosticsLogger.cs create mode 100644 src/EFCore/Internal/DiagnosticsLogger.cs create mode 100644 test/EFCore.Design.Tests/DesignEventIdTest.cs create mode 100644 test/EFCore.InMemory.Tests/InMemoryEventIdTest.cs create mode 100644 test/EFCore.Relational.Design.Tests/RelationalDesignEventIdTest.cs create mode 100644 test/EFCore.Relational.Tests/RelationalEventIdTest.cs create mode 100644 test/EFCore.SqlServer.Design.Tests/SqlServerDesignEventIdTest.cs create mode 100644 test/EFCore.SqlServer.Tests/SqlServerEventIdTest.cs create mode 100644 test/EFCore.Sqlite.Design.Tests/SqliteDesignEventIdTest.cs create mode 100644 test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs create mode 100644 test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs diff --git a/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs b/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs index 89358e3ff99..233a06cdd05 100644 --- a/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs +++ b/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.IO; using System.Reflection; using JetBrains.Annotations; @@ -93,6 +94,8 @@ protected virtual IServiceCollection ConfigureContextServices( .AddTransient() .AddTransient(_ => contextServices.GetService()) .AddTransient(_ => contextServices.GetService(typeof(IInterceptingLogger<>))) + .AddTransient(_ => contextServices.GetService(typeof(IDiagnosticsLogger<>))) + .AddTransient(_ => contextServices.GetService()) .AddTransient(_ => contextServices.GetService()) .AddTransient(_ => contextServices.GetService()) .AddTransient(_ => contextServices.GetService()) diff --git a/src/EFCore.Design/Design/Internal/OperationLogger.cs b/src/EFCore.Design/Design/Internal/OperationLogger.cs index f4ac213c468..5bcd0fd1b34 100644 --- a/src/EFCore.Design/Design/Internal/OperationLogger.cs +++ b/src/EFCore.Design/Design/Internal/OperationLogger.cs @@ -5,7 +5,6 @@ using System.Text; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Design.Internal @@ -55,8 +54,8 @@ public virtual void Log( Func formatter) { // Only show SQL when verbose - if (_categoryName == typeof(RelationalCommandBuilderFactory).FullName - && eventId.Id == (int)RelationalEventId.ExecutedCommand) + if (_categoryName == LoggerCategory.Database.Sql.Name + && eventId.Id == RelationalEventId.CommandExecuted.Id) { logLevel = LogLevel.Debug; } diff --git a/src/EFCore.Design/Infrastructure/DesignEventId.cs b/src/EFCore.Design/Infrastructure/DesignEventId.cs index e60cae32ffe..3ef2156b3e5 100644 --- a/src/EFCore.Design/Infrastructure/DesignEventId.cs +++ b/src/EFCore.Design/Infrastructure/DesignEventId.cs @@ -1,24 +1,137 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; +using Microsoft.Extensions.Logging; + namespace Microsoft.EntityFrameworkCore.Infrastructure { - public enum DesignEventId + /// + /// + /// Event IDs for design events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// + /// + public static class DesignEventId { - ForceRemoveMigration = 1, - RemovingMigration, - NoMigrationFile, - NoMigrationMetadataFile, - ManuallyDeleted, - RemovingSnapshot, - NoSnapshotFile, - WritingSnapshot, - ReusingNamespace, - ReusingDirectory, - RevertingSnapshot, - WritingMigration, - ReusingSnapshotName, - DestructiveOperation, - ForeignMigrations + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Migrations events + MigrationForceRemove = CoreEventId.CoreDesignBaseId, + MigrationRemoving, + MigrationFileNotFound, + MigrationMetadataFileNotFound, + MigrationManuallyDeleted, + SnapshotRemoving, + SnapshotFileNotFound, + SnapshotWriting, + NamespaceReusing, + DirectoryReusing, + SnapshotReverting, + MigrationWriting, + SnapshotNameReusing, + DestructiveOperation, + ForeignMigrations + } + + private static readonly string _migrationsPrefix = LoggerCategory.Migrations.Name + "."; + private static EventId MakeMigrationsId(Id id) => new EventId((int)id, _migrationsPrefix + id); + + /// + /// Removing a migration without checking the database. + /// This event is in the category. + /// + public static readonly EventId MigrationForceRemove = MakeMigrationsId(Id.MigrationForceRemove); + + /// + /// Removing migration. + /// This event is in the category. + /// + public static readonly EventId MigrationRemoving = MakeMigrationsId(Id.MigrationRemoving); + + /// + /// A migration file was not found. + /// This event is in the category. + /// + public static readonly EventId MigrationFileNotFound = MakeMigrationsId(Id.MigrationFileNotFound); + + /// + /// A metadata file was not found. + /// This event is in the category. + /// + public static readonly EventId MigrationMetadataFileNotFound = MakeMigrationsId(Id.MigrationMetadataFileNotFound); + + /// + /// A manual migration deletion was detected. + /// This event is in the category. + /// + public static readonly EventId MigrationManuallyDeleted = MakeMigrationsId(Id.MigrationManuallyDeleted); + + /// + /// Removing model snapshot. + /// This event is in the category. + /// + public static readonly EventId SnapshotRemoving = MakeMigrationsId(Id.SnapshotRemoving); + + /// + /// No model snapshot file named was found. + /// This event is in the category. + /// + public static readonly EventId SnapshotFileNotFound = MakeMigrationsId(Id.SnapshotFileNotFound); + + /// + /// Writing model snapshot to file. + /// This event is in the category. + /// + public static readonly EventId SnapshotWriting = MakeMigrationsId(Id.SnapshotWriting); + + /// + /// Reusing namespace of a type. + /// This event is in the category. + /// + public static readonly EventId NamespaceReusing = MakeMigrationsId(Id.NamespaceReusing); + + /// + /// Reusing directory for a file. + /// This event is in the category. + /// + public static readonly EventId DirectoryReusing = MakeMigrationsId(Id.DirectoryReusing); + + /// + /// Reverting model snapshot. + /// This event is in the category. + /// + public static readonly EventId SnapshotReverting = MakeMigrationsId(Id.SnapshotReverting); + + /// + /// Writing migration to file. + /// This event is in the category. + /// + public static readonly EventId MigrationWriting = MakeMigrationsId(Id.MigrationWriting); + + /// + /// Resuing model snapshot name. + /// This event is in the category. + /// + public static readonly EventId SnapshotNameReusing = MakeMigrationsId(Id.SnapshotNameReusing); + + /// + /// An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy. + /// This event is in the category. + /// + public static readonly EventId DestructiveOperation = MakeMigrationsId(Id.DestructiveOperation); + + /// + /// The namespace contains migrations for a different context. + /// This event is in the category. + /// + public static readonly EventId ForeignMigrations = MakeMigrationsId(Id.ForeignMigrations); } } diff --git a/src/EFCore.Design/Internal/DesignLoggerExtensions.cs b/src/EFCore.Design/Internal/DesignLoggerExtensions.cs index ad44b5525f9..7ff18087086 100644 --- a/src/EFCore.Design/Internal/DesignLoggerExtensions.cs +++ b/src/EFCore.Design/Internal/DesignLoggerExtensions.cs @@ -2,30 +2,456 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Design; +using Microsoft.EntityFrameworkCore.Migrations.Internal; +using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Internal { - internal static class DesignLoggerExtensions + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static class DesignLoggerExtensions { - public static void LogWarning( - [NotNull] this ILogger logger, - DesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Warning, (int)eventId, null, null, (_, __) => formatter()); - - public static void LogInformation( - [NotNull] this ILogger logger, - DesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Information, (int)eventId, null, null, (_, __) => formatter()); - - public static void LogDebug( - [NotNull] this ILogger logger, - DesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Debug, (int)eventId, null, null, (_, __) => formatter()); + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignMigrations( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] string migrationNamespace) + { + var eventId = DesignEventId.ForeignMigrations; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + DesignStrings.ForeignMigrations(migrationNamespace)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + MigrationNamespace = migrationNamespace + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SnapshotNameReusing( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] string lastModelSnapshotName) + { + var eventId = DesignEventId.SnapshotNameReusing; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + DesignStrings.ReusingSnapshotName(lastModelSnapshotName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + LastModelSnapshotName = lastModelSnapshotName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void DestructiveOperation( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IEnumerable operations) + { + var eventId = DesignEventId.DestructiveOperation; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + DesignStrings.DestructiveOperation); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Operations = (ICollection)operations.ToList() + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationForceRemove( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Migration migration) + { + var eventId = DesignEventId.MigrationForceRemove; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + DesignStrings.ForceRemoveMigration(migration.GetId())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Migration = migration + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationRemoving( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Migration migration) + { + var eventId = DesignEventId.MigrationRemoving; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Information)) + { + diagnostics.Logger.LogInformation( + eventId, + DesignStrings.RemovingMigration(migration.GetId())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Migration = migration + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationFileNotFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Migration migration, + [NotNull] string fileName) + { + var eventId = DesignEventId.MigrationFileNotFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + DesignStrings.NoMigrationFile(fileName, migration.GetType().ShortDisplayName())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Migration = migration, + FileName = fileName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationMetadataFileNotFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Migration migration, + [NotNull] string fileName) + { + var eventId = DesignEventId.MigrationMetadataFileNotFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + DesignStrings.NoMigrationMetadataFile(fileName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Migration = migration, + FileName = fileName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationManuallyDeleted( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Migration migration) + { + var eventId = DesignEventId.MigrationManuallyDeleted; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + DesignStrings.ManuallyDeleted); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Migration = migration, + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SnapshotRemoving( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] ModelSnapshot modelSnapshot, + [NotNull] string fileName) + { + var eventId = DesignEventId.SnapshotRemoving; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Information)) + { + diagnostics.Logger.LogInformation( + eventId, + DesignStrings.RemovingSnapshot); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ModelSnapshot = modelSnapshot, + FileName = fileName, + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SnapshotFileNotFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] ModelSnapshot modelSnapshot, + [NotNull] string fileName) + { + var eventId = DesignEventId.SnapshotFileNotFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + DesignStrings.NoSnapshotFile(fileName, modelSnapshot.GetType().ShortDisplayName())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ModelSnapshot = modelSnapshot, + FileName = fileName, + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SnapshotReverting( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] ModelSnapshot modelSnapshot, + [NotNull] string fileName) + { + var eventId = DesignEventId.SnapshotReverting; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Information)) + { + diagnostics.Logger.LogInformation( + eventId, + DesignStrings.RevertingSnapshot); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ModelSnapshot = modelSnapshot, + FileName = fileName, + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationWriting( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] ScaffoldedMigration scaffoldedMigration, + [NotNull] string fileName) + { + var eventId = DesignEventId.MigrationWriting; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + DesignStrings.WritingMigration(fileName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ScaffoldedMigration = scaffoldedMigration, + FileName = fileName, + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SnapshotWriting( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] ScaffoldedMigration scaffoldedMigration, + [NotNull] string fileName) + { + var eventId = DesignEventId.SnapshotWriting; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + DesignStrings.WritingSnapshot(fileName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ScaffoldedMigration = scaffoldedMigration, + FileName = fileName, + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void NamespaceReusing( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Type type) + { + var eventId = DesignEventId.NamespaceReusing; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + DesignStrings.ReusingNamespace(type.ShortDisplayName())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Type = type + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void DirectoryReusing( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] string fileName) + { + var eventId = DesignEventId.DirectoryReusing; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + DesignStrings.ReusingDirectory(fileName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + FileName = fileName + }); + } + } } } diff --git a/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs b/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs index b18291cc4af..0b6e0800402 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs @@ -17,7 +17,6 @@ using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; -using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Migrations.Design { @@ -30,7 +29,7 @@ public class MigrationsScaffolder private readonly IMigrationsIdGenerator _idGenerator; private readonly MigrationsCodeGenerator _migrationCodeGenerator; private readonly IHistoryRepository _historyRepository; - private readonly ILogger _logger; + private readonly IDiagnosticsLogger _logger; private readonly string _activeProvider; public MigrationsScaffolder( @@ -41,7 +40,7 @@ public MigrationsScaffolder( [NotNull] IMigrationsIdGenerator idGenerator, [NotNull] MigrationsCodeGenerator migrationCodeGenerator, [NotNull] IHistoryRepository historyRepository, - [NotNull] ILogger logger, + [NotNull] IDiagnosticsLogger logger, [NotNull] IDatabaseProvider databaseProvider) { Check.NotNull(currentContext, nameof(currentContext)); @@ -123,9 +122,7 @@ public virtual ScaffoldedMigration ScaffoldMigration( } else { - _logger.LogWarning( - DesignEventId.ForeignMigrations, - () => DesignStrings.ForeignMigrations(migrationNamespace)); + _logger.ForeignMigrations(migrationNamespace); } } @@ -144,9 +141,7 @@ public virtual ScaffoldedMigration ScaffoldMigration( var lastModelSnapshotName = modelSnapshot.GetType().Name; if (lastModelSnapshotName != modelSnapshotName) { - _logger.LogDebug( - DesignEventId.ReusingSnapshotName, - () => DesignStrings.ReusingSnapshotName(lastModelSnapshotName)); + _logger.SnapshotNameReusing(lastModelSnapshotName); modelSnapshotName = lastModelSnapshotName; } @@ -154,9 +149,7 @@ public virtual ScaffoldedMigration ScaffoldMigration( if (upOperations.Any(o => o.IsDestructiveChange)) { - _logger.LogWarning( - DesignEventId.DestructiveOperation, - () => DesignStrings.DestructiveOperation); + _logger.DestructiveOperation(upOperations.Where(o => o.IsDestructiveChange)); } var migrationCode = _migrationCodeGenerator.GenerateMigration( @@ -224,9 +217,7 @@ public virtual MigrationFiles RemoveMigration([NotNull] string projectDir, [NotN { if (force) { - _logger.LogWarning( - DesignEventId.ForceRemoveMigration, - () => DesignStrings.ForceRemoveMigration(migration.GetId())); + _logger.MigrationForceRemove(migration); } else if (_historyRepository.GetAppliedMigrations().Any( e => e.MigrationId.Equals(migration.GetId(), StringComparison.OrdinalIgnoreCase))) @@ -238,17 +229,13 @@ public virtual MigrationFiles RemoveMigration([NotNull] string projectDir, [NotN var migrationFile = TryGetProjectFile(projectDir, migrationFileName); if (migrationFile != null) { - _logger.LogInformation( - DesignEventId.RemovingMigration, - () => DesignStrings.RemovingMigration(migration.GetId())); + _logger.MigrationRemoving(migration); File.Delete(migrationFile); files.MigrationFile = migrationFile; } else { - _logger.LogWarning( - DesignEventId.NoMigrationFile, - () => DesignStrings.NoMigrationFile(migrationFileName, migration.GetType().ShortDisplayName())); + _logger.MigrationFileNotFound(migration, migrationFileName); } var migrationMetadataFileName = migration.GetId() + ".Designer" + language; @@ -260,9 +247,7 @@ public virtual MigrationFiles RemoveMigration([NotNull] string projectDir, [NotN } else { - _logger.LogDebug( - DesignEventId.NoMigrationMetadataFile, - () => DesignStrings.NoMigrationMetadataFile(migrationMetadataFileName)); + _logger.MigrationMetadataFileNotFound(migration, migrationMetadataFileName); } model = migrations.Count > 1 @@ -271,9 +256,7 @@ public virtual MigrationFiles RemoveMigration([NotNull] string projectDir, [NotN } else { - _logger.LogDebug( - DesignEventId.ManuallyDeleted, - () => DesignStrings.ManuallyDeleted); + _logger.MigrationManuallyDeleted(migration); } } @@ -284,17 +267,13 @@ public virtual MigrationFiles RemoveMigration([NotNull] string projectDir, [NotN { if (modelSnapshotFile != null) { - _logger.LogInformation( - DesignEventId.RemovingSnapshot, - () => DesignStrings.RemovingSnapshot); + _logger.SnapshotRemoving(modelSnapshot, modelSnapshotFile); File.Delete(modelSnapshotFile); files.SnapshotFile = modelSnapshotFile; } else { - _logger.LogWarning( - DesignEventId.NoSnapshotFile, - () => DesignStrings.NoSnapshotFile(modelSnapshotFileName, modelSnapshot.GetType().ShortDisplayName())); + _logger.SnapshotFileNotFound(modelSnapshot, modelSnapshotFile); } } else @@ -314,9 +293,7 @@ public virtual MigrationFiles RemoveMigration([NotNull] string projectDir, [NotN modelSnapshotFileName); } - _logger.LogInformation( - DesignEventId.RevertingSnapshot, - () => DesignStrings.RevertingSnapshot); + _logger.SnapshotReverting(modelSnapshot, modelSnapshotFile); File.WriteAllText(modelSnapshotFile, modelSnapshotCode, Encoding.UTF8); } @@ -339,16 +316,12 @@ public virtual MigrationFiles Save( var modelSnapshotDirectory = outputDir ?? GetDirectory(projectDir, modelSnapshotFileName, migration.SnapshotSubnamespace); var modelSnapshotFile = Path.Combine(modelSnapshotDirectory, modelSnapshotFileName); - _logger.LogDebug( - DesignEventId.WritingMigration, - () => DesignStrings.WritingMigration(migrationFile)); + _logger.MigrationWriting(migration, migrationFile); Directory.CreateDirectory(migrationDirectory); File.WriteAllText(migrationFile, migration.MigrationCode, Encoding.UTF8); File.WriteAllText(migrationMetadataFile, migration.MetadataCode, Encoding.UTF8); - _logger.LogDebug( - DesignEventId.WritingSnapshot, - () => DesignStrings.WritingSnapshot(modelSnapshotFile)); + _logger.SnapshotWriting(migration, modelSnapshotFile); Directory.CreateDirectory(modelSnapshotDirectory); File.WriteAllText(modelSnapshotFile, migration.SnapshotCode, Encoding.UTF8); @@ -367,9 +340,7 @@ protected virtual string GetNamespace([CanBeNull] Type siblingType, [NotNull] st var lastNamespace = siblingType.Namespace; if (lastNamespace != defaultNamespace) { - _logger.LogDebug( - DesignEventId.ReusingNamespace, - () => DesignStrings.ReusingNamespace(siblingType.ShortDisplayName())); + _logger.NamespaceReusing(siblingType); return lastNamespace; } @@ -396,9 +367,7 @@ protected virtual string GetDirectory( var lastDirectory = Path.GetDirectoryName(siblingPath); if (!defaultDirectory.Equals(lastDirectory, StringComparison.OrdinalIgnoreCase)) { - _logger.LogDebug( - DesignEventId.ReusingDirectory, - () => DesignStrings.ReusingDirectory(siblingFileName)); + _logger.DirectoryReusing(siblingFileName); return lastDirectory; } diff --git a/src/EFCore.Design/Scaffolding/Internal/ScaffoldingServiceCollectionExtensions.cs b/src/EFCore.Design/Scaffolding/Internal/ScaffoldingServiceCollectionExtensions.cs index 1311df63a9e..be3a992c992 100644 --- a/src/EFCore.Design/Scaffolding/Internal/ScaffoldingServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Scaffolding/Internal/ScaffoldingServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; @@ -32,6 +33,8 @@ public static IServiceCollection AddScaffolding([NotNull] this IServiceCollectio .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton(new DiagnosticListener(LoggerCategory.Root)) + .AddSingleton(typeof(IDiagnosticsLogger<>), typeof(DiagnosticsLogger<>)) .AddSingleton(typeof(IInterceptingLogger<>), typeof(InterceptingLogger<>)); } } diff --git a/src/EFCore.InMemory/Extensions/Internal/InMemoryLoggerExtensions.cs b/src/EFCore.InMemory/Extensions/Internal/InMemoryLoggerExtensions.cs index dcaf003c47e..e456d3cf600 100644 --- a/src/EFCore.InMemory/Extensions/Internal/InMemoryLoggerExtensions.cs +++ b/src/EFCore.InMemory/Extensions/Internal/InMemoryLoggerExtensions.cs @@ -1,29 +1,71 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; +using System.Collections.Generic; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Update; using Microsoft.Extensions.Logging; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Internal { - internal static class InMemoryLoggerExtensions + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static class InMemoryLoggerExtensions { - public static void LogInformation( - this ILogger logger, InMemoryEventId eventId, TState state, Func formatter) + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TransactionIgnoredWarning( + [NotNull] this IDiagnosticsLogger diagnostics) { - if (logger.IsEnabled(LogLevel.Information)) + var eventId = InMemoryEventId.TransactionIgnoredWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) { - logger.Log(LogLevel.Information, (int)eventId, state, null, (s, _) => formatter(s)); + diagnostics.Logger.LogWarning( + eventId, + InMemoryStrings.TransactionsNotSupported); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, null); } } - public static void LogWarning(this ILogger logger, InMemoryEventId eventId, Func formatter) + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ChangesSaved( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IEnumerable entries, + int rowsAffected) { - // Always call Log for Warnings because Warnings as Errors should work even - // if LogLevel.Warning is not enabled. - logger.Log(LogLevel.Warning, (int)eventId, eventId, null, (_, __) => formatter()); + var eventId = InMemoryEventId.ChangesSaved; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Information)) + { + diagnostics.Logger.LogInformation( + eventId, + InMemoryStrings.LogSavedChanges(rowsAffected)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Entries = entries, + RowsAffected = rowsAffected + }); + } } } } diff --git a/src/EFCore.InMemory/Infrastructure/InMemoryEventId.cs b/src/EFCore.InMemory/Infrastructure/InMemoryEventId.cs index 632ec57181f..438fe6f1f85 100644 --- a/src/EFCore.InMemory/Infrastructure/InMemoryEventId.cs +++ b/src/EFCore.InMemory/Infrastructure/InMemoryEventId.cs @@ -1,23 +1,51 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Infrastructure { /// - /// Values that are used as the eventId when logging messages from the in-memory database provider via . + /// + /// Event IDs for in-memory events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// /// - public enum InMemoryEventId + public static class InMemoryEventId { + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Transaction events + TransactionIgnoredWarning = CoreEventId.ProviderBaseId, + + // Update events + ChangesSaved = CoreEventId.ProviderBaseId + 100 + } + + private static readonly string _transactionPrefix = LoggerCategory.Database.Transaction.Name + "."; + private static EventId MakeTransactionId(Id id) => new EventId((int)id, _transactionPrefix + id); + /// /// Changes were saved to the database. + /// This event is in the category. /// - SavedChanges = 1, + public static readonly EventId TransactionIgnoredWarning = MakeTransactionId(Id.TransactionIgnoredWarning); + + private static readonly string _updatePrefix = LoggerCategory.Update.Name + "."; + private static EventId MakeUpdateId(Id id) => new EventId((int)id, _updatePrefix + id); /// /// A transaction operation was requested, but ignored because in-memory does not support transactions. + /// This event is in the category. /// - TransactionIgnoredWarning + public static readonly EventId ChangesSaved = MakeUpdateId(Id.ChangesSaved); } } diff --git a/src/EFCore.InMemory/Storage/Internal/IInMemoryStore.cs b/src/EFCore.InMemory/Storage/Internal/IInMemoryStore.cs index 96748f9007b..ca208d52d7e 100644 --- a/src/EFCore.InMemory/Storage/Internal/IInMemoryStore.cs +++ b/src/EFCore.InMemory/Storage/Internal/IInMemoryStore.cs @@ -37,6 +37,6 @@ public interface IInMemoryStore /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - int ExecuteTransaction([NotNull] IEnumerable entries, [NotNull] IInterceptingLogger updateLogger); + int ExecuteTransaction([NotNull] IEnumerable entries, [NotNull] IDiagnosticsLogger updateLogger); } } diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs index 9be4d5f11df..966ce454274 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs @@ -23,7 +23,7 @@ namespace Microsoft.EntityFrameworkCore.Storage.Internal public class InMemoryDatabase : Database, IInMemoryDatabase { private readonly IInMemoryStore _store; - private readonly IInterceptingLogger _updateLogger; + private readonly IDiagnosticsLogger _updateLogger; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -33,7 +33,7 @@ public InMemoryDatabase( [NotNull] DatabaseDependencies dependencies, [NotNull] IInMemoryStoreSource storeSource, [NotNull] IDbContextOptions options, - [NotNull] IInterceptingLogger updateLogger) + [NotNull] IDiagnosticsLogger updateLogger) : base(dependencies) { Check.NotNull(storeSource, nameof(storeSource)); diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs index 0da3a0586fa..b28c917d70f 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs @@ -104,8 +104,8 @@ public virtual IReadOnlyList GetTables(IEntityType entity /// directly from your code. This API may change or be removed in future releases. /// public virtual int ExecuteTransaction( - IEnumerable entries, - IInterceptingLogger updateLogger) + IEnumerable entries, + IDiagnosticsLogger updateLogger) { var rowsAffected = 0; @@ -140,10 +140,7 @@ public virtual int ExecuteTransaction( } } - updateLogger.LogInformation( - InMemoryEventId.SavedChanges, - rowsAffected, - InMemoryStrings.LogSavedChanges); + updateLogger.ChangesSaved(entries, rowsAffected); return rowsAffected; } diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs index 813af3073bc..4b2da39fd28 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryTransactionManager.cs @@ -14,10 +14,10 @@ public class InMemoryTransactionManager : IDbContextTransactionManager { private static readonly InMemoryTransaction _stubTransaction = new InMemoryTransaction(); - private readonly IInterceptingLogger _logger; + private readonly IDiagnosticsLogger _logger; public InMemoryTransactionManager( - [NotNull] IInterceptingLogger logger) + [NotNull] IDiagnosticsLogger logger) { Check.NotNull(logger, nameof(logger)); @@ -26,7 +26,7 @@ public InMemoryTransactionManager( public virtual IDbContextTransaction BeginTransaction() { - LogWarning(); + _logger.TransactionIgnoredWarning(); return _stubTransaction; } @@ -34,24 +34,17 @@ public virtual IDbContextTransaction BeginTransaction() public virtual Task BeginTransactionAsync( CancellationToken cancellationToken = default(CancellationToken)) { - LogWarning(); + _logger.TransactionIgnoredWarning(); return Task.FromResult(_stubTransaction); } - public virtual void CommitTransaction() => LogWarning(); + public virtual void CommitTransaction() => _logger.TransactionIgnoredWarning(); - public virtual void RollbackTransaction() => LogWarning(); + public virtual void RollbackTransaction() => _logger.TransactionIgnoredWarning(); public virtual IDbContextTransaction CurrentTransaction => null; - protected virtual void LogWarning() - { - _logger.LogWarning( - InMemoryEventId.TransactionIgnoredWarning, - () => InMemoryStrings.TransactionsNotSupported); - } - public virtual void Reset() { } diff --git a/src/EFCore.InMemory/WarningConfigurationBuilderExtensions.cs b/src/EFCore.InMemory/WarningConfigurationBuilderExtensions.cs deleted file mode 100644 index 352db930890..00000000000 --- a/src/EFCore.InMemory/WarningConfigurationBuilderExtensions.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore -{ - /// - /// In-memory specific extension methods for . - /// - public static class WarningConfigurationBuilderExtensions - { - /// - /// Causes an exception to be thrown when the specified in-memory warnings are generated. - /// - /// The builder being used to configure warnings. - /// - /// The (s) for the warnings. - /// - /// The same builder instance so that multiple calls can be chained. - public static WarningsConfigurationBuilder Throw( - [NotNull] this WarningsConfigurationBuilder warningsConfigurationBuilder, - [NotNull] params InMemoryEventId[] inMemoryEventIds) - { - Check.NotNull(warningsConfigurationBuilder, nameof(warningsConfigurationBuilder)); - Check.NotNull(inMemoryEventIds, nameof(inMemoryEventIds)); - - return warningsConfigurationBuilder.WithOption( - e => e.WithExplicit(inMemoryEventIds.Cast(), WarningBehavior.Throw)); - } - - /// - /// Causes a warning to be logged when the specified in-memory warnings are generated. - /// - /// The builder being used to configure warnings. - /// - /// The (s) for the warnings. - /// - /// The same builder instance so that multiple calls can be chained. - public static WarningsConfigurationBuilder Log( - [NotNull] this WarningsConfigurationBuilder warningsConfigurationBuilder, - [NotNull] params InMemoryEventId[] inMemoryEventIds) - { - Check.NotNull(warningsConfigurationBuilder, nameof(warningsConfigurationBuilder)); - Check.NotNull(inMemoryEventIds, nameof(inMemoryEventIds)); - - return warningsConfigurationBuilder.WithOption( - e => e.WithExplicit(inMemoryEventIds.Cast(), WarningBehavior.Log)); - } - - /// - /// Causes nothing to happen when the specified in-memory warnings are generated. - /// - /// The builder being used to configure warnings. - /// - /// The (s) for the warnings. - /// - /// The same builder instance so that multiple calls can be chained. - public static WarningsConfigurationBuilder Ignore( - [NotNull] this WarningsConfigurationBuilder warningsConfigurationBuilder, - [NotNull] params InMemoryEventId[] inMemoryEventIds) - { - Check.NotNull(warningsConfigurationBuilder, nameof(warningsConfigurationBuilder)); - Check.NotNull(inMemoryEventIds, nameof(inMemoryEventIds)); - - return warningsConfigurationBuilder.WithOption( - e => e.WithExplicit(inMemoryEventIds.Cast(), WarningBehavior.Ignore)); - } - - private static WarningsConfigurationBuilder WithOption( - this WarningsConfigurationBuilder warningsConfigurationBuilder, - Func withFunc) - { - var optionsBuilder = warningsConfigurationBuilder.OptionsBuilder; - - var coreOptionsExtension = optionsBuilder.Options.FindExtension() ?? new CoreOptionsExtension(); - - ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension( - coreOptionsExtension.WithWarningsConfiguration(withFunc(coreOptionsExtension.WarningsConfiguration))); - - return warningsConfigurationBuilder; - } - } -} diff --git a/src/EFCore.Relational.Design.Specification.Tests/ReverseEngineering/E2ETestBase.cs b/src/EFCore.Relational.Design.Specification.Tests/ReverseEngineering/E2ETestBase.cs index 9e87cf0c836..ddcea9160d8 100644 --- a/src/EFCore.Relational.Design.Specification.Tests/ReverseEngineering/E2ETestBase.cs +++ b/src/EFCore.Relational.Design.Specification.Tests/ReverseEngineering/E2ETestBase.cs @@ -32,9 +32,7 @@ protected E2ETestBase(ITestOutputHelper output) var serviceBuilder = new ServiceCollection() .AddScaffolding() - .AddLogging() - .AddSingleton() - .AddSingleton(typeof(IInterceptingLogger<>), typeof(InterceptingLogger<>)); + .AddLogging(); ConfigureDesignTimeServices(serviceBuilder); diff --git a/src/EFCore.Relational.Design/Infrastructure/RelationalDesignEventId.cs b/src/EFCore.Relational.Design/Infrastructure/RelationalDesignEventId.cs index 8830af12e63..6c18a602357 100644 --- a/src/EFCore.Relational.Design/Infrastructure/RelationalDesignEventId.cs +++ b/src/EFCore.Relational.Design/Infrastructure/RelationalDesignEventId.cs @@ -1,32 +1,244 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; +using Microsoft.Extensions.Logging; + namespace Microsoft.EntityFrameworkCore.Infrastructure { - public enum RelationalDesignEventId + /// + /// + /// Event IDs for relational design events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// + /// + public static class RelationalDesignEventId { - MissingSchemaWarning = 1, - MissingTableWarning, - SequenceMustBeNamedWarning, - SequenceTypeNotSupportedWarning, - UnableToGenerateEntityTypeWarning, - ColumnTypeNotMappedWarning, - MissingPrimaryKeyWarning, - PrimaryKeyColumnsNotMappedWarning, - IndexColumnsNotMappedWarning, - ForeignKeyReferencesMissingTableWarning, - ForeignKeyColumnsNotMappedWarning, - ForeignKeyReferencesMissingPrincipalKeyWarning, - FoundTable, - TableSkipped, - FoundColumn, - ColumnSkipped, - FoundIndex, - FoundIndexColumn, - IndexColumnSkipped, - FoundForeignKeyColumn, - FoundSequence, - ForeignKeyReferencesMissingTable, - ForeignKeyPrincipalEndContainsNullableColumns + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Scaffolding warning events + MissingSchemaWarning = CoreEventId.RelationalDesignBaseId, + MissingTableWarning, + SequenceNotNamedWarning, + SequenceTypeNotSupportedWarning, + UnableToGenerateEntityTypeWarning, + ColumnTypeNotMappedWarning, + MissingPrimaryKeyWarning, + PrimaryKeyColumnsNotMappedWarning, + IndexColumnsNotMappedWarning, + ForeignKeyReferencesMissingTableWarning, + ForeignKeyReferencesMissingPrincipalTableWarning, + ForeignKeyReferencesNotMappedTableWarning, + ForeignKeyColumnsNotMappedWarning, + ForeignKeyReferencesMissingPrincipalKeyWarning, + ForeignKeyPrincipalEndContainsNullableColumnsWarning, + ForeignKeyNotNamedWarning, + ForeignKeyColumnMissingWarning, + ForeignKeyColumnNotNamedWarning, + ForeignKeyPrincipalColumnMissingWarning, + ColumnNotNamedWarning, + IndexNotNamedWarning, + IndexTableMissingWarning, + IndexColumnNotNamedWarning, + + // Scaffolding events + TableFound = CoreEventId.RelationalDesignBaseId + 1000, + TableSkipped, + ColumnSkipped, + IndexFound, + IndexColumnFound, + IndexColumnSkipped, + SequenceFound + } + + private static readonly string _scaffoldingPrefix = LoggerCategory.Scaffolding.Name + "."; + private static EventId MakeScaffoldingId(Id id) => new EventId((int)id, _scaffoldingPrefix + id); + + /// + /// The database is missing a schema. + /// This event is in the category. + /// + public static readonly EventId MissingSchemaWarning = MakeScaffoldingId(Id.MissingSchemaWarning); + + /// + /// The database is missing a table. + /// This event is in the category. + /// + public static readonly EventId MissingTableWarning = MakeScaffoldingId(Id.MissingTableWarning); + + /// + /// The database has an unnamed sequence. + /// This event is in the category. + /// + public static readonly EventId SequenceNotNamedWarning = MakeScaffoldingId(Id.SequenceNotNamedWarning); + + /// + /// The database has a sequence of a type that is not supported. + /// This event is in the category. + /// + public static readonly EventId SequenceTypeNotSupportedWarning = MakeScaffoldingId(Id.SequenceTypeNotSupportedWarning); + + /// + /// An entity type could not be generated. + /// This event is in the category. + /// + public static readonly EventId UnableToGenerateEntityTypeWarning = MakeScaffoldingId(Id.UnableToGenerateEntityTypeWarning); + + /// + /// A column type could not be mapped. + /// This event is in the category. + /// + public static readonly EventId ColumnTypeNotMappedWarning = MakeScaffoldingId(Id.ColumnTypeNotMappedWarning); + + /// + /// A table is missing a primary key. + /// This event is in the category. + /// + public static readonly EventId MissingPrimaryKeyWarning = MakeScaffoldingId(Id.MissingPrimaryKeyWarning); + + /// + /// Columns in a primary key were not mapped. + /// This event is in the category. + /// + public static readonly EventId PrimaryKeyColumnsNotMappedWarning = MakeScaffoldingId(Id.PrimaryKeyColumnsNotMappedWarning); + + /// + /// Columns in an index were not mapped. + /// This event is in the category. + /// + public static readonly EventId IndexColumnsNotMappedWarning = MakeScaffoldingId(Id.IndexColumnsNotMappedWarning); + + /// + /// A foreign key references a missing table. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyReferencesMissingTableWarning = MakeScaffoldingId(Id.ForeignKeyReferencesMissingTableWarning); + + /// + /// A foreign key references a missing table at the principal end. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyReferencesMissingPrincipalTableWarning = MakeScaffoldingId(Id.ForeignKeyReferencesMissingPrincipalTableWarning); + + /// + /// A foreign key references a table that was not mapped. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyReferencesNotMappedTableWarning = MakeScaffoldingId(Id.ForeignKeyReferencesNotMappedTableWarning); + + /// + /// Columns in a foreign key were not mapped. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyColumnsNotMappedWarning = MakeScaffoldingId(Id.ForeignKeyColumnsNotMappedWarning); + + /// + /// A foreign key references missing prinicpal key columns. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyReferencesMissingPrincipalKeyWarning = MakeScaffoldingId(Id.ForeignKeyReferencesMissingPrincipalKeyWarning); + + /// + /// A principal key contains nullable columns. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyPrincipalEndContainsNullableColumnsWarning = MakeScaffoldingId(Id.ForeignKeyPrincipalEndContainsNullableColumnsWarning); + + /// + /// A foreign key is not named. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyNotNamedWarning = MakeScaffoldingId(Id.ForeignKeyNotNamedWarning); + + /// + /// A foreign key column was not found. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyColumnMissingWarning = MakeScaffoldingId(Id.ForeignKeyColumnMissingWarning); + + /// + /// A foreign key column was not found. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyPrincipalColumnMissingWarning = MakeScaffoldingId(Id.ForeignKeyPrincipalColumnMissingWarning); + + /// + /// A foreign key column was not named. + /// This event is in the category. + /// + public static readonly EventId ForeignKeyColumnNotNamedWarning = MakeScaffoldingId(Id.ForeignKeyColumnNotNamedWarning); + + /// + /// A column is not named. + /// This event is in the category. + /// + public static readonly EventId ColumnNotNamedWarning = MakeScaffoldingId(Id.ColumnNotNamedWarning); + + /// + /// An index is not named. + /// This event is in the category. + /// + public static readonly EventId IndexNotNamedWarning = MakeScaffoldingId(Id.IndexNotNamedWarning); + + /// + /// The table references by an index was not found. + /// This event is in the category. + /// + public static readonly EventId IndexTableMissingWarning = MakeScaffoldingId(Id.IndexTableMissingWarning); + + /// + /// An index column was not named. + /// This event is in the category. + /// + public static readonly EventId IndexColumnNotNamedWarning = MakeScaffoldingId(Id.IndexColumnNotNamedWarning); + + /// + /// A table was found. + /// This event is in the category. + /// + public static readonly EventId TableFound = MakeScaffoldingId(Id.TableFound); + + /// + /// A table was skipped. + /// This event is in the category. + /// + public static readonly EventId TableSkipped = MakeScaffoldingId(Id.TableSkipped); + + /// + /// A column was skipped. + /// This event is in the category. + /// + public static readonly EventId ColumnSkipped = MakeScaffoldingId(Id.ColumnSkipped); + + /// + /// An index was found. + /// This event is in the category. + /// + public static readonly EventId IndexFound = MakeScaffoldingId(Id.IndexFound); + + /// + /// An index was skipped. + /// This event is in the category. + /// + public static readonly EventId IndexColumnFound = MakeScaffoldingId(Id.IndexColumnFound); + + /// + /// A column of an index was skipped. + /// This event is in the category. + /// + public static readonly EventId IndexColumnSkipped = MakeScaffoldingId(Id.IndexColumnSkipped); + + /// + /// A sequence was found. + /// This event is in the category. + /// + public static readonly EventId SequenceFound = MakeScaffoldingId(Id.SequenceFound); } } diff --git a/src/EFCore.Relational.Design/Internal/RelationalDesignLoggerExtensions.cs b/src/EFCore.Relational.Design/Internal/RelationalDesignLoggerExtensions.cs index a065beb132c..07e342fc2bd 100644 --- a/src/EFCore.Relational.Design/Internal/RelationalDesignLoggerExtensions.cs +++ b/src/EFCore.Relational.Design/Internal/RelationalDesignLoggerExtensions.cs @@ -1,29 +1,937 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Internal { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// public static class RelationalDesignLoggerExtensions { - public static void LogWarning( - [NotNull] this ILogger logger, - RelationalDesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Warning, (int)eventId, null, null, (_, __) => formatter()); + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MissingSchemaWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string schemaName) + { + var eventId = RelationalDesignEventId.MissingSchemaWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.MissingSchema(schemaName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + SchemaName = schemaName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MissingTableWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.MissingTableWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.MissingTable(tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SequenceNotNamedWarning( + [NotNull] this IDiagnosticsLogger diagnostics) + { + var eventId = RelationalDesignEventId.SequenceNotNamedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.SequencesRequireName); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, null); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SequenceTypeNotSupportedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string sequenceName, + [CanBeNull] string dataTypeName) + { + var eventId = RelationalDesignEventId.SequenceTypeNotSupportedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.BadSequenceType(sequenceName, dataTypeName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + SequenceName = sequenceName, + DataTypeName = dataTypeName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void UnableToGenerateEntityTypeWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.UnableToGenerateEntityTypeWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.UnableToGenerateEntityType(tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ColumnTypeNotMappedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string columnName, + [CanBeNull] string dataTypeName) + { + var eventId = RelationalDesignEventId.ColumnTypeNotMappedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.CannotFindTypeMappingForColumn(columnName, dataTypeName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ColumnName = columnName, + DataTypeName = dataTypeName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MissingPrimaryKeyWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.MissingPrimaryKeyWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.MissingPrimaryKey(tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void PrimaryKeyColumnsNotMappedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName, + [NotNull] IList unmappedColumnNames) + { + var eventId = RelationalDesignEventId.PrimaryKeyColumnsNotMappedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.PrimaryKeyErrorPropertyNotFound( + tableName, + string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, unmappedColumnNames))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName, + UnmappedColumnNames = unmappedColumnNames + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void IndexColumnsNotMappedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string indexName, + [NotNull] IList unmappedColumnNames) + { + var eventId = RelationalDesignEventId.IndexColumnsNotMappedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.UnableToScaffoldIndexMissingProperty( + indexName, + string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, unmappedColumnNames))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + IndexName = indexName, + UnmappedColumnNames = unmappedColumnNames + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyReferencesMissingTableWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string foreignKeyName) + { + var eventId = RelationalDesignEventId.ForeignKeyReferencesMissingTableWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ForeignKeyScaffoldErrorPrincipalTableNotFound(foreignKeyName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ForeignKeyName = foreignKeyName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyReferencesNotMappedTableWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string foreignKeyName, + [NotNull] string principalTableName) + { + var eventId = RelationalDesignEventId.ForeignKeyReferencesNotMappedTableWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ForeignKeyScaffoldErrorPrincipalTableScaffoldingError(foreignKeyName, principalTableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ForeignKeyName = foreignKeyName, + PrincipalTableName = principalTableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyColumnsNotMappedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string foreignKeyName, + [NotNull] IList unmappedColumnNames) + { + var eventId = RelationalDesignEventId.ForeignKeyColumnsNotMappedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ForeignKeyScaffoldErrorPropertyNotFound( + foreignKeyName, + string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, unmappedColumnNames))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ForeignKeyName = foreignKeyName, + UnmappedColumnNames = unmappedColumnNames + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyReferencesMissingPrincipalKeyWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string foreignKeyName, + [CanBeNull] string principalEntityTypeName, + [NotNull] IList principalColumnNames) + { + var eventId = RelationalDesignEventId.ForeignKeyReferencesMissingPrincipalKeyWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ForeignKeyScaffoldErrorPrincipalKeyNotFound( + foreignKeyName, + string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, principalColumnNames), + principalEntityTypeName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ForeignKeyName = foreignKeyName, + PrincipalEntityTypeName = principalEntityTypeName, + PrincipalColumnNames = principalColumnNames + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyPrincipalEndContainsNullableColumnsWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string foreignKeyName, + [CanBeNull] string indexName, + [CanBeNull] IList nullablePropertyNames) + { + var eventId = RelationalDesignEventId.ForeignKeyPrincipalEndContainsNullableColumnsWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ForeignKeyPrincipalEndContainsNullableColumns( + foreignKeyName, + indexName, + nullablePropertyNames.Aggregate((a, b) => a + "," + b))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ForeignKeyName = foreignKeyName, + IndexName = indexName, + NullablePropertyNames = nullablePropertyNames + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SequenceFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string sequenceName, + [CanBeNull] string sequenceTypeName, + bool? cyclic, + int? increment, + long? start, + long? min, + long? max) + { + var eventId = RelationalDesignEventId.SequenceFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalDesignStrings.FoundSequence( + sequenceName, sequenceTypeName, cyclic, + increment, start, min, max)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + SequenceName = sequenceName, + SequenceTypeName = sequenceTypeName, + Cyclic = cyclic, + Increment = increment, + Start = start, + Min = min, + Max = max + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TableFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.TableFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalDesignStrings.FoundTable(tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TableSkipped( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.TableSkipped; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalDesignStrings.TableNotInSelectionSet(tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ColumnSkipped( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName, + [CanBeNull] string columnName) + { + var eventId = RelationalDesignEventId.ColumnSkipped; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalDesignStrings.ColumnNotInSelectionSet(columnName, tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName, + ColumnName = columnName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void IndexColumnFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName, + [CanBeNull] string indexName, + bool? unique, + [CanBeNull] string columnName, + int? ordinal) + { + var eventId = RelationalDesignEventId.IndexColumnFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalDesignStrings.FoundIndexColumn(indexName, tableName, columnName, ordinal)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName, + IndexName = indexName, + Unique = unique, + ColumnName = columnName, + Ordinal = ordinal + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ColumnNotNamedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.ColumnNotNamedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ColumnNameEmptyOnTable(tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void IndexColumnSkipped( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName, + [CanBeNull] string indexName, + [CanBeNull] string columnName) + { + var eventId = RelationalDesignEventId.IndexColumnSkipped; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.IndexColumnNotInSelectionSet(columnName, indexName, tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName, + IndexName = indexName, + ColumnName = columnName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void IndexNotNamedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.IndexNotNamedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.IndexNameEmpty(tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName + }); + } + } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static void LogDebug( - [NotNull] this ILogger logger, - RelationalDesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Debug, (int)eventId, null, null, (_, __) => formatter()); + public static void IndexTableMissingWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string indexName, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.IndexTableMissingWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.UnableToFindTableForIndex(indexName, tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + IndexName = indexName, + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void IndexColumnNotNamedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string indexName, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.IndexColumnNotNamedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ColumnNameEmptyOnIndex(indexName, tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + IndexName = indexName, + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyNotNamedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.ForeignKeyNotNamedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ForeignKeyNameEmpty(tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyColumnMissingWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string columnName, + [CanBeNull] string foreignKeyName, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.ForeignKeyColumnMissingWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ForeignKeyColumnNotInSelectionSet(columnName, foreignKeyName, tableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ColumnName = columnName, + ForeignKeyName = foreignKeyName, + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyReferencesMissingPrincipalTableWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string foreignKeyName, + [CanBeNull] string tableName, + [CanBeNull] string principalTableName) + { + var eventId = RelationalDesignEventId.ForeignKeyReferencesMissingPrincipalTableWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.PrincipalTableNotInSelectionSet(foreignKeyName, tableName, principalTableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ForeignKeyName = foreignKeyName, + TableName = tableName, + PrincipalTableName = principalTableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyColumnNotNamedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string foreignKeyName, + [CanBeNull] string tableName) + { + var eventId = RelationalDesignEventId.ForeignKeyColumnNotNamedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.ColumnNameEmptyOnForeignKey(tableName, foreignKeyName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ForeignKeyName = foreignKeyName, + TableName = tableName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void IndexFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string indexName, + [CanBeNull] string tableName, + bool? unique) + { + var eventId = RelationalDesignEventId.IndexFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalDesignStrings.FoundIndex(indexName, tableName, unique)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + IndexName = indexName, + TableName = tableName, + Unique = unique + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ForeignKeyPrincipalColumnMissingWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string foreignKeyName, + [CanBeNull] string tableName, + [CanBeNull] string principalColumnName, + [CanBeNull] string principalTableName) + { + var eventId = RelationalDesignEventId.ForeignKeyPrincipalColumnMissingWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalDesignStrings.PrincipalColumnNotFound(foreignKeyName, tableName, principalColumnName, principalTableName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ForeignKeyName = foreignKeyName, + TableName = tableName, + PrincipalColumnName = principalColumnName, + PrincipalTableName = principalTableName + }); + } + } } } diff --git a/src/EFCore.Relational.Design/Metadata/SequenceModel.cs b/src/EFCore.Relational.Design/Metadata/SequenceModel.cs index 27904ead0a4..a14384a3448 100644 --- a/src/EFCore.Relational.Design/Metadata/SequenceModel.cs +++ b/src/EFCore.Relational.Design/Metadata/SequenceModel.cs @@ -18,5 +18,8 @@ public class SequenceModel : Annotatable public virtual long? Min { get; [param: CanBeNull] set; } public virtual long? Max { get; [param: CanBeNull] set; } public virtual bool? IsCyclic { get; [param: CanBeNull] set; } + + public virtual string DisplayName + => (!string.IsNullOrEmpty(SchemaName) ? SchemaName + "." : "") + Name; } } diff --git a/src/EFCore.Relational.Design/Properties/RelationalDesignStrings.Designer.cs b/src/EFCore.Relational.Design/Properties/RelationalDesignStrings.Designer.cs index 16befc14390..2fb0c6ad174 100644 --- a/src/EFCore.Relational.Design/Properties/RelationalDesignStrings.Designer.cs +++ b/src/EFCore.Relational.Design/Properties/RelationalDesignStrings.Designer.cs @@ -15,6 +15,52 @@ public static class RelationalDesignStrings private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.RelationalDesignStrings", typeof(RelationalDesignStrings).GetTypeInfo().Assembly); + /// + /// Metadata model returned should not be null. Provider: {providerTypeName}. + /// + public static string ProviderReturnedNullModel([CanBeNull] object providerTypeName) + => string.Format( + GetString("ProviderReturnedNullModel", nameof(providerTypeName)), + providerTypeName); + + /// + /// No files generated in directory {outputDirectoryName}. The following file(s) already exist and must be made writeable to continue: {readOnlyFiles}. + /// + public static string ReadOnlyFiles([CanBeNull] object outputDirectoryName, [CanBeNull] object readOnlyFiles) + => string.Format( + GetString("ReadOnlyFiles", nameof(outputDirectoryName), nameof(readOnlyFiles)), + outputDirectoryName, readOnlyFiles); + + /// + /// Cannot scaffold the connection string. The "UseProviderMethodName" is missing from the scaffolding model. + /// + public static string MissingUseProviderMethodNameAnnotation + => GetString("MissingUseProviderMethodNameAnnotation"); + + /// + /// The following file(s) already exist in directory {outputDirectoryName}: {existingFiles}. Use the Force flag to overwrite these files. + /// + public static string ExistingFiles([CanBeNull] object outputDirectoryName, [CanBeNull] object existingFiles) + => string.Format( + GetString("ExistingFiles", nameof(outputDirectoryName), nameof(existingFiles)), + outputDirectoryName, existingFiles); + + /// + /// Found a column on index {indexName} on table {tableName} with an empty or null name. Not including column in index. + /// + public static string ColumnNameEmptyOnIndex([CanBeNull] object indexName, [CanBeNull] object tableName) + => string.Format( + GetString("ColumnNameEmptyOnIndex", nameof(indexName), nameof(tableName)), + indexName, tableName); + + /// + /// For foreign key with identity {id} on table {tableName}, unable to find the column called {principalColumnName} on the foreign key's principal table, {principalTableName}. Skipping foreign key. + /// + public static string PrincipalColumnNotFound([CanBeNull] object id, [CanBeNull] object tableName, [CanBeNull] object principalColumnName, [CanBeNull] object principalTableName) + => string.Format( + GetString("PrincipalColumnNotFound", nameof(id), nameof(tableName), nameof(principalColumnName), nameof(principalTableName)), + id, tableName, principalColumnName, principalTableName); + /// /// Could not find type mapping for column '{columnName}' with data type '{dateType}'. Skipping column. /// @@ -72,50 +118,132 @@ public static string MissingPrimaryKey([CanBeNull] object tableName) tableName); /// - /// Metadata model returned should not be null. Provider: {providerTypeName}. + /// Found table with name: {name}. /// - public static string ProviderReturnedNullModel([CanBeNull] object providerTypeName) + public static string FoundTable([CanBeNull] object name) => string.Format( - GetString("ProviderReturnedNullModel", nameof(providerTypeName)), - providerTypeName); + GetString("FoundTable", nameof(name)), + name); /// - /// No files generated in directory {outputDirectoryName}. The following file(s) already exist and must be made writeable to continue: {readOnlyFiles}. + /// Table {tableName} is not included in the selection set. Skipping. /// - public static string ReadOnlyFiles([CanBeNull] object outputDirectoryName, [CanBeNull] object readOnlyFiles) + public static string TableNotInSelectionSet([CanBeNull] object tableName) => string.Format( - GetString("ReadOnlyFiles", nameof(outputDirectoryName), nameof(readOnlyFiles)), - outputDirectoryName, readOnlyFiles); + GetString("TableNotInSelectionSet", nameof(tableName)), + tableName); /// - /// Unable to generate entity type for table '{tableName}'. + /// Column {columnName} belongs to table {tableName} which is not included in the selection set. Skipping. /// - public static string UnableToGenerateEntityType([CanBeNull] object tableName) + public static string ColumnNotInSelectionSet([CanBeNull] object columnName, [CanBeNull] object tableName) => string.Format( - GetString("UnableToGenerateEntityType", nameof(tableName)), + GetString("ColumnNotInSelectionSet", nameof(columnName), nameof(tableName)), + columnName, tableName); + + /// + /// Found index with name: {indexName}, table: {tableName}, is unique: {isUnique}. + /// + public static string FoundIndex([CanBeNull] object indexName, [CanBeNull] object tableName, [CanBeNull] object isUnique) + => string.Format( + GetString("FoundIndex", nameof(indexName), nameof(tableName), nameof(isUnique)), + indexName, tableName, isUnique); + + /// + /// Found index column on index {indexName} on table {tableName}, column name: {columnName}, ordinal: {ordinal}. + /// + public static string FoundIndexColumn([CanBeNull] object indexName, [CanBeNull] object tableName, [CanBeNull] object columnName, [CanBeNull] object ordinal) + => string.Format( + GetString("FoundIndexColumn", nameof(indexName), nameof(tableName), nameof(columnName), nameof(ordinal)), + indexName, tableName, columnName, ordinal); + + /// + /// Index column {columnName} belongs to index {indexName} on table {tableName} which is not included in the selection set. Skipping. + /// + public static string IndexColumnNotInSelectionSet([CanBeNull] object columnName, [CanBeNull] object indexName, [CanBeNull] object tableName) + => string.Format( + GetString("IndexColumnNotInSelectionSet", nameof(columnName), nameof(indexName), nameof(tableName)), + columnName, indexName, tableName); + + /// + /// Found sequence name: {name}, data type: {dataType}, cyclic: {isCyclic}, increment: {increment}, start: {start}, minimum: {min}, maximum: {max}. + /// + public static string FoundSequence([CanBeNull] object name, [CanBeNull] object dataType, [CanBeNull] object isCyclic, [CanBeNull] object increment, [CanBeNull] object start, [CanBeNull] object min, [CanBeNull] object max) + => string.Format( + GetString("FoundSequence", nameof(name), nameof(dataType), nameof(isCyclic), nameof(increment), nameof(start), nameof(min), nameof(max)), + name, dataType, isCyclic, increment, start, min, max); + + /// + /// Found a column on foreign key {tableName}.{fkName} with an empty or null name. Not including column in foreign key + /// + public static string ColumnNameEmptyOnForeignKey([CanBeNull] object tableName, [CanBeNull] object fkName) + => string.Format( + GetString("ColumnNameEmptyOnForeignKey", nameof(tableName), nameof(fkName)), + tableName, fkName); + + /// + /// Found a column on table {tableName} with an empty or null name. Skipping column. + /// + public static string ColumnNameEmptyOnTable([CanBeNull] object tableName) + => string.Format( + GetString("ColumnNameEmptyOnTable", nameof(tableName)), tableName); /// - /// Unable to scaffold the index '{indexName}'. The following columns could not be scaffolded: {columnNames}. + /// For index {indexName}. Unable to find parent table {tablename}. Skipping index. /// - public static string UnableToScaffoldIndexMissingProperty([CanBeNull] object indexName, [CanBeNull] object columnNames) + public static string UnableToFindTableForIndex([CanBeNull] object indexName, [CanBeNull] object tablename) => string.Format( - GetString("UnableToScaffoldIndexMissingProperty", nameof(indexName), nameof(columnNames)), - indexName, columnNames); + GetString("UnableToFindTableForIndex", nameof(indexName), nameof(tablename)), + indexName, tablename); /// - /// Cannot scaffold the connection string. The "UseProviderMethodName" is missing from the scaffolding model. + /// Found an index on table {tablename} with an empty or null name. Skipping index. /// - public static string MissingUseProviderMethodNameAnnotation - => GetString("MissingUseProviderMethodNameAnnotation"); + public static string IndexNameEmpty([CanBeNull] object tablename) + => string.Format( + GetString("IndexNameEmpty", nameof(tablename)), + tablename); /// - /// The following file(s) already exist in directory {outputDirectoryName}: {existingFiles}. Use the Force flag to overwrite these files. + /// Found a foreign key on table {tablename} with an empty or null name. Skipping foreign key. /// - public static string ExistingFiles([CanBeNull] object outputDirectoryName, [CanBeNull] object existingFiles) + public static string ForeignKeyNameEmpty([CanBeNull] object tablename) => string.Format( - GetString("ExistingFiles", nameof(outputDirectoryName), nameof(existingFiles)), - outputDirectoryName, existingFiles); + GetString("ForeignKeyNameEmpty", nameof(tablename)), + tablename); + + /// + /// Foreign key column {columnName} belongs to foreign key {fkName} on table {tablename} which is not included in the selection set. Skipping. + /// + public static string ForeignKeyColumnNotInSelectionSet([CanBeNull] object columnName, [CanBeNull] object fkName, [CanBeNull] object tablename) + => string.Format( + GetString("ForeignKeyColumnNotInSelectionSet", nameof(columnName), nameof(fkName), nameof(tablename)), + columnName, fkName, tablename); + + /// + /// For foreign key {fkName} on table {tablename}, unable to model the end of the foreign key on principal table {principalTableName}. This is usually because the principal table was not included in the selection set. + /// + public static string PrincipalTableNotInSelectionSet([CanBeNull] object fkName, [CanBeNull] object tablename, [CanBeNull] object principalTableName) + => string.Format( + GetString("PrincipalTableNotInSelectionSet", nameof(fkName), nameof(tablename), nameof(principalTableName)), + fkName, tablename, principalTableName); + + /// + /// Unable to generate entity type for table '{tableName}'. + /// + public static string UnableToGenerateEntityType([CanBeNull] object tableName) + => string.Format( + GetString("UnableToGenerateEntityType", nameof(tableName)), + tableName); + + /// + /// Unable to scaffold the index '{indexName}'. The following columns could not be scaffolded: {columnNames}. + /// + public static string UnableToScaffoldIndexMissingProperty([CanBeNull] object indexName, [CanBeNull] object columnNames) + => string.Format( + GetString("UnableToScaffoldIndexMissingProperty", nameof(indexName), nameof(columnNames)), + indexName, columnNames); /// /// Sequence name cannot be null or empty. Entity Framework cannot model a sequence that does not have a name. diff --git a/src/EFCore.Relational.Design/Properties/RelationalDesignStrings.resx b/src/EFCore.Relational.Design/Properties/RelationalDesignStrings.resx index c19f5d901f7..ee1a47ad23c 100644 --- a/src/EFCore.Relational.Design/Properties/RelationalDesignStrings.resx +++ b/src/EFCore.Relational.Design/Properties/RelationalDesignStrings.resx @@ -117,6 +117,24 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Metadata model returned should not be null. Provider: {providerTypeName}. + + + No files generated in directory {outputDirectoryName}. The following file(s) already exist and must be made writeable to continue: {readOnlyFiles}. + + + Cannot scaffold the connection string. The "UseProviderMethodName" is missing from the scaffolding model. + + + The following file(s) already exist in directory {outputDirectoryName}: {existingFiles}. Use the Force flag to overwrite these files. + + + Found a column on index {indexName} on table {tableName} with an empty or null name. Not including column in index. + + + For foreign key with identity {id} on table {tableName}, unable to find the column called {principalColumnName} on the foreign key's principal table, {principalTableName}. Skipping foreign key. + Could not find type mapping for column '{columnName}' with data type '{dateType}'. Skipping column. @@ -138,11 +156,47 @@ Unable to identify the primary key for table '{tableName}'. - - Metadata model returned should not be null. Provider: {providerTypeName}. + + Found table with name: {name}. - - No files generated in directory {outputDirectoryName}. The following file(s) already exist and must be made writeable to continue: {readOnlyFiles}. + + Table {tableName} is not included in the selection set. Skipping. + + + Column {columnName} belongs to table {tableName} which is not included in the selection set. Skipping. + + + Found index with name: {indexName}, table: {tableName}, is unique: {isUnique}. + + + Found index column on index {indexName} on table {tableName}, column name: {columnName}, ordinal: {ordinal}. + + + Index column {columnName} belongs to index {indexName} on table {tableName} which is not included in the selection set. Skipping. + + + Found sequence name: {name}, data type: {dataType}, cyclic: {isCyclic}, increment: {increment}, start: {start}, minimum: {min}, maximum: {max}. + + + Found a column on foreign key {tableName}.{fkName} with an empty or null name. Not including column in foreign key + + + Found a column on table {tableName} with an empty or null name. Skipping column. + + + For index {indexName}. Unable to find parent table {tablename}. Skipping index. + + + Found an index on table {tablename} with an empty or null name. Skipping index. + + + Found a foreign key on table {tablename} with an empty or null name. Skipping foreign key. + + + Foreign key column {columnName} belongs to foreign key {fkName} on table {tablename} which is not included in the selection set. Skipping. + + + For foreign key {fkName} on table {tablename}, unable to model the end of the foreign key on principal table {principalTableName}. This is usually because the principal table was not included in the selection set. Unable to generate entity type for table '{tableName}'. @@ -150,12 +204,6 @@ Unable to scaffold the index '{indexName}'. The following columns could not be scaffolded: {columnNames}. - - Cannot scaffold the connection string. The "UseProviderMethodName" is missing from the scaffolding model. - - - The following file(s) already exist in directory {outputDirectoryName}: {existingFiles}. Use the Force flag to overwrite these files. - Sequence name cannot be null or empty. Entity Framework cannot model a sequence that does not have a name. diff --git a/src/EFCore.Relational.Design/RelationalScaffoldingModelFactory.cs b/src/EFCore.Relational.Design/RelationalScaffoldingModelFactory.cs index 2f28e6d4756..081a31e183a 100644 --- a/src/EFCore.Relational.Design/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Relational.Design/RelationalScaffoldingModelFactory.cs @@ -25,7 +25,7 @@ public class RelationalScaffoldingModelFactory : IScaffoldingModelFactory internal const string NavigationNameUniquifyingPattern = "{0}Navigation"; internal const string SelfReferencingPrincipalEndNavigationNamePattern = "Inverse{0}"; - protected virtual IInterceptingLogger Logger { get; } + protected virtual IDiagnosticsLogger Logger { get; } protected virtual IRelationalTypeMapper TypeMapper { get; } protected virtual CandidateNamingService CandidateNamingService { get; } @@ -37,7 +37,7 @@ public class RelationalScaffoldingModelFactory : IScaffoldingModelFactory private readonly IPluralizer _pluralizer; public RelationalScaffoldingModelFactory( - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, [NotNull] IRelationalTypeMapper typeMapper, [NotNull] IDatabaseModelFactory databaseModelFactory, [NotNull] CandidateNamingService candidateNamingService, @@ -73,16 +73,12 @@ public virtual void CheckSelectionsMatched([NotNull] TableSelectionSet tableSele { foreach (var schemaSelection in tableSelectionSet.Schemas.Where(s => !s.IsMatched)) { - Logger.LogWarning( - RelationalDesignEventId.MissingSchemaWarning, - () => RelationalDesignStrings.MissingSchema(schemaSelection.Text)); + Logger.MissingSchemaWarning(schemaSelection.Text); } foreach (var tableSelection in tableSelectionSet.Tables.Where(t => !t.IsMatched)) { - Logger.LogWarning( - RelationalDesignEventId.MissingTableWarning, - () => RelationalDesignStrings.MissingTable(tableSelection.Text)); + Logger.MissingTableWarning(tableSelection.Text); } } @@ -170,9 +166,7 @@ protected virtual RelationalSequenceBuilder VisitSequence([NotNull] ModelBuilder if (string.IsNullOrEmpty(sequence.Name)) { - Logger.LogWarning( - RelationalDesignEventId.SequenceMustBeNamedWarning, - () => RelationalDesignStrings.SequencesRequireName); + Logger.SequenceNotNamedWarning(); return null; } @@ -185,9 +179,7 @@ protected virtual RelationalSequenceBuilder VisitSequence([NotNull] ModelBuilder if (sequenceType != null && !Sequence.SupportedTypes.Contains(sequenceType)) { - Logger.LogWarning( - RelationalDesignEventId.SequenceTypeNotSupportedWarning, - () => RelationalDesignStrings.BadSequenceType(sequence.Name, sequence.DataType)); + Logger.SequenceTypeNotSupportedWarning(sequence.Name, sequence.DataType); return null; } @@ -257,9 +249,7 @@ protected virtual EntityTypeBuilder VisitTable([NotNull] ModelBuilder modelBuild if (keyBuilder == null) { var errorMessage = RelationalDesignStrings.UnableToGenerateEntityType(table.DisplayName); - Logger.LogWarning( - RelationalDesignEventId.UnableToGenerateEntityTypeWarning, - () => errorMessage); + Logger.UnableToGenerateEntityTypeWarning(table.DisplayName); var model = modelBuilder.Model; model.RemoveEntityType(entityTypeName); @@ -296,9 +286,7 @@ protected virtual PropertyBuilder VisitColumn([NotNull] EntityTypeBuilder builde if (clrType == null) { _unmappedColumns.Add(column); - Logger.LogWarning( - RelationalDesignEventId.ColumnTypeNotMappedWarning, - () => RelationalDesignStrings.CannotFindTypeMappingForColumn(column.DisplayName, column.DataType)); + Logger.ColumnTypeNotMappedWarning(column.DisplayName, column.DataType); return null; } @@ -370,9 +358,7 @@ protected virtual KeyBuilder VisitPrimaryKey([NotNull] EntityTypeBuilder builder if (keyColumns.Count == 0) { - Logger.LogWarning( - RelationalDesignEventId.MissingPrimaryKeyWarning, - () => RelationalDesignStrings.MissingPrimaryKey(table.DisplayName)); + Logger.MissingPrimaryKeyWarning(table.DisplayName); return null; } @@ -381,11 +367,7 @@ protected virtual KeyBuilder VisitPrimaryKey([NotNull] EntityTypeBuilder builder .Select(c => c.Name).ToList(); if (unmappedColumns.Any()) { - Logger.LogWarning( - RelationalDesignEventId.PrimaryKeyColumnsNotMappedWarning, - () => RelationalDesignStrings.PrimaryKeyErrorPropertyNotFound( - table.DisplayName, - string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, unmappedColumns))); + Logger.PrimaryKeyColumnsNotMappedWarning(table.DisplayName, unmappedColumns); return null; } @@ -418,11 +400,7 @@ protected virtual IndexBuilder VisitIndex([NotNull] EntityTypeBuilder builder, [ .Select(c => c.Name).ToList(); if (unmappedColumns.Any()) { - Logger.LogWarning( - RelationalDesignEventId.IndexColumnsNotMappedWarning, - () => RelationalDesignStrings.UnableToScaffoldIndexMissingProperty( - index.Name, - string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, unmappedColumns))); + Logger.IndexColumnsNotMappedWarning(index.Name, unmappedColumns); return null; } @@ -496,9 +474,7 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode if (foreignKey.PrincipalTable == null) { - Logger.LogWarning( - RelationalDesignEventId.ForeignKeyReferencesMissingTableWarning, - () => RelationalDesignStrings.ForeignKeyScaffoldErrorPrincipalTableNotFound(foreignKey.DisplayName)); + Logger.ForeignKeyReferencesMissingTableWarning(foreignKey.DisplayName); return null; } @@ -522,11 +498,7 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode .ToList(); if (unmappedDependentColumns.Any()) { - Logger.LogWarning( - RelationalDesignEventId.ForeignKeyColumnsNotMappedWarning, - () => RelationalDesignStrings.ForeignKeyScaffoldErrorPropertyNotFound( - foreignKey.DisplayName, - string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, unmappedDependentColumns))); + Logger.ForeignKeyColumnsNotMappedWarning(foreignKey.DisplayName, unmappedDependentColumns); return null; } @@ -539,10 +511,7 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode var principalEntityType = modelBuilder.Model.FindEntityType(GetEntityTypeName(foreignKey.PrincipalTable)); if (principalEntityType == null) { - Logger.LogWarning( - RelationalDesignEventId.ForeignKeyReferencesMissingTableWarning, - () => RelationalDesignStrings.ForeignKeyScaffoldErrorPrincipalTableScaffoldingError( - foreignKey.DisplayName, foreignKey.PrincipalTable.DisplayName)); + Logger.ForeignKeyReferencesNotMappedTableWarning(foreignKey.DisplayName, foreignKey.PrincipalTable.DisplayName); return null; } @@ -553,11 +522,7 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode .ToList(); if (unmappedPrincipalColumns.Any()) { - Logger.LogWarning( - RelationalDesignEventId.ForeignKeyColumnsNotMappedWarning, - () => RelationalDesignStrings.ForeignKeyScaffoldErrorPropertyNotFound( - foreignKey.DisplayName, - string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, unmappedPrincipalColumns))); + Logger.ForeignKeyColumnsNotMappedWarning(foreignKey.DisplayName, unmappedPrincipalColumns); return null; } @@ -581,14 +546,11 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode principalPropertiesMap.Where(tuple => tuple.Item1.IsNullable); if (nullablePrincipalProperties.Any()) { - Logger.LogWarning( - RelationalDesignEventId.ForeignKeyPrincipalEndContainsNullableColumns, - () => RelationalDesignStrings.ForeignKeyPrincipalEndContainsNullableColumns( + Logger.ForeignKeyPrincipalEndContainsNullableColumnsWarning( foreignKey.DisplayName, index.Relational().Name, - nullablePrincipalProperties - .Select(tuple => tuple.Item2.DisplayName) - .Aggregate((a, b) => a + "," + b))); + nullablePrincipalProperties.Select(tuple => tuple.Item2.DisplayName).ToList()); + nullablePrincipalProperties .ToList().ForEach(tuple => tuple.Item1.IsNullable = false); } @@ -596,14 +558,11 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode } else { - var principalColumns = foreignKeyColumns - .Select(c => c.PrincipalColumn.Name) - .Aggregate((a, b) => a + "," + b); - - Logger.LogWarning( - RelationalDesignEventId.ForeignKeyReferencesMissingPrincipalKeyWarning, - () => RelationalDesignStrings.ForeignKeyScaffoldErrorPrincipalKeyNotFound( - foreignKey.DisplayName, principalColumns, principalEntityType.DisplayName())); + var principalColumns = foreignKeyColumns.Select(c => c.PrincipalColumn.Name).ToList(); + + Logger.ForeignKeyReferencesMissingPrincipalKeyWarning( + foreignKey.DisplayName, principalEntityType.DisplayName(), principalColumns); + return null; } } diff --git a/src/EFCore.Relational.Specification.Tests/QueryNoClientEvalTestBase.cs b/src/EFCore.Relational.Specification.Tests/QueryNoClientEvalTestBase.cs index 875d53ac3e3..232edb765f8 100644 --- a/src/EFCore.Relational.Specification.Tests/QueryNoClientEvalTestBase.cs +++ b/src/EFCore.Relational.Specification.Tests/QueryNoClientEvalTestBase.cs @@ -19,11 +19,13 @@ public virtual void Throws_when_where() { using (var context = CreateContext()) { - Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("[c].IsLondon")), Assert.Throws( - () => context.Customers.Where(c => c.IsLondon).ToList()).Message); + () => context.Customers.Where(c => c.IsLondon).ToList()) + .Message); } } @@ -33,7 +35,7 @@ public virtual void Throws_when_orderby() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("orderby [c].IsLondon asc")), Assert.Throws( () => context.Customers.OrderBy(c => c.IsLondon).ToList()).Message); @@ -46,7 +48,7 @@ public virtual void Throws_when_orderby_multiple() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("orderby [c].IsLondon asc, ClientMethod([c]) asc")), Assert.Throws( () => context.Customers @@ -64,7 +66,7 @@ public virtual void Throws_when_where_subquery_correlated() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning( "{from Customer c2 in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Microsoft.EntityFrameworkCore.Specification.Tests.TestModels.Northwind.Customer]) where (([c1].CustomerID == [c2].CustomerID) AndAlso [c2].IsLondon) select [c2] => Any()}")), Assert.Throws( @@ -81,7 +83,7 @@ public virtual void Throws_when_all() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("All([c].IsLondon)")), Assert.Throws( () => context.Customers.All(c => c.IsLondon)).Message); @@ -94,7 +96,7 @@ public virtual void Throws_when_from_sql_composed() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("[c].IsLondon")), Assert.Throws( () => context.Customers @@ -124,7 +126,7 @@ public virtual void Throws_when_subquery_main_from_clause() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("[c].IsLondon")), Assert.Throws( () => @@ -144,7 +146,7 @@ public virtual void Throws_when_select_many() { Assert.Equal( CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("from Int32 i in value(System.Int32[])")), Assert.Throws( () => @@ -161,7 +163,7 @@ public virtual void Throws_when_join() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("join Int32 i in __p_0 on [e1].EmployeeID equals [i]")), Assert.Throws( () => @@ -178,7 +180,7 @@ public virtual void Throws_when_group_join() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("join Int32 i in __p_0 on [e1].EmployeeID equals [i]")), Assert.Throws( () => @@ -195,7 +197,7 @@ public virtual void Throws_when_group_by() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("GroupBy([c].CustomerID, [c])")), Assert.Throws( () => context.Customers @@ -210,7 +212,7 @@ public virtual void Throws_when_first() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("[c].IsLondon")), Assert.Throws( () => context.Customers.First(c => c.IsLondon)).Message); @@ -223,7 +225,7 @@ public virtual void Throws_when_single() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("[c].IsLondon")), Assert.Throws( () => context.Customers.Single(c => c.IsLondon)).Message); @@ -236,7 +238,7 @@ public virtual void Throws_when_first_or_default() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("[c].IsLondon")), Assert.Throws( () => context.Customers.FirstOrDefault(c => c.IsLondon)).Message); @@ -249,7 +251,7 @@ public virtual void Throws_when_single_or_default() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("[c].IsLondon")), Assert.Throws( () => context.Customers.SingleOrDefault(c => c.IsLondon)).Message); diff --git a/src/EFCore.Relational.Specification.Tests/WarningsTestBase.cs b/src/EFCore.Relational.Specification.Tests/WarningsTestBase.cs index 25a6daa1828..7a407e2ad27 100644 --- a/src/EFCore.Relational.Specification.Tests/WarningsTestBase.cs +++ b/src/EFCore.Relational.Specification.Tests/WarningsTestBase.cs @@ -20,7 +20,7 @@ public virtual void Throws_when_warning_as_error() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("[c].IsLondon")), Assert.Throws( () => context.Customers.Where(c => c.IsLondon).ToList()).Message); @@ -110,7 +110,7 @@ public virtual void Last_without_order_by_issues_client_eval_warning() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("Last()")), Assert.Throws( () => context.Customers.Last()).Message); @@ -123,7 +123,7 @@ public virtual void Last_with_order_by_issues_client_eval_warning_in_subquery() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("Last()")), Assert.Throws( () => context.Customers.Where(c => c.CustomerID == "ALFKI" && c.Orders.OrderBy(o => o.OrderID).Last().OrderID > 1000).ToList()).Message); @@ -136,7 +136,7 @@ public virtual void LastOrDefault_without_order_by_issues_client_eval_warning() using (var context = CreateContext()) { Assert.Equal(CoreStrings.WarningAsErrorTemplate( - $"{nameof(RelationalEventId)}.{nameof(RelationalEventId.QueryClientEvaluationWarning)}", + RelationalEventId.QueryClientEvaluationWarning, RelationalStrings.ClientEvalWarning("LastOrDefault()")), Assert.Throws( () => context.Customers.LastOrDefault()).Message); diff --git a/src/EFCore.Relational/EFCore.Relational.csproj b/src/EFCore.Relational/EFCore.Relational.csproj index d51562729de..059ef8feefd 100644 --- a/src/EFCore.Relational/EFCore.Relational.csproj +++ b/src/EFCore.Relational/EFCore.Relational.csproj @@ -18,7 +18,6 @@ - diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 48d97a81687..4f8e4dac716 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Metadata; @@ -151,10 +150,6 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); - ServiceCollectionMap - .TryAddSingleton(s => new DiagnosticListener("Microsoft.EntityFrameworkCore")) - .TryAddSingleton(s => s.GetService()); - ServiceCollectionMap.GetInfrastructure() .AddDependencySingleton() .AddDependencySingleton() diff --git a/src/EFCore.Relational/Infrastructure/RelationalEventId.cs b/src/EFCore.Relational/Infrastructure/RelationalEventId.cs index 7977b544665..6abf7ca16e2 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalEventId.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalEventId.cs @@ -1,110 +1,230 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Infrastructure { /// - /// Values that are used as the eventId when logging messages from a relational database provider via . + /// + /// Event IDs for relational events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// /// - public enum RelationalEventId + public static class RelationalEventId { + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Connection events + ConnectionOpening = CoreEventId.RelationalBaseId, + ConnectionOpened, + ConnectionClosing, + ConnectionClosed, + ConnectionError, + + // Command events + CommandExecuting = CoreEventId.RelationalBaseId + 100, + CommandExecuted, + CommandError, + + // Transaction events + TransactionStarted = CoreEventId.RelationalBaseId + 200, + TransactionUsed, + TransactionCommitted, + TransactionRolledBack, + TransactionDisposed, + TransactionError, + AmbientTransactionWarning, + + // DataReader events + DataReaderDisposing = CoreEventId.RelationalBaseId + 300, + + // Migrations events + MigrateUsingConnection = CoreEventId.RelationalBaseId + 400, + MigrationReverting, + MigrationApplying, + MigrationGeneratingDownScript, + MigrationGeneratingUpScript, + + // Query events + QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500, + QueryPossibleUnintendedUseOfEqualsWarning, + + // Model validation events + ModelValidationKeyDefaultValueWarning = CoreEventId.RelationalBaseId + 600, + } + + private static readonly string _connectionPrefix = LoggerCategory.Database.Connection.Name + "."; + private static EventId MakeConnectionId(Id id) => new EventId((int)id, _connectionPrefix + id); + /// - /// A command was executed against the database. + /// A database connection is opening. + /// This event is in the category. /// - ExecutedCommand = 1, + public static readonly EventId ConnectionOpening = MakeConnectionId(Id.ConnectionOpening); /// - /// A database is being created. + /// A database connection has been opened. + /// This event is in the category. /// - CreatingDatabase, + public static readonly EventId ConnectionOpened = MakeConnectionId(Id.ConnectionOpened); /// - /// A connection is being opened. + /// A database connection is closing. + /// This event is in the category. /// - OpeningConnection, + public static readonly EventId ConnectionClosing = MakeConnectionId(Id.ConnectionClosing); /// - /// A connection is being closed. + /// A database connection has been closed. + /// This event is in the category. /// - ClosingConnection, + public static readonly EventId ConnectionClosed = MakeConnectionId(Id.ConnectionClosed); /// - /// A transaction is beginning. + /// A error occurred while opening or using a database connection. + /// This event is in the category. /// - BeginningTransaction, + public static readonly EventId ConnectionError = MakeConnectionId(Id.ConnectionError); + + private static readonly string _sqlPrefix = LoggerCategory.Database.Sql.Name + "."; + private static EventId MakeCommandId(Id id) => new EventId((int)id, _sqlPrefix + id); /// - /// A transaction is being committed. + /// A database command is executing. + /// This event is in the category. /// - CommittingTransaction, + public static readonly EventId CommandExecuting = MakeCommandId(Id.CommandExecuting); /// - /// A transaction is being rolled back. + /// A database command has been executed. + /// This event is in the category. /// - RollingbackTransaction, + public static readonly EventId CommandExecuted = MakeCommandId(Id.CommandExecuted); /// - /// A LINQ query is being executed where some of the query will be evaluated on the client - /// (i.e. part of the query can not be translated to SQL). + /// An error occurred while a database command was executing. + /// This event is in the category. /// - QueryClientEvaluationWarning, + public static readonly EventId CommandError = MakeCommandId(Id.CommandError); + + private static readonly string _transactionPrefix = LoggerCategory.Database.Transaction.Name + "."; + private static EventId MakeTransactionId(Id id) => new EventId((int)id, _transactionPrefix + id); /// - /// Two entities were compared for equality in a LINQ query, which may not produce the desired result. + /// A database transaction has been started. + /// This event is in the category. /// - PossibleUnintendedUseOfEqualsWarning, + public static readonly EventId TransactionStarted = MakeTransactionId(Id.TransactionStarted); /// - /// An ambient transaction is present, which is not fully supported by Entity Framework Core. + /// Entity Framework started using an already existing database transaction. + /// This event is in the category. /// - AmbientTransactionWarning, + public static readonly EventId TransactionUsed = MakeTransactionId(Id.TransactionUsed); /// - /// Linq translation of 'Contains', 'EndsWith' and 'StartsWith' functions may produce incorrect results - /// when searched value contains wildcard characters. + /// A database transaction has been committed. + /// This event is in the category. /// - PossibleIncorrectResultsUsingLikeOperator, + public static readonly EventId TransactionCommitted = MakeTransactionId(Id.TransactionCommitted); /// - /// A migration is being applied to the database. + /// A database transaction has been rolled back. + /// This event is in the category. /// - ApplyingMigration, + public static readonly EventId TransactionRolledBack = MakeTransactionId(Id.TransactionRolledBack); /// - /// The revert script is being generated for a migration. + /// A database transaction has been disposed. + /// This event is in the category. /// - GeneratingMigrationDownScript, + public static readonly EventId TransactionDisposed = MakeTransactionId(Id.TransactionDisposed); /// - /// The apply script is being generated for a migration. + /// An error has occurred while using. committing, or rolling back a database transaction. + /// This event is in the category. /// - GeneratingMigrationUpScript, + public static readonly EventId TransactionError = MakeTransactionId(Id.TransactionError); /// - /// Migrations are being applied on the database. + /// An application may have expected an ambient transaction to be used when it was actually ignorred. + /// This event is in the category. /// - MigrateUsingConnection, + public static readonly EventId AmbientTransactionWarning = MakeTransactionId(Id.AmbientTransactionWarning); + + private static readonly string _dataReaderPrefix = LoggerCategory.Database.DataReader.Name + "."; + private static EventId MakeReaderId(Id id) => new EventId((int)id, _dataReaderPrefix + id); + + /// + /// A database data reader has been disposed. + /// This event is in the category. + /// + public static readonly EventId DataReaderDisposing = MakeReaderId(Id.DataReaderDisposing); + + private static readonly string _migrationsPrefix = LoggerCategory.Migrations.Name + "."; + private static EventId MakeMigrationsId(Id id) => new EventId((int)id, _migrationsPrefix + id); + + /// + /// Migrations is using a database connection. + /// This event is in the category. + /// + public static readonly EventId MigrateUsingConnection = MakeMigrationsId(Id.MigrateUsingConnection); /// /// A migration is being reverted. + /// This event is in the category. + /// + public static readonly EventId MigrationReverting = MakeMigrationsId(Id.MigrationReverting); + + /// + /// A migration is being applied. + /// This event is in the category. + /// + public static readonly EventId MigrationApplying = MakeMigrationsId(Id.MigrationApplying); + + /// + /// Migrations is generating a "down" script. + /// This event is in the category. /// - RevertingMigration, + public static readonly EventId MigrationGeneratingDownScript = MakeMigrationsId(Id.MigrationGeneratingDownScript); /// - /// The SQL for a migration being reverted. + /// Migrations is generating an "up" script. + /// This event is in the category. /// - RevertingMigrationSql, + public static readonly EventId MigrationGeneratingUpScript = MakeMigrationsId(Id.MigrationGeneratingUpScript); + + private static readonly string _queryPrefix = LoggerCategory.Query.Name + "."; + private static EventId MakeQueryId(Id id) => new EventId((int)id, _queryPrefix + id); /// - /// The SQL for a migration being applied. + /// Part of a query is being evaluated on the client instead of on the database server. + /// This event is in the category. /// - ApplyingMigrationSql, + public static readonly EventId QueryClientEvaluationWarning = MakeQueryId(Id.QueryClientEvaluationWarning); + + /// + /// A query is using equals comparisons in a possibly unintended way. + /// This event is in the category. + /// + public static readonly EventId QueryPossibleUnintendedUseOfEqualsWarning = MakeQueryId(Id.QueryPossibleUnintendedUseOfEqualsWarning); + + private static readonly string _validationPrefix = LoggerCategory.Model.Validation.Name + "."; + private static EventId MakeValidationId(Id id) => new EventId((int)id, _validationPrefix + id); /// - /// A warning during model validation indicating a key is configured with a default value. + /// A single database default column value has been set on a key column. + /// This event is in the category. /// - ModelValidationKeyDefaultValueWarning + public static readonly EventId ModelValidationKeyDefaultValueWarning = MakeValidationId(Id.ModelValidationKeyDefaultValueWarning); } } diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 3628b4fbbd6..76d33eb6097 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Infrastructure @@ -96,8 +95,7 @@ protected virtual void EnsureNoDefaultValuesOnKeys([NotNull] IModel model) k => k.Properties)) .Where(p => RelationalExtensions.For(p).DefaultValue != null)) { - ShowWarning(RelationalEventId.ModelValidationKeyDefaultValueWarning, - RelationalStrings.KeyHasDefaultValue(property.Name, property.DeclaringEntityType.DisplayName())); + Dependencies.Logger.ModelValidationKeyDefaultValueWarning(property); } } @@ -443,13 +441,6 @@ private void ValidateDiscriminatorValues(IEntityType rootEntityType) } } - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - protected virtual void ShowWarning(RelationalEventId eventId, [NotNull] string message) - => Dependencies.Logger.LogWarning(eventId, () => message); - private static string Format(IEnumerable columnNames) => "{" + string.Join(", ", columnNames.Select(c => "'" + c + "'")) + "}"; diff --git a/src/EFCore.Relational/Internal/RelationalDiagnostics.cs b/src/EFCore.Relational/Internal/RelationalDiagnostics.cs deleted file mode 100644 index 6ee0248eca9..00000000000 --- a/src/EFCore.Relational/Internal/RelationalDiagnostics.cs +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Data.Common; -using System.Diagnostics; - -namespace Microsoft.EntityFrameworkCore.Internal -{ - internal static class RelationalDiagnostics - { - private const string NamePrefix = "Microsoft.EntityFrameworkCore."; - - public const string BeforeExecuteCommand = NamePrefix + nameof(BeforeExecuteCommand); - public const string AfterExecuteCommand = NamePrefix + nameof(AfterExecuteCommand); - public const string CommandExecutionError = NamePrefix + nameof(CommandExecutionError); - - public const string ConnectionOpening = NamePrefix + nameof(ConnectionOpening); - public const string ConnectionOpened = NamePrefix + nameof(ConnectionOpened); - public const string ConnectionClosing = NamePrefix + nameof(ConnectionClosing); - public const string ConnectionClosed = NamePrefix + nameof(ConnectionClosed); - public const string ConnectionError = NamePrefix + nameof(ConnectionError); - - public const string TransactionStarted = NamePrefix + nameof(TransactionStarted); - public const string TransactionCommitted = NamePrefix + nameof(TransactionCommitted); - public const string TransactionRolledback = NamePrefix + nameof(TransactionRolledback); - public const string TransactionDisposed = NamePrefix + nameof(TransactionDisposed); - public const string TransactionError = NamePrefix + nameof(TransactionError); - - public const string DataReaderDisposing = NamePrefix + nameof(DataReaderDisposing); - - public static void WriteCommandBefore( - this DiagnosticSource diagnosticSource, - Guid connectionId, - DbCommand command, - string executeMethod, - Guid instanceId, - long startTimestamp, - bool async) - { - if (diagnosticSource.IsEnabled(BeforeExecuteCommand)) - { - diagnosticSource.Write( - BeforeExecuteCommand, - new RelationalDiagnosticSourceBeforeMessage - { - ConnectionId = connectionId, - Command = command, - ExecuteMethod = executeMethod, - InstanceId = instanceId, - Timestamp = startTimestamp, - IsAsync = async - }); - } - } - - public static void WriteCommandAfter( - this DiagnosticSource diagnosticSource, - Guid connectionId, - DbCommand command, - string executeMethod, - object methodResult, - Guid instanceId, - long startTimestamp, - long currentTimestamp, - bool async = false) - { - if (diagnosticSource.IsEnabled(AfterExecuteCommand)) - { - diagnosticSource.Write( - AfterExecuteCommand, - new RelationalDiagnosticSourceAfterMessage - { - ConnectionId = connectionId, - Command = command, - ExecuteMethod = executeMethod, - Result = methodResult, - InstanceId = instanceId, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp, - IsAsync = async - }); - } - } - - public static void WriteCommandError( - this DiagnosticSource diagnosticSource, - Guid connectionId, - DbCommand command, - string executeMethod, - Guid instanceId, - long startTimestamp, - long currentTimestamp, - Exception exception, - bool async) - { - if (diagnosticSource.IsEnabled(CommandExecutionError)) - { - diagnosticSource.Write( - CommandExecutionError, - new RelationalDiagnosticSourceAfterMessage - { - ConnectionId = connectionId, - Command = command, - ExecuteMethod = executeMethod, - InstanceId = instanceId, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp, - Exception = exception, - IsAsync = async - }); - } - } - - public static void WriteConnectionOpening( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - Guid instanceId, - long startTimestamp, - bool async) - { - if (diagnosticSource.IsEnabled(ConnectionOpening)) - { - diagnosticSource.Write(ConnectionOpening, - new - { - Connection = connection, - ConnectionId = connectionId, - InstanceId = instanceId, - Timestamp = startTimestamp, - IsAsync = async - }); - } - } - - public static void WriteConnectionOpened( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - Guid instanceId, - long startTimestamp, - long currentTimestamp, - bool async) - { - if (diagnosticSource.IsEnabled(ConnectionOpened)) - { - diagnosticSource.Write(ConnectionOpened, - new - { - Connection = connection, - ConnectionId = connectionId, - InstanceId = instanceId, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp, - IsAsync = async - }); - } - } - - public static void WriteConnectionClosing( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - Guid instanceId, - long startTimestamp, - bool async) - { - if (diagnosticSource.IsEnabled(ConnectionClosing)) - { - diagnosticSource.Write(ConnectionClosing, - new - { - Connection = connection, - ConnectionId = connectionId, - InstanceId = instanceId, - Timestamp = startTimestamp, - IsAsync = async - }); - } - } - - public static void WriteConnectionClosed( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - Guid instanceId, - long startTimestamp, - long currentTimestamp, - bool async) - { - if (diagnosticSource.IsEnabled(ConnectionClosed)) - { - diagnosticSource.Write(ConnectionClosed, - new - { - Connection = connection, - ConnectionId = connectionId, - InstanceId = instanceId, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp, - IsAsync = async - }); - } - } - - public static void WriteConnectionError( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - Exception exception, - Guid instanceId, - long startTimestamp, - long currentTimestamp, - bool async) - { - if (diagnosticSource.IsEnabled(ConnectionError)) - { - diagnosticSource.Write(ConnectionError, - new - { - Connection = connection, - ConnectionId = connectionId, - Exception = exception, - InstanceId = instanceId, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp, - IsAsync = async - }); - } - } - - public static void WriteTransactionStarted( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - DbTransaction transaction) - { - if (diagnosticSource.IsEnabled(TransactionStarted)) - { - diagnosticSource.Write(TransactionStarted, - new - { - Connection = connection, - ConnectionId = connectionId, - Transaction = transaction - }); - } - } - - public static void WriteTransactionCommit( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - DbTransaction transaction, - long startTimestamp, - long currentTimestamp) - { - if (diagnosticSource.IsEnabled(TransactionCommitted)) - { - diagnosticSource.Write(TransactionCommitted, - new - { - Connection = connection, - ConnectionId = connectionId, - Transaction = transaction, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp - }); - } - } - - public static void WriteTransactionRollback( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - DbTransaction transaction, - long startTimestamp, - long currentTimestamp) - { - if (diagnosticSource.IsEnabled(TransactionRolledback)) - { - diagnosticSource.Write(TransactionRolledback, - new - { - Connection = connection, - ConnectionId = connectionId, - Transaction = transaction, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp - }); - } - } - - public static void WriteTransactionDisposed( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - DbTransaction transaction) - { - if (diagnosticSource.IsEnabled(TransactionDisposed)) - { - diagnosticSource.Write(TransactionDisposed, - new - { - Connection = connection, - ConnectionId = connectionId, - Transaction = transaction - }); - } - } - - public static void WriteTransactionError( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - DbTransaction transaction, - string action, - Exception exception, - long startTimestamp, - long currentTimestamp) - { - if (diagnosticSource.IsEnabled(TransactionError)) - { - diagnosticSource.Write(TransactionError, - new - { - Connection = connection, - ConnectionId = connectionId, - Transaction = transaction, - Exception = exception, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp - }); - } - } - - public static void WriteDataReaderDisposing( - this DiagnosticSource diagnosticSource, - DbConnection connection, - Guid connectionId, - DbDataReader dataReader, - int recordsAffected, - long startTimestamp, - long currentTimestamp) - { - if (diagnosticSource.IsEnabled(DataReaderDisposing)) - { - diagnosticSource.Write(DataReaderDisposing, - new - { - Connection = connection, - ConnectionId = connectionId, - DataReader = dataReader, - RecordsAffected = recordsAffected, - Timestamp = currentTimestamp, - Duration = currentTimestamp - startTimestamp - }); - } - } - } -} diff --git a/src/EFCore.Relational/Internal/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Internal/RelationalLoggerExtensions.cs new file mode 100644 index 00000000000..fa5b18ce3f0 --- /dev/null +++ b/src/EFCore.Relational/Internal/RelationalLoggerExtensions.cs @@ -0,0 +1,935 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Data.Common; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Microsoft.Extensions.Logging; +using Remotion.Linq; + +namespace Microsoft.EntityFrameworkCore.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static class RelationalLoggerExtensions + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void CommandExecuting( + [NotNull] this IDiagnosticsLogger diagnostics, + Guid connectionId, + [NotNull] DbCommand command, + [NotNull] string executeMethod, + Guid instanceId, + long startTimestamp, + bool async) + { + var eventId = RelationalEventId.CommandExecuting; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + var logData = CreateCommandLogData(diagnostics, command, 0, 0); + + var message = RelationalStrings.RelationalLoggerExecutingCommand( + logData.Parameters + // Interpolation okay here because value is always a string. + .Select(p => $"{p.Name}={p.FormatParameter()}") + .Join(), + logData.CommandType, + logData.CommandTimeout, + Environment.NewLine, + logData.CommandText); + + diagnostics.Logger.LogDebug(eventId, message); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new RelationalDiagnosticSourceBeforeMessage + { + ConnectionId = connectionId, + Command = command, + ExecuteMethod = executeMethod, + InstanceId = instanceId, + Timestamp = startTimestamp, + IsAsync = async + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void CommandExecuted( + [NotNull] this IDiagnosticsLogger diagnostics, + Guid connectionId, + [NotNull] DbCommand command, + [NotNull] string executeMethod, + [CanBeNull] object methodResult, + Guid instanceId, + long startTimestamp, + long currentTimestamp, + bool async = false) + { + var eventId = RelationalEventId.CommandExecuted; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Information)) + { + var logData = CreateCommandLogData(diagnostics, command, startTimestamp, currentTimestamp); + + var elapsedMilliseconds = DeriveTimespan(startTimestamp, currentTimestamp); + + var message = RelationalStrings.RelationalLoggerExecutedCommand( + string.Format(CultureInfo.InvariantCulture, "{0:N0}", elapsedMilliseconds), + logData.Parameters + // Interpolation okay here because value is always a string. + .Select(p => $"{p.Name}={p.FormatParameter()}") + .Join(), + logData.CommandType, + logData.CommandTimeout, + Environment.NewLine, + logData.CommandText); + + diagnostics.Logger.Log(LogLevel.Information, eventId, logData, null, (_, __) => message); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new RelationalDiagnosticSourceAfterMessage + { + ConnectionId = connectionId, + Command = command, + ExecuteMethod = executeMethod, + Result = methodResult, + InstanceId = instanceId, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp, + IsAsync = async + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void CommandError( + [NotNull] this IDiagnosticsLogger diagnostics, + Guid connectionId, + [NotNull] DbCommand command, + [NotNull] string executeMethod, + Guid instanceId, + long startTimestamp, + long currentTimestamp, + [NotNull] Exception exception, + bool async) + { + var eventId = RelationalEventId.CommandError; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Error)) + { + var logData = CreateCommandLogData(diagnostics, command, startTimestamp, currentTimestamp); + + var elapsedMilliseconds = DeriveTimespan(startTimestamp, currentTimestamp); + + var message = RelationalStrings.RelationalLoggerCommandFailed( + string.Format(CultureInfo.InvariantCulture, "{0:N0}", elapsedMilliseconds), + logData.Parameters + // Interpolation okay here because value is always a string. + .Select(p => $"{p.Name}={p.FormatParameter()}") + .Join(), + logData.CommandType, + logData.CommandTimeout, + Environment.NewLine, + logData.CommandText); + + diagnostics.Logger.Log(LogLevel.Error, eventId, logData, null, (_, __) => message); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new RelationalDiagnosticSourceAfterMessage + { + ConnectionId = connectionId, + Command = command, + ExecuteMethod = executeMethod, + InstanceId = instanceId, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp, + Exception = exception, + IsAsync = async + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// +#pragma warning disable 618 + private static DbCommandLogData CreateCommandLogData( +#pragma warning restore 618 + this IDiagnosticsLogger diagnostics, + DbCommand command, + long startTimestamp, + long currentTimestamp) + { + var logParameterValues + = command.Parameters.Count > 0 + && diagnostics.Logger.ShouldLogSensitiveData(diagnostics); + +#pragma warning disable 618 + var logData = new DbCommandLogData( +#pragma warning restore 618 + command.CommandText.TrimEnd(), + command.CommandType, + command.CommandTimeout, + command.Parameters + .Cast() + .Select( + p => new DbParameterLogData( + p.ParameterName, + logParameterValues ? p.Value : "?", + logParameterValues, + p.Direction, + p.DbType, + p.IsNullable, + p.Size, + p.Precision, + p.Scale)) + .ToList(), + DeriveTimespan(startTimestamp, currentTimestamp)); + + return logData; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ConnectionOpening( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + Guid instanceId, + long startTimestamp, + bool async) + { + var eventId = RelationalEventId.ConnectionOpening; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerOpeningConnection( + connection.DbConnection.Database, + connection.DbConnection.DataSource)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + InstanceId = instanceId, + Timestamp = startTimestamp, + IsAsync = async + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ConnectionOpened( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + Guid instanceId, + long startTimestamp, + long currentTimestamp, + bool async) + { + var eventId = RelationalEventId.ConnectionOpened; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerOpenedConnection( + connection.DbConnection.Database, + connection.DbConnection.DataSource)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + InstanceId = instanceId, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp, + IsAsync = async + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ConnectionClosing( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + Guid instanceId, + long startTimestamp, + bool async) + { + var eventId = RelationalEventId.ConnectionClosing; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerClosingConnection( + connection.DbConnection.Database, + connection.DbConnection.DataSource)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + InstanceId = instanceId, + Timestamp = startTimestamp, + IsAsync = async + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ConnectionClosed( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + Guid instanceId, + long startTimestamp, + long currentTimestamp, + bool async) + { + var eventId = RelationalEventId.ConnectionClosed; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerClosedConnection( + connection.DbConnection.Database, + connection.DbConnection.DataSource)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + InstanceId = instanceId, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp, + IsAsync = async + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ConnectionError( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + [NotNull] Exception exception, + Guid instanceId, + long startTimestamp, + long currentTimestamp, + bool async) + { + var eventId = RelationalEventId.ConnectionError; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Error)) + { + diagnostics.Logger.LogError( + eventId, + exception, + RelationalStrings.RelationalLoggerConnectionError( + connection.DbConnection.Database, + connection.DbConnection.DataSource)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + Exception = exception, + InstanceId = instanceId, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp, + IsAsync = async + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TransactionStarted( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + [NotNull] DbTransaction transaction) + { + var eventId = RelationalEventId.TransactionStarted; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerBeginningTransaction(transaction.IsolationLevel.ToString("G"))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + Transaction = transaction + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TransactionUsed( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + [NotNull] DbTransaction transaction) + { + var eventId = RelationalEventId.TransactionUsed; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerUsingTransaction(transaction.IsolationLevel.ToString("G"))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + Transaction = transaction + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TransactionCommitted( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + [NotNull] DbTransaction transaction, + long startTimestamp, + long currentTimestamp) + { + var eventId = RelationalEventId.TransactionCommitted; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerCommittingTransaction); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + Transaction = transaction, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TransactionRolledBack( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + [NotNull] DbTransaction transaction, + long startTimestamp, + long currentTimestamp) + { + var eventId = RelationalEventId.TransactionRolledBack; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerRollingbackTransaction); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + Transaction = transaction, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TransactionDisposed( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + [NotNull] DbTransaction transaction) + { + var eventId = RelationalEventId.TransactionDisposed; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.RelationalLoggerDisposingTransaction); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + Transaction = transaction + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TransactionError( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + [NotNull] DbTransaction transaction, + [NotNull] string action, + [NotNull] Exception exception, + long startTimestamp, + long currentTimestamp) + { + var eventId = RelationalEventId.TransactionError; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Error)) + { + diagnostics.Logger.LogError( + eventId, + exception, + RelationalStrings.RelationalLoggerTransactionError); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + Transaction = transaction, + Exception = exception, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void AmbientTransactionWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection) + { + var eventId = RelationalEventId.AmbientTransactionWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalStrings.AmbientTransaction); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void DataReaderDisposing( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IRelationalConnection connection, + [NotNull] DbDataReader dataReader, + int recordsAffected, + long startTimestamp, + long currentTimestamp) + { + var eventId = RelationalEventId.DataReaderDisposing; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.DisposingDataReader); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + DataReader = dataReader, + RecordsAffected = recordsAffected, + Timestamp = currentTimestamp, + Duration = currentTimestamp - startTimestamp + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrateUsingConnection( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IMigrator migrator, + [NotNull] IRelationalConnection connection, + [CanBeNull] string targetMigration) + { + var eventId = RelationalEventId.MigrateUsingConnection; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + var dbConnection = connection.DbConnection; + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.UsingConnection(dbConnection.Database, dbConnection.DataSource)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Migrator = migrator, + Connection = connection.DbConnection, + ConnectionId = connection.ConnectionId, + TargetMigration = targetMigration + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationReverting( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IMigrator migrator, + [NotNull] Migration migration, + [CanBeNull] string targetMigration) + { + var eventId = RelationalEventId.MigrationReverting; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Information)) + { + diagnostics.Logger.LogInformation( + eventId, + RelationalStrings.RevertingMigration(migration.GetId())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Migrator = migrator, + Migration = migration, + TargetMigration = targetMigration + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationApplying( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IMigrator migrator, + [NotNull] Migration migration, + [CanBeNull] string targetMigration) + { + var eventId = RelationalEventId.MigrationApplying; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Information)) + { + diagnostics.Logger.LogInformation( + eventId, + RelationalStrings.ApplyingMigration(migration.GetId())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Migrator = migrator, + Migration = migration, + TargetMigration = targetMigration + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationGeneratingDownScript( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IMigrator migrator, + [NotNull] Migration migration, + [CanBeNull] string fromMigration, + [CanBeNull] string toMigration, + bool idempotent) + { + var eventId = RelationalEventId.MigrationGeneratingDownScript; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.GeneratingDown(migration.GetId())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Migrator = migrator, + Migration = migration, + FromMigration= fromMigration, + ToMigration = toMigration, + Idempotent = idempotent + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void MigrationGeneratingUpScript( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IMigrator migrator, + [NotNull] Migration migration, + [CanBeNull] string fromMigration, + [CanBeNull] string toMigration, + bool idempotent) + { + var eventId = RelationalEventId.MigrationGeneratingUpScript; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + RelationalStrings.GeneratingUp(migration.GetId())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Migrator = migrator, + Migration = migration, + FromMigration = fromMigration, + ToMigration = toMigration, + Idempotent = idempotent + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void QueryClientEvaluationWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] QueryModel queryModel, + [NotNull] object expression) + { + var eventId = RelationalEventId.QueryClientEvaluationWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalStrings.ClientEvalWarning(expression.ToString())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + QueryModel = queryModel, + Expression = expression + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void QueryPossibleUnintendedUseOfEqualsWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] MethodCallExpression methodCallExpression, + [NotNull] Expression argument) + { + var eventId = RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalStrings.PossibleUnintendedUseOfEquals( + methodCallExpression.Object.ToString(), + argument)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + MethodCallExpression = methodCallExpression, + Argument = argument + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ModelValidationKeyDefaultValueWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IProperty property) + { + var eventId = RelationalEventId.ModelValidationKeyDefaultValueWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + RelationalStrings.KeyHasDefaultValue(property.Name, property.DeclaringEntityType.DisplayName())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, + new + { + Property = property + }); + } + } + + private static readonly double _timestampToMilliseconds + = (double)TimeSpan.TicksPerSecond / (Stopwatch.Frequency * TimeSpan.TicksPerMillisecond); + + private static long DeriveTimespan(long startTimestamp, long currentTimestamp) + => (long)((currentTimestamp - startTimestamp) * _timestampToMilliseconds); + } +} diff --git a/src/EFCore.Relational/Migrations/Internal/Migrator.cs b/src/EFCore.Relational/Migrations/Internal/Migrator.cs index ea163390fed..6e86ae19372 100644 --- a/src/EFCore.Relational/Migrations/Internal/Migrator.cs +++ b/src/EFCore.Relational/Migrations/Internal/Migrator.cs @@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Migrations.Internal @@ -30,7 +29,7 @@ public class Migrator : IMigrator private readonly IMigrationCommandExecutor _migrationCommandExecutor; private readonly IRelationalConnection _connection; private readonly ISqlGenerationHelper _sqlGenerationHelper; - private readonly IInterceptingLogger _logger; + private readonly IDiagnosticsLogger _logger; private readonly string _activeProvider; /// @@ -46,7 +45,7 @@ public Migrator( [NotNull] IMigrationCommandExecutor migrationCommandExecutor, [NotNull] IRelationalConnection connection, [NotNull] ISqlGenerationHelper sqlGenerationHelper, - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, [NotNull] IDatabaseProvider databaseProvider) { Check.NotNull(migrationsAssembly, nameof(migrationsAssembly)); @@ -78,10 +77,7 @@ public Migrator( /// public virtual void Migrate(string targetMigration = null) { - var connection = _connection.DbConnection; - _logger.LogDebug( - RelationalEventId.MigrateUsingConnection, - () => RelationalStrings.UsingConnection(connection.Database, connection.DataSource)); + _logger.MigrateUsingConnection(this, _connection, targetMigration); if (!_historyRepository.Exists()) { @@ -110,10 +106,7 @@ public virtual async Task MigrateAsync( string targetMigration = null, CancellationToken cancellationToken = default(CancellationToken)) { - var connection = _connection.DbConnection; - _logger.LogDebug( - RelationalEventId.MigrateUsingConnection, - () => RelationalStrings.UsingConnection(connection.Database, connection.DataSource)); + _logger.MigrateUsingConnection(this, _connection, targetMigration); if (!await _historyRepository.ExistsAsync(cancellationToken)) { @@ -155,9 +148,7 @@ private IEnumerable>> GetMigrationCommandLi var index = i; yield return () => { - _logger.LogInformation( - RelationalEventId.RevertingMigration, - () => RelationalStrings.RevertingMigration(migration.GetId())); + _logger.MigrationReverting(this, migration, targetMigration); return GenerateDownSql( migration, @@ -171,9 +162,7 @@ private IEnumerable>> GetMigrationCommandLi { yield return () => { - _logger.LogInformation( - RelationalEventId.ApplyingMigration, - () => RelationalStrings.ApplyingMigration(migration.GetId())); + _logger.MigrationApplying(this, migration, targetMigration); return GenerateUpSql(migration); }; @@ -276,9 +265,7 @@ public virtual string GenerateScript( ? migrationsToRevert[i + 1] : null; - _logger.LogDebug( - RelationalEventId.GeneratingMigrationDownScript, - () => RelationalStrings.GeneratingDown(migration.GetId())); + _logger.MigrationGeneratingDownScript(this, migration, fromMigration, toMigration, idempotent); foreach (var command in GenerateDownSql(migration, previousMigration)) { @@ -302,9 +289,7 @@ public virtual string GenerateScript( foreach (var migration in migrationsToApply) { - _logger.LogDebug( - RelationalEventId.GeneratingMigrationUpScript, - () => RelationalStrings.GeneratingUp(migration.GetId())); + _logger.MigrationGeneratingUpScript(this, migration, fromMigration, toMigration, idempotent); foreach (var command in GenerateUpSql(migration)) { diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index eac69264891..899df3d6595 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -95,6 +95,14 @@ public static string RelationalLoggerOpeningConnection([CanBeNull] object databa GetString("RelationalLoggerOpeningConnection", nameof(database), nameof(server)), database, server); + /// + /// Opened connection to database '{database}' on server '{server}'. + /// + public static string RelationalLoggerOpenedConnection([CanBeNull] object database, [CanBeNull] object server) + => string.Format( + GetString("RelationalLoggerOpenedConnection", nameof(database), nameof(server)), + database, server); + /// /// Closing connection to database '{database}' on server '{server}'. /// @@ -103,6 +111,22 @@ public static string RelationalLoggerClosingConnection([CanBeNull] object databa GetString("RelationalLoggerClosingConnection", nameof(database), nameof(server)), database, server); + /// + /// Closed connection to database '{database}' on server '{server}'. + /// + public static string RelationalLoggerClosedConnection([CanBeNull] object database, [CanBeNull] object server) + => string.Format( + GetString("RelationalLoggerClosedConnection", nameof(database), nameof(server)), + database, server); + + /// + /// An error occurred using the connection to database '{database}' on server '{server}'. + /// + public static string RelationalLoggerConnectionError([CanBeNull] object database, [CanBeNull] object server) + => string.Format( + GetString("RelationalLoggerConnectionError", nameof(database), nameof(server)), + database, server); + /// /// Beginning transaction with isolation level '{isolationLevel}'. /// @@ -111,6 +135,14 @@ public static string RelationalLoggerBeginningTransaction([CanBeNull] object iso GetString("RelationalLoggerBeginningTransaction", nameof(isolationLevel)), isolationLevel); + /// + /// Using an existing transaction with isolation level '{isolationLevel}'. + /// + public static string RelationalLoggerUsingTransaction([CanBeNull] object isolationLevel) + => string.Format( + GetString("RelationalLoggerUsingTransaction", nameof(isolationLevel)), + isolationLevel); + /// /// Committing transaction. /// @@ -123,6 +155,24 @@ public static string RelationalLoggerCommittingTransaction public static string RelationalLoggerRollingbackTransaction => GetString("RelationalLoggerRollingbackTransaction"); + /// + /// Disposing transaction. + /// + public static string RelationalLoggerDisposingTransaction + => GetString("RelationalLoggerDisposingTransaction"); + + /// + /// An error occurred using a transaction. + /// + public static string RelationalLoggerTransactionError + => GetString("RelationalLoggerTransactionError"); + + /// + /// A data reader was disposed. + /// + public static string DisposingDataReader + => GetString("DisposingDataReader"); + /// /// Invalid type for sequence. Valid types are 'Int64' (the default), 'Int32', 'Int16', and 'Byte'. /// @@ -201,14 +251,6 @@ public static string PossibleUnintendedUseOfEquals([CanBeNull] object left, [Can GetString("PossibleUnintendedUseOfEquals", nameof(left), nameof(right)), left, right); - /// - /// Linq translation for method '{function}' used by this database provider can return incorrect results when the value argument contains wildcard characters (e.g. '%' or '_'). - /// - public static string PossibleIncorrectResultsUsingLikeOperator([CanBeNull] object function) - => string.Format( - GetString("PossibleIncorrectResultsUsingLikeOperator", nameof(function)), - function); - /// /// The Include operation is not supported when calling a stored procedure. /// @@ -343,6 +385,14 @@ public static string ParameterNotObjectArray([CanBeNull] object parameter) GetString("ParameterNotObjectArray", nameof(parameter)), parameter); + /// + /// Executing DbCommand [Parameters=[{parameters}], CommandType='{commandType}', CommandTimeout='{commandTimeout}']{newLine}{commandText} + /// + public static string RelationalLoggerExecutingCommand([CanBeNull] object parameters, [CanBeNull] object commandType, [CanBeNull] object commandTimeout, [CanBeNull] object newLine, [CanBeNull] object commandText) + => string.Format( + GetString("RelationalLoggerExecutingCommand", nameof(parameters), nameof(commandType), nameof(commandTimeout), nameof(newLine), nameof(commandText)), + parameters, commandType, commandTimeout, newLine, commandText); + /// /// Executed DbCommand ({elapsed}ms) [Parameters=[{parameters}], CommandType='{commandType}', CommandTimeout='{commandTimeout}']{newLine}{commandText} /// @@ -351,6 +401,14 @@ public static string RelationalLoggerExecutedCommand([CanBeNull] object elapsed, GetString("RelationalLoggerExecutedCommand", nameof(elapsed), nameof(parameters), nameof(commandType), nameof(commandTimeout), nameof(newLine), nameof(commandText)), elapsed, parameters, commandType, commandTimeout, newLine, commandText); + /// + /// Failed executing DbCommand ({elapsed}ms) [Parameters=[{parameters}], CommandType='{commandType}', CommandTimeout='{commandTimeout}']{newLine}{commandText} + /// + public static string RelationalLoggerCommandFailed([CanBeNull] object elapsed, [CanBeNull] object parameters, [CanBeNull] object commandType, [CanBeNull] object commandTimeout, [CanBeNull] object newLine, [CanBeNull] object commandText) + => string.Format( + GetString("RelationalLoggerCommandFailed", nameof(elapsed), nameof(parameters), nameof(commandType), nameof(commandTimeout), nameof(newLine), nameof(commandText)), + elapsed, parameters, commandType, commandTimeout, newLine, commandText); + /// /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different data types ('{dataType1}' and '{dataType2}'). /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index eda52f7c6ff..63496bba270 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -153,18 +153,39 @@ Opening connection to database '{database}' on server '{server}'. + + Opened connection to database '{database}' on server '{server}'. + Closing connection to database '{database}' on server '{server}'. + + Closed connection to database '{database}' on server '{server}'. + + + An error occurred using the connection to database '{database}' on server '{server}'. + Beginning transaction with isolation level '{isolationLevel}'. + + Using an existing transaction with isolation level '{isolationLevel}'. + Committing transaction. Rolling back transaction. + + Disposing transaction. + + + An error occurred using a transaction. + + + A data reader was disposed. + Invalid type for sequence. Valid types are 'Int64' (the default), 'Int32', 'Int16', and 'Byte'. @@ -198,9 +219,6 @@ Possible unintended use of method Equals(object) for arguments of different types: '{left}', '{right}'. This comparison will always return 'false'. - - Linq translation for method '{function}' used by this database provider can return incorrect results when the value argument contains wildcard characters (e.g. '%' or '_'). - The Include operation is not supported when calling a stored procedure. @@ -253,9 +271,15 @@ Cannot use the value provided for parameter '{parameter}' because it isn't assignable to type object[]. + + Executing DbCommand [Parameters=[{parameters}], CommandType='{commandType}', CommandTimeout='{commandTimeout}']{newLine}{commandText} + Executed DbCommand ({elapsed}ms) [Parameters=[{parameters}], CommandType='{commandType}', CommandTimeout='{commandTimeout}']{newLine}{commandText} + + Failed executing DbCommand ({elapsed}ms) [Parameters=[{parameters}], CommandType='{commandType}', CommandTimeout='{commandTimeout}']{newLine}{commandText} + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different data types ('{dataType1}' and '{dataType2}'). diff --git a/src/EFCore.Relational/Query/ExpressionTranslators/Internal/EqualsTranslator.cs b/src/EFCore.Relational/Query/ExpressionTranslators/Internal/EqualsTranslator.cs index 1bb71ea3bcc..1df9472c004 100644 --- a/src/EFCore.Relational/Query/ExpressionTranslators/Internal/EqualsTranslator.cs +++ b/src/EFCore.Relational/Query/ExpressionTranslators/Internal/EqualsTranslator.cs @@ -6,7 +6,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal @@ -17,13 +16,13 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal /// public class EqualsTranslator : IMethodCallTranslator { - private readonly IInterceptingLogger _logger; + private readonly IDiagnosticsLogger _logger; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public EqualsTranslator([NotNull] IInterceptingLogger logger) + public EqualsTranslator([NotNull] IDiagnosticsLogger logger) { Check.NotNull(logger, nameof(logger)); @@ -59,11 +58,7 @@ public virtual Expression Translate(MethodCallExpression methodCallExpression) Expression.Convert(argument, unwrappedArgumentType)); } - _logger.LogWarning( - RelationalEventId.PossibleUnintendedUseOfEqualsWarning, - () => RelationalStrings.PossibleUnintendedUseOfEquals( - methodCallExpression.Object.ToString(), - argument.ToString())); + _logger.QueryPossibleUnintendedUseOfEqualsWarning(methodCallExpression, argument); // Equals(object) always returns false if when comparing objects of different types return Expression.Constant(false); diff --git a/src/EFCore.Relational/Query/ExpressionTranslators/RelationalCompositeMethodCallTranslatorDependencies.cs b/src/EFCore.Relational/Query/ExpressionTranslators/RelationalCompositeMethodCallTranslatorDependencies.cs index 6aee6599dc4..81d24b8a588 100644 --- a/src/EFCore.Relational/Query/ExpressionTranslators/RelationalCompositeMethodCallTranslatorDependencies.cs +++ b/src/EFCore.Relational/Query/ExpressionTranslators/RelationalCompositeMethodCallTranslatorDependencies.cs @@ -39,7 +39,7 @@ public sealed class RelationalCompositeMethodCallTranslatorDependencies /// /// /// A logger. - public RelationalCompositeMethodCallTranslatorDependencies([NotNull] IInterceptingLogger logger) + public RelationalCompositeMethodCallTranslatorDependencies([NotNull] IDiagnosticsLogger logger) { Logger = logger; } @@ -47,14 +47,14 @@ public RelationalCompositeMethodCallTranslatorDependencies([NotNull] IIntercepti /// /// The logger. /// - public IInterceptingLogger Logger { get; } + public IDiagnosticsLogger Logger { get; } /// /// Clones this dependency parameter object with one service replaced. /// /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. - public RelationalCompositeMethodCallTranslatorDependencies With([NotNull] IInterceptingLogger logger) + public RelationalCompositeMethodCallTranslatorDependencies With([NotNull] IDiagnosticsLogger logger) => new RelationalCompositeMethodCallTranslatorDependencies(logger); } } diff --git a/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs index 90e937fe4df..a8037635e5c 100644 --- a/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs @@ -340,7 +340,7 @@ public override void VisitAdditionalFromClause( if (RequiresClientSelectMany) { - WarnClientEval(fromClause); + WarnClientEval(queryModel, fromClause); } } @@ -399,7 +399,7 @@ public override void VisitJoinClause( if (RequiresClientJoin) { - WarnClientEval(joinClause); + WarnClientEval(queryModel, joinClause); } } @@ -466,7 +466,7 @@ public override void VisitGroupJoinClause( if (RequiresClientJoin) { - WarnClientEval(groupJoinClause.JoinClause); + WarnClientEval(queryModel, groupJoinClause.JoinClause); } } @@ -800,7 +800,7 @@ var sqlTranslatingExpressionVisitor if (RequiresClientFilter) { - WarnClientEval(whereClause.Predicate); + WarnClientEval(queryModel, whereClause.Predicate); base.VisitWhereClause(whereClause, queryModel, index); } @@ -876,7 +876,7 @@ var sqlOrderingExpression if (RequiresClientOrderBy) { - WarnClientEval(orderByClause); + WarnClientEval(queryModel, orderByClause); base.VisitOrderByClause(orderByClause, queryModel, index); } @@ -978,7 +978,7 @@ public override void VisitResultOperator(ResultOperatorBase resultOperator, Quer if (RequiresClientResultOperator) { - WarnClientEval(resultOperator); + WarnClientEval(queryModel, resultOperator); } } @@ -1004,14 +1004,15 @@ var typeIsExpressionTranslatingVisitor /// /// Generated a client-eval warning /// + /// The query model /// The expression being client-eval'd. - protected virtual void WarnClientEval([NotNull] object expression) + protected virtual void WarnClientEval( + [NotNull] QueryModel queryModel, + [NotNull] object expression) { Check.NotNull(expression, nameof(expression)); - QueryCompilationContext.Logger.LogWarning( - RelationalEventId.QueryClientEvaluationWarning, - () => RelationalStrings.ClientEvalWarning(expression)); + QueryCompilationContext.Logger.QueryClientEvaluationWarning(queryModel, expression); } private class TypeIsExpressionTranslatingVisitor : ExpressionVisitorBase diff --git a/src/EFCore.Relational/Storage/Internal/RelationalCommand.cs b/src/EFCore.Relational/Storage/Internal/RelationalCommand.cs index a9c3b856caa..b071e5fa9d0 100644 --- a/src/EFCore.Relational/Storage/Internal/RelationalCommand.cs +++ b/src/EFCore.Relational/Storage/Internal/RelationalCommand.cs @@ -25,18 +25,18 @@ public class RelationalCommand : IRelationalCommand /// directly from your code. This API may change or be removed in future releases. /// public RelationalCommand( - [NotNull] IInterceptingLogger logger, - [NotNull] DiagnosticSource diagnosticSource, + [NotNull] IDiagnosticsLogger sqlLogger, + [NotNull] IDiagnosticsLogger readerLogger, [NotNull] string commandText, [NotNull] IReadOnlyList parameters) { - Check.NotNull(logger, nameof(logger)); - Check.NotNull(diagnosticSource, nameof(diagnosticSource)); + Check.NotNull(sqlLogger, nameof(sqlLogger)); + Check.NotNull(readerLogger, nameof(readerLogger)); Check.NotNull(commandText, nameof(commandText)); Check.NotNull(parameters, nameof(parameters)); - Logger = logger; - DiagnosticSource = diagnosticSource; + SqlLogger = sqlLogger; + ReaderLogger = readerLogger; CommandText = commandText; Parameters = parameters; } @@ -45,13 +45,13 @@ public RelationalCommand( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected virtual IInterceptingLogger Logger { get; } + protected virtual IDiagnosticsLogger SqlLogger { get; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected virtual DiagnosticSource DiagnosticSource { get; } + protected virtual IDiagnosticsLogger ReaderLogger { get; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -188,7 +188,7 @@ protected virtual object Execute( var startTimestamp = Stopwatch.GetTimestamp(); var instanceId = Guid.NewGuid(); - DiagnosticSource.WriteCommandBefore( + SqlLogger.CommandExecuting( connection.ConnectionId, dbCommand, executeMethod, @@ -228,7 +228,7 @@ protected virtual object Execute( connection, dbCommand, dbCommand.ExecuteReader(), - DiagnosticSource); + ReaderLogger); } catch { @@ -247,9 +247,7 @@ protected virtual object Execute( var currentTimestamp = Stopwatch.GetTimestamp(); - Logger.LogCommandExecuted(dbCommand, startTimestamp, currentTimestamp); - - DiagnosticSource.WriteCommandAfter( + SqlLogger.CommandExecuted( connection.ConnectionId, dbCommand, executeMethod, @@ -267,9 +265,7 @@ protected virtual object Execute( { var currentTimestamp = Stopwatch.GetTimestamp(); - Logger.LogCommandExecuted(dbCommand, startTimestamp, currentTimestamp); - - DiagnosticSource.WriteCommandError( + SqlLogger.CommandError( connection.ConnectionId, dbCommand, executeMethod, @@ -312,7 +308,7 @@ protected virtual async Task ExecuteAsync( var startTimestamp = Stopwatch.GetTimestamp(); var instanceId = Guid.NewGuid(); - DiagnosticSource.WriteCommandBefore( + SqlLogger.CommandExecuting( connection.ConnectionId, dbCommand, executeMethod, @@ -350,8 +346,8 @@ protected virtual async Task ExecuteAsync( result = new RelationalDataReader( connection, dbCommand, - await dbCommand.ExecuteReaderAsync(cancellationToken), - DiagnosticSource); + await dbCommand.ExecuteReaderAsync(cancellationToken), + ReaderLogger); } catch { @@ -370,9 +366,7 @@ await dbCommand.ExecuteReaderAsync(cancellationToken), var currentTimestamp = Stopwatch.GetTimestamp(); - Logger.LogCommandExecuted(dbCommand, startTimestamp, currentTimestamp); - - DiagnosticSource.WriteCommandAfter( + SqlLogger.CommandExecuted( connection.ConnectionId, dbCommand, executeMethod, @@ -391,9 +385,7 @@ await dbCommand.ExecuteReaderAsync(cancellationToken), { var currentTimestamp = Stopwatch.GetTimestamp(); - Logger.LogCommandExecuted(dbCommand, startTimestamp, currentTimestamp); - - DiagnosticSource.WriteCommandError( + SqlLogger.CommandError( connection.ConnectionId, dbCommand, executeMethod, diff --git a/src/EFCore.Relational/Storage/Internal/RelationalCommandBuilder.cs b/src/EFCore.Relational/Storage/Internal/RelationalCommandBuilder.cs index 607630ec8e2..c12fed03dc5 100644 --- a/src/EFCore.Relational/Storage/Internal/RelationalCommandBuilder.cs +++ b/src/EFCore.Relational/Storage/Internal/RelationalCommandBuilder.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Diagnostics; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; @@ -16,8 +15,8 @@ namespace Microsoft.EntityFrameworkCore.Storage.Internal /// public class RelationalCommandBuilder : IRelationalCommandBuilder { - private readonly IInterceptingLogger _logger; - private readonly DiagnosticSource _diagnosticSource; + private readonly IDiagnosticsLogger _sqlLogger; + private readonly IDiagnosticsLogger _readerLogger; private readonly IndentedStringBuilder _commandTextBuilder = new IndentedStringBuilder(); @@ -26,16 +25,16 @@ public class RelationalCommandBuilder : IRelationalCommandBuilder /// directly from your code. This API may change or be removed in future releases. /// public RelationalCommandBuilder( - [NotNull] IInterceptingLogger logger, - [NotNull] DiagnosticSource diagnosticSource, + [NotNull] IDiagnosticsLogger sqlLogger, + [NotNull] IDiagnosticsLogger readerLogger, [NotNull] IRelationalTypeMapper typeMapper) { - Check.NotNull(logger, nameof(logger)); - Check.NotNull(diagnosticSource, nameof(diagnosticSource)); + Check.NotNull(sqlLogger, nameof(sqlLogger)); + Check.NotNull(readerLogger, nameof(readerLogger)); Check.NotNull(typeMapper, nameof(typeMapper)); - _logger = logger; - _diagnosticSource = diagnosticSource; + _sqlLogger = sqlLogger; + _readerLogger = readerLogger; ParameterBuilder = new RelationalParameterBuilder(typeMapper); } @@ -54,8 +53,8 @@ IndentedStringBuilder IInfrastructure.Instance /// public virtual IRelationalCommand Build() => BuildCore( - _logger, - _diagnosticSource, + _sqlLogger, + _readerLogger, _commandTextBuilder.ToString(), ParameterBuilder.Parameters); @@ -64,12 +63,12 @@ public virtual IRelationalCommand Build() /// directly from your code. This API may change or be removed in future releases. /// protected virtual IRelationalCommand BuildCore( - [NotNull] IInterceptingLogger logger, - [NotNull] DiagnosticSource diagnosticSource, + [NotNull] IDiagnosticsLogger sqlLogger, + [NotNull] IDiagnosticsLogger readerLogger, [NotNull] string commandText, [NotNull] IReadOnlyList parameters) => new RelationalCommand( - logger, diagnosticSource, commandText, parameters); + sqlLogger, readerLogger, commandText, parameters); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore.Relational/Storage/Internal/RelationalCommandBuilderFactory.cs b/src/EFCore.Relational/Storage/Internal/RelationalCommandBuilderFactory.cs index b67d129383c..eda23e69968 100644 --- a/src/EFCore.Relational/Storage/Internal/RelationalCommandBuilderFactory.cs +++ b/src/EFCore.Relational/Storage/Internal/RelationalCommandBuilderFactory.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Diagnostics; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; @@ -14,8 +13,8 @@ namespace Microsoft.EntityFrameworkCore.Storage.Internal /// public class RelationalCommandBuilderFactory : IRelationalCommandBuilderFactory { - private readonly IInterceptingLogger _logger; - private readonly DiagnosticSource _diagnosticSource; + private readonly IDiagnosticsLogger _sqlLogger; + private readonly IDiagnosticsLogger _readerLogger; private readonly IRelationalTypeMapper _typeMapper; /// @@ -23,16 +22,16 @@ public class RelationalCommandBuilderFactory : IRelationalCommandBuilderFactory /// directly from your code. This API may change or be removed in future releases. /// public RelationalCommandBuilderFactory( - [NotNull] IInterceptingLogger logger, - [NotNull] DiagnosticSource diagnosticSource, + [NotNull] IDiagnosticsLogger sqlLogger, + [NotNull] IDiagnosticsLogger readerLogger, [NotNull] IRelationalTypeMapper typeMapper) { - Check.NotNull(logger, nameof(logger)); - Check.NotNull(diagnosticSource, nameof(diagnosticSource)); + Check.NotNull(sqlLogger, nameof(sqlLogger)); + Check.NotNull(readerLogger, nameof(readerLogger)); Check.NotNull(typeMapper, nameof(typeMapper)); - _logger = logger; - _diagnosticSource = diagnosticSource; + _sqlLogger = sqlLogger; + _readerLogger = readerLogger; _typeMapper = typeMapper; } @@ -40,19 +39,19 @@ public RelationalCommandBuilderFactory( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual IRelationalCommandBuilder Create() => CreateCore(_logger, _diagnosticSource, _typeMapper); + public virtual IRelationalCommandBuilder Create() => CreateCore(_sqlLogger, _readerLogger, _typeMapper); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// protected virtual IRelationalCommandBuilder CreateCore( - [NotNull] IInterceptingLogger logger, - [NotNull] DiagnosticSource diagnosticSource, + [NotNull] IDiagnosticsLogger sqlLogger, + [NotNull] IDiagnosticsLogger readerLogger, [NotNull] IRelationalTypeMapper relationalTypeMapper) => new RelationalCommandBuilder( - logger, - diagnosticSource, + sqlLogger, + readerLogger, relationalTypeMapper); } } diff --git a/src/EFCore.Relational/Storage/Internal/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Storage/Internal/RelationalLoggerExtensions.cs deleted file mode 100644 index b7f533cd6ca..00000000000 --- a/src/EFCore.Relational/Storage/Internal/RelationalLoggerExtensions.cs +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Data.Common; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Utilities; -using Microsoft.Extensions.Logging; - -namespace Microsoft.EntityFrameworkCore.Storage.Internal -{ - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static class RelationalLoggerExtensions - { - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static void LogCommandExecuted( - [NotNull] this IInterceptingLogger logger, - [NotNull] DbCommand command, - long startTimestamp, - long currentTimestamp) - { - Check.NotNull(logger, nameof(logger)); - Check.NotNull(command, nameof(command)); - - if (logger.IsEnabled(LogLevel.Information)) - { - var logParameterValues - = command.Parameters.Count > 0 - && logger.LogSensitiveData; - -#pragma warning disable 618 - var logData = new DbCommandLogData( -#pragma warning restore 618 - command.CommandText.TrimEnd(), - command.CommandType, - command.CommandTimeout, - command.Parameters - .Cast() - .Select( - p => new DbParameterLogData( - p.ParameterName, - logParameterValues ? p.Value : "?", - logParameterValues, - p.Direction, - p.DbType, - p.IsNullable, - p.Size, - p.Precision, - p.Scale)) - .ToList(), - DeriveTimespan(startTimestamp, currentTimestamp)); - - logger.Log( - LogLevel.Information, - (int)RelationalEventId.ExecutedCommand, - logData, - null, - (state, _) => - { - var elapsedMilliseconds = DeriveTimespan(startTimestamp, currentTimestamp); - - return RelationalStrings.RelationalLoggerExecutedCommand( - string.Format(CultureInfo.InvariantCulture, "{0:N0}", elapsedMilliseconds), - state.Parameters - // Interpolation okay here because value is always a string. - .Select(p => $"{p.Name}={p.FormatParameter()}") - .Join(), - state.CommandType, - state.CommandTimeout, - Environment.NewLine, - state.CommandText); - }); - } - } - - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static void LogDebug( - [NotNull] this ILogger logger, - RelationalEventId eventId, - [NotNull] Func formatter) - { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.Log(LogLevel.Debug, (int)eventId, null, null, (_, __) => formatter()); - } - } - - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static void LogDebug( - [NotNull] this ILogger logger, - RelationalEventId eventId, - [CanBeNull] TState state, - [NotNull] Func formatter) - { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.Log(LogLevel.Debug, (int)eventId, state, null, (s, __) => formatter(s)); - } - } - - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static void LogWarning( - [NotNull] this ILogger logger, - RelationalEventId eventId, - [NotNull] Func formatter) - { - // Always call Log for Warnings because Warnings as Errors should work even - // if LogLevel.Warning is not enabled. - logger.Log(LogLevel.Warning, (int)eventId, eventId, null, (_, __) => formatter()); - } - - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static void LogInformation( - [NotNull] this ILogger logger, - RelationalEventId eventId, - [NotNull] Func formatter) - { - if (logger.IsEnabled(LogLevel.Information)) - { - logger.Log(LogLevel.Information, (int)eventId, null, null, (_, __) => formatter()); - } - } - - private static readonly double TimestampToMilliseconds = (double)TimeSpan.TicksPerSecond / (Stopwatch.Frequency * TimeSpan.TicksPerMillisecond); - - private static long DeriveTimespan(long startTimestamp, long currentTimestamp) - => (long)((currentTimestamp - startTimestamp) * TimestampToMilliseconds); - } -} diff --git a/src/EFCore.Relational/Storage/RelationalConnection.cs b/src/EFCore.Relational/Storage/RelationalConnection.cs index 49ebc30f84a..539ef7b2c05 100644 --- a/src/EFCore.Relational/Storage/RelationalConnection.cs +++ b/src/EFCore.Relational/Storage/RelationalConnection.cs @@ -90,11 +90,6 @@ protected RelationalConnection([NotNull] RelationalConnectionDependencies depend /// The connection. protected abstract DbConnection CreateDbConnection(); - /// - /// Gets the diagnostic source. - /// - protected virtual DiagnosticSource DiagnosticSource => Dependencies.DiagnosticSource; - /// /// Gets the connection string for the database. /// @@ -189,19 +184,17 @@ public virtual async Task BeginTransactionAsync( private IDbContextTransaction BeginTransactionWithNoPreconditions(IsolationLevel isolationLevel) { - Dependencies.TransactionLogger.LogDebug( - RelationalEventId.BeginningTransaction, - isolationLevel, - il => RelationalStrings.RelationalLoggerBeginningTransaction(il.ToString("G"))); + var dbTransaction = DbConnection.BeginTransaction(isolationLevel); CurrentTransaction = new RelationalTransaction( this, - DbConnection.BeginTransaction(isolationLevel), + dbTransaction, Dependencies.TransactionLogger, - DiagnosticSource, transactionOwned: true); + Dependencies.TransactionLogger.TransactionStarted(this, dbTransaction); + return CurrentTransaction; } @@ -231,8 +224,9 @@ public virtual IDbContextTransaction UseTransaction(DbTransaction transaction) this, transaction, Dependencies.TransactionLogger, - DiagnosticSource, transactionOwned: false); + + Dependencies.TransactionLogger.TransactionUsed(this, transaction); } return CurrentTransaction; @@ -281,22 +275,10 @@ public virtual bool Open() if (_connection.Value.State != ConnectionState.Open) { - Dependencies.ConnectionLogger.LogDebug( - RelationalEventId.OpeningConnection, - new - { - _connection.Value.Database, - _connection.Value.DataSource - }, - state => - RelationalStrings.RelationalLoggerOpeningConnection( - state.Database, - state.DataSource)); - var startTimestamp = Stopwatch.GetTimestamp(); var instanceId = Guid.NewGuid(); - DiagnosticSource.WriteConnectionOpening(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionOpening( + this, instanceId, startTimestamp, async: false); @@ -307,8 +289,8 @@ public virtual bool Open() wasOpened = true; var currentTimestamp = Stopwatch.GetTimestamp(); - DiagnosticSource.WriteConnectionOpened(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionOpened( + this, instanceId, startTimestamp, currentTimestamp, @@ -317,8 +299,8 @@ public virtual bool Open() catch (Exception e) { var currentTimestamp = Stopwatch.GetTimestamp(); - DiagnosticSource.WriteConnectionError(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionError( + this, e, instanceId, startTimestamp, @@ -364,22 +346,10 @@ public virtual bool Open() if (_connection.Value.State != ConnectionState.Open) { - Dependencies.ConnectionLogger.LogDebug( - RelationalEventId.OpeningConnection, - new - { - _connection.Value.Database, - _connection.Value.DataSource - }, - state => - RelationalStrings.RelationalLoggerOpeningConnection( - state.Database, - state.DataSource)); - var startTimestamp = Stopwatch.GetTimestamp(); var instanceId = Guid.NewGuid(); - DiagnosticSource.WriteConnectionOpening(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionOpening( + this, instanceId, startTimestamp, async: true); @@ -390,8 +360,8 @@ public virtual bool Open() wasOpened = true; var currentTimestamp = Stopwatch.GetTimestamp(); - DiagnosticSource.WriteConnectionOpened(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionOpened( + this, instanceId, startTimestamp, currentTimestamp, @@ -400,8 +370,8 @@ public virtual bool Open() catch (Exception e) { var currentTimestamp = Stopwatch.GetTimestamp(); - DiagnosticSource.WriteConnectionError(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionError( + this, e, instanceId, startTimestamp, @@ -429,9 +399,7 @@ private void CheckForAmbientTransactions() #if NET46 if (Transaction.Current != null) { - Dependencies.TransactionLogger.LogWarning( - RelationalEventId.AmbientTransactionWarning, - () => RelationalStrings.AmbientTransaction); + Dependencies.TransactionLogger.AmbientTransactionWarning(this); } #elif NETSTANDARD1_3 #else @@ -453,22 +421,10 @@ public virtual bool Close() { if (_connection.Value.State != ConnectionState.Closed) { - Dependencies.ConnectionLogger.LogDebug( - RelationalEventId.ClosingConnection, - new - { - _connection.Value.Database, - _connection.Value.DataSource - }, - state => - RelationalStrings.RelationalLoggerClosingConnection( - state.Database, - state.DataSource)); - var startTimestamp = Stopwatch.GetTimestamp(); var instanceId = Guid.NewGuid(); - DiagnosticSource.WriteConnectionClosing(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionClosing( + this, instanceId, startTimestamp, async: false); @@ -479,8 +435,8 @@ public virtual bool Close() wasClosed = true; var currentTimestamp = Stopwatch.GetTimestamp(); - DiagnosticSource.WriteConnectionClosed(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionClosed( + this, instanceId, startTimestamp, currentTimestamp, @@ -489,8 +445,8 @@ public virtual bool Close() catch (Exception e) { var currentTimestamp = Stopwatch.GetTimestamp(); - DiagnosticSource.WriteConnectionError(_connection.Value, - ConnectionId, + Dependencies.ConnectionLogger.ConnectionError( + this, e, instanceId, startTimestamp, diff --git a/src/EFCore.Relational/Storage/RelationalConnectionDependencies.cs b/src/EFCore.Relational/Storage/RelationalConnectionDependencies.cs index c0f4866df77..3934534d206 100644 --- a/src/EFCore.Relational/Storage/RelationalConnectionDependencies.cs +++ b/src/EFCore.Relational/Storage/RelationalConnectionDependencies.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Diagnostics; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; @@ -43,22 +42,18 @@ public sealed class RelationalConnectionDependencies /// The options for the current context instance. /// The logger to which transaction messages will be written. /// The logger to which connection messages will be written. - /// The diagnostic source to write to. public RelationalConnectionDependencies( [NotNull] IDbContextOptions contextOptions, - [NotNull] IInterceptingLogger transactionLogger, - [NotNull] IInterceptingLogger connectionLogger, - [NotNull] DiagnosticSource diagnosticSource) + [NotNull] IDiagnosticsLogger transactionLogger, + [NotNull] IDiagnosticsLogger connectionLogger) { Check.NotNull(contextOptions, nameof(contextOptions)); - Check.NotNull(connectionLogger, nameof(connectionLogger)); Check.NotNull(transactionLogger, nameof(transactionLogger)); - Check.NotNull(diagnosticSource, nameof(diagnosticSource)); + Check.NotNull(connectionLogger, nameof(connectionLogger)); ContextOptions = contextOptions; - ConnectionLogger = connectionLogger; TransactionLogger = transactionLogger; - DiagnosticSource = diagnosticSource; + ConnectionLogger = connectionLogger; } /// @@ -69,18 +64,13 @@ public RelationalConnectionDependencies( /// /// The logger to which transaction messages will be written. /// - public IInterceptingLogger TransactionLogger { get; } + public IDiagnosticsLogger TransactionLogger { get; } /// /// The logger to which connection messages will be written. /// - public IInterceptingLogger ConnectionLogger { get; } - - /// - /// The diagnostic source to write to. - /// - public DiagnosticSource DiagnosticSource { get; } + public IDiagnosticsLogger ConnectionLogger { get; } /// /// Clones this dependency parameter object with one service replaced. @@ -90,7 +80,7 @@ public RelationalConnectionDependencies( /// /// A new parameter object with the given service replaced. public RelationalConnectionDependencies With([NotNull] IDbContextOptions contextOptions) - => new RelationalConnectionDependencies(contextOptions, TransactionLogger, ConnectionLogger, DiagnosticSource); + => new RelationalConnectionDependencies(contextOptions, TransactionLogger, ConnectionLogger); /// /// Clones this dependency parameter object with one service replaced. @@ -99,8 +89,8 @@ public RelationalConnectionDependencies With([NotNull] IDbContextOptions context /// A replacement for the current dependency of this type. /// /// A new parameter object with the given service replaced. - public RelationalConnectionDependencies With([NotNull] IInterceptingLogger connectionLogger) - => new RelationalConnectionDependencies(ContextOptions, TransactionLogger, connectionLogger, DiagnosticSource); + public RelationalConnectionDependencies With([NotNull] IDiagnosticsLogger connectionLogger) + => new RelationalConnectionDependencies(ContextOptions, TransactionLogger, connectionLogger); /// /// Clones this dependency parameter object with one service replaced. @@ -109,17 +99,7 @@ public RelationalConnectionDependencies With([NotNull] IInterceptingLogger /// A new parameter object with the given service replaced. - public RelationalConnectionDependencies With([NotNull] IInterceptingLogger transactionLogger) - => new RelationalConnectionDependencies(ContextOptions, transactionLogger, ConnectionLogger, DiagnosticSource); - - /// - /// Clones this dependency parameter object with one service replaced. - /// - /// - /// A replacement for the current dependency of this type. - /// - /// A new parameter object with the given service replaced. - public RelationalConnectionDependencies With([NotNull] DiagnosticSource diagnosticSource) - => new RelationalConnectionDependencies(ContextOptions, TransactionLogger, ConnectionLogger, diagnosticSource); + public RelationalConnectionDependencies With([NotNull] IDiagnosticsLogger transactionLogger) + => new RelationalConnectionDependencies(ContextOptions, transactionLogger, ConnectionLogger); } } diff --git a/src/EFCore.Relational/Storage/RelationalDataReader.cs b/src/EFCore.Relational/Storage/RelationalDataReader.cs index 92e516152c5..c9d5405fd8b 100644 --- a/src/EFCore.Relational/Storage/RelationalDataReader.cs +++ b/src/EFCore.Relational/Storage/RelationalDataReader.cs @@ -5,7 +5,9 @@ using System.Data.Common; using System.Diagnostics; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Storage @@ -24,7 +26,7 @@ public class RelationalDataReader : IDisposable private readonly IRelationalConnection _connection; private readonly DbCommand _command; private readonly DbDataReader _reader; - private readonly DiagnosticSource _diagnosticSource; + private readonly IDiagnosticsLogger _logger; private readonly long _startTimestamp; private bool _disposed; @@ -35,21 +37,21 @@ public class RelationalDataReader : IDisposable /// The connection. /// The command that was executed. /// The underlying reader for the result set. - /// The diagnostic source. + /// The diagnostic source. public RelationalDataReader( [CanBeNull] IRelationalConnection connection, [NotNull] DbCommand command, [NotNull] DbDataReader reader, - [NotNull] DiagnosticSource diagnosticSource) + [NotNull] IDiagnosticsLogger logger) { Check.NotNull(command, nameof(command)); Check.NotNull(reader, nameof(reader)); - Check.NotNull(diagnosticSource, nameof(diagnosticSource)); + Check.NotNull(logger, nameof(logger)); _connection = connection; _command = command; _reader = reader; - _diagnosticSource = diagnosticSource; + _logger = logger; _startTimestamp = Stopwatch.GetTimestamp(); } @@ -76,9 +78,8 @@ public virtual void Dispose() { var currentTimestamp = Stopwatch.GetTimestamp(); - _diagnosticSource.WriteDataReaderDisposing( - _connection.DbConnection, - _connection.ConnectionId, + _logger.DataReaderDisposing( + _connection, _reader, _reader.RecordsAffected, _startTimestamp, diff --git a/src/EFCore.Relational/Storage/RelationalTransaction.cs b/src/EFCore.Relational/Storage/RelationalTransaction.cs index 82c21cac650..383fd33e56c 100644 --- a/src/EFCore.Relational/Storage/RelationalTransaction.cs +++ b/src/EFCore.Relational/Storage/RelationalTransaction.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Storage @@ -25,8 +24,7 @@ public class RelationalTransaction : IDbContextTransaction, IInfrastructure _logger; - private readonly DiagnosticSource _diagnosticSource; + private readonly IDiagnosticsLogger _logger; private readonly bool _transactionOwned; private bool _connectionClosed; @@ -38,21 +36,18 @@ public class RelationalTransaction : IDbContextTransaction, IInfrastructure The connection to the database. /// The underlying . /// The logger to write to. - /// The diagnostic source to write to. /// /// A value indicating whether the transaction is owned by this class (i.e. if it can be disposed when this class is disposed). /// public RelationalTransaction( [NotNull] IRelationalConnection connection, [NotNull] DbTransaction transaction, - [NotNull] IInterceptingLogger logger, - [NotNull] DiagnosticSource diagnosticSource, + [NotNull] IDiagnosticsLogger logger, bool transactionOwned) { Check.NotNull(connection, nameof(connection)); Check.NotNull(transaction, nameof(transaction)); Check.NotNull(logger, nameof(logger)); - Check.NotNull(diagnosticSource, nameof(diagnosticSource)); if (connection.DbConnection != transaction.Connection) { @@ -63,13 +58,7 @@ public RelationalTransaction( _dbTransaction = transaction; _logger = logger; - _diagnosticSource = diagnosticSource; _transactionOwned = transactionOwned; - - _diagnosticSource.WriteTransactionStarted( - _relationalConnection.DbConnection, - _relationalConnection.ConnectionId, - _dbTransaction); } /// @@ -77,10 +66,6 @@ public RelationalTransaction( /// public virtual void Commit() { - _logger.LogDebug( - RelationalEventId.CommittingTransaction, - () => RelationalStrings.RelationalLoggerCommittingTransaction); - var startTimestamp = Stopwatch.GetTimestamp(); try @@ -89,9 +74,8 @@ public virtual void Commit() var currentTimestamp = Stopwatch.GetTimestamp(); - _diagnosticSource.WriteTransactionCommit( - _relationalConnection.DbConnection, - _relationalConnection.ConnectionId, + _logger.TransactionCommitted( + _relationalConnection, _dbTransaction, startTimestamp, currentTimestamp); @@ -100,9 +84,8 @@ public virtual void Commit() { var currentTimestamp = Stopwatch.GetTimestamp(); - _diagnosticSource.WriteTransactionError( - _relationalConnection.DbConnection, - _relationalConnection.ConnectionId, + _logger.TransactionError( + _relationalConnection, _dbTransaction, "Commit", e, @@ -119,10 +102,6 @@ public virtual void Commit() /// public virtual void Rollback() { - _logger.LogDebug( - RelationalEventId.RollingbackTransaction, - () => RelationalStrings.RelationalLoggerRollingbackTransaction); - var startTimestamp = Stopwatch.GetTimestamp(); try @@ -131,9 +110,8 @@ public virtual void Rollback() var currentTimestamp = Stopwatch.GetTimestamp(); - _diagnosticSource.WriteTransactionRollback( - _relationalConnection.DbConnection, - _relationalConnection.ConnectionId, + _logger.TransactionRolledBack( + _relationalConnection, _dbTransaction, startTimestamp, currentTimestamp); @@ -142,9 +120,8 @@ public virtual void Rollback() { var currentTimestamp = Stopwatch.GetTimestamp(); - _diagnosticSource.WriteTransactionError( - _relationalConnection.DbConnection, - _relationalConnection.ConnectionId, + _logger.TransactionError( + _relationalConnection, _dbTransaction, "Rollback", e, @@ -169,9 +146,8 @@ public virtual void Dispose() { _dbTransaction.Dispose(); - _diagnosticSource.WriteTransactionDisposed( - _relationalConnection.DbConnection, - _relationalConnection.ConnectionId, + _logger.TransactionDisposed( + _relationalConnection, _dbTransaction); } diff --git a/src/EFCore.Relational/WarningConfigurationBuilderExtensions.cs b/src/EFCore.Relational/WarningConfigurationBuilderExtensions.cs deleted file mode 100644 index 509d86383e5..00000000000 --- a/src/EFCore.Relational/WarningConfigurationBuilderExtensions.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore -{ - /// - /// Relational database specific extension methods for . - /// - public static class WarningConfigurationBuilderExtensions - { - /// - /// Causes an exception to be thrown when the specified relational database warnings are generated. - /// - /// The builder being used to configure warnings. - /// - /// The (s) for the warnings. - /// - /// The same builder instance so that multiple calls can be chained. - public static WarningsConfigurationBuilder Throw( - [NotNull] this WarningsConfigurationBuilder warningsConfigurationBuilder, - [NotNull] params RelationalEventId[] relationalEventIds) - { - Check.NotNull(warningsConfigurationBuilder, nameof(warningsConfigurationBuilder)); - Check.NotNull(relationalEventIds, nameof(relationalEventIds)); - - return warningsConfigurationBuilder.WithOption( - e => e.WithExplicit(relationalEventIds.Cast(), WarningBehavior.Throw)); - } - - /// - /// Causes a warning to be logged when the specified relational database warnings are generated. - /// - /// The builder being used to configure warnings. - /// - /// The (s) for the warnings. - /// - /// The same builder instance so that multiple calls can be chained. - public static WarningsConfigurationBuilder Log( - [NotNull] this WarningsConfigurationBuilder warningsConfigurationBuilder, - [NotNull] params RelationalEventId[] relationalEventIds) - { - Check.NotNull(warningsConfigurationBuilder, nameof(warningsConfigurationBuilder)); - Check.NotNull(relationalEventIds, nameof(relationalEventIds)); - - return warningsConfigurationBuilder.WithOption( - e => e.WithExplicit(relationalEventIds.Cast(), WarningBehavior.Log)); - } - - /// - /// Causes nothing to happen when the specified relational database warnings are generated. - /// - /// The builder being used to configure warnings. - /// - /// The (s) for the warnings. - /// - /// The same builder instance so that multiple calls can be chained. - public static WarningsConfigurationBuilder Ignore( - [NotNull] this WarningsConfigurationBuilder warningsConfigurationBuilder, - [NotNull] params RelationalEventId[] relationalEventIds) - { - Check.NotNull(warningsConfigurationBuilder, nameof(warningsConfigurationBuilder)); - Check.NotNull(relationalEventIds, nameof(relationalEventIds)); - - return warningsConfigurationBuilder.WithOption( - e => e.WithExplicit(relationalEventIds.Cast(), WarningBehavior.Ignore)); - } - - private static WarningsConfigurationBuilder WithOption( - this WarningsConfigurationBuilder warningsConfigurationBuilder, - Func withFunc) - { - var optionsBuilder = warningsConfigurationBuilder.OptionsBuilder; - - var coreOptionsExtension = optionsBuilder.Options.FindExtension() ?? new CoreOptionsExtension(); - - coreOptionsExtension.WithWarningsConfiguration(withFunc(coreOptionsExtension.WarningsConfiguration)); - - ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension( - coreOptionsExtension.WithWarningsConfiguration(withFunc(coreOptionsExtension.WarningsConfiguration))); - - return warningsConfigurationBuilder; - } - } -} diff --git a/src/EFCore.Specification.Tests/TestHelpers.cs b/src/EFCore.Specification.Tests/TestHelpers.cs index b56ea801cff..807a2958495 100644 --- a/src/EFCore.Specification.Tests/TestHelpers.cs +++ b/src/EFCore.Specification.Tests/TestHelpers.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -13,6 +14,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; using Xunit; namespace Microsoft.EntityFrameworkCore.Specification.Tests @@ -72,6 +74,152 @@ private class FakeCurrentDbContext : ICurrentDbContext public DbContext Context { get; } } + public void TestEventLogging( + Type eventIdType, + Type loggerExtensionsType, + IDictionary> fakeFactories) + { + var eventIdFields = eventIdType.GetTypeInfo() + .DeclaredFields + .Where(p => p.FieldType == typeof(EventId)) + .ToList(); + + var declaredMethods = loggerExtensionsType.GetTypeInfo() + .DeclaredMethods.OrderBy(e => e.Name) + .ToList(); + + var loggerMethods = declaredMethods + .ToDictionary(m => m.Name); + + foreach (var eventIdField in eventIdFields) + { + var eventName = eventIdField.Name; + Assert.Contains(eventName, loggerMethods.Keys); + + var loggerMethod = loggerMethods[eventName]; + + var loggerParameters = loggerMethod.GetParameters(); + var category = loggerParameters[0].ParameterType.GenericTypeArguments[0]; + + if (category.GetTypeInfo().ContainsGenericParameters) + { + category = typeof(LoggerCategory.Infrastructure); + loggerMethod = loggerMethod.MakeGenericMethod(category); + } + + var eventId = ((EventId)eventIdField.GetValue(null)); + + var categoryName = Activator.CreateInstance(category).ToString(); + Assert.Equal(categoryName + "." + eventName, eventId.Name); + + var testLogger = (TestLoggerBase)Activator.CreateInstance(typeof(TestLogger<>).MakeGenericType(category)); + var testDiagnostics = new TestDiagnosticSource(); + + var args = new object[loggerParameters.Length]; + args[0] = Activator.CreateInstance( + typeof(DiagnosticsLogger<>).MakeGenericType(category), + testLogger, + testDiagnostics); + + for (var i = 1; i < args.Length; i++) + { + var type = loggerParameters[i].ParameterType; + + if (fakeFactories.TryGetValue(type, out var factory)) + { + args[i] = factory(); + } + else + { + try + { + args[i] = Activator.CreateInstance(type); + } + catch (Exception) + { + Assert.True(false, "Need to add factory for type " + type.DisplayName()); + } + } + } + + foreach (var enableFor in new[] { "Foo", eventId.Name }) + { + testDiagnostics.EnableFor = enableFor; + + var logged = false; + foreach (LogLevel logLevel in Enum.GetValues(typeof(LogLevel))) + { + testLogger.EnabledFor = logLevel; + testLogger.LoggedAt = null; + testDiagnostics.Logged = null; + + loggerMethod.Invoke(null, args); + + if (testLogger.LoggedAt != null) + { + Assert.Equal(logLevel, testLogger.LoggedAt); + logged = true; + } + + if (enableFor == eventId.Name) + { + Assert.Equal(eventId.Name, testDiagnostics.Logged); + } + else + { + Assert.Null(testDiagnostics.Logged); + } + } + + Assert.True(logged); + } + } + } + + private class TestLoggerBase + { + public LogLevel EnabledFor { get; set; } + + public LogLevel? LoggedAt { get; set; } + + public EventId LoggedEvent { get; set; } + } + + private class TestLogger : TestLoggerBase, IInterceptingLogger + where TCategory : LoggerCategory, new() + { + public ILoggingOptions Options => null; + + public IDisposable BeginScope(TState state) => null; + + public bool IsEnabled(EventId eventId, LogLevel logLevel) + { + LoggedEvent = eventId; + return EnabledFor == logLevel; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + LoggedAt = logLevel; + Assert.Equal(LoggedEvent, eventId); + } + + public bool ShouldLogSensitiveData(IDiagnosticsLogger diagnostics) => false; + } + + private class TestDiagnosticSource : DiagnosticSource + { + public string EnableFor { get; set; } + public string Logged { get; set; } + + public override void Write(string name, object value) + { + Logged = name; + } + + public override bool IsEnabled(string name) => name == EnableFor; + } + public DbContextOptions CreateOptions(IModel model, IServiceProvider serviceProvider = null) { var optionsBuilder = new DbContextOptionsBuilder() diff --git a/src/EFCore.SqlServer.Design/Infrastructure/SqlServerDesignEventId.cs b/src/EFCore.SqlServer.Design/Infrastructure/SqlServerDesignEventId.cs index 870c170fbc8..c65a666d394 100644 --- a/src/EFCore.SqlServer.Design/Infrastructure/SqlServerDesignEventId.cs +++ b/src/EFCore.SqlServer.Design/Infrastructure/SqlServerDesignEventId.cs @@ -1,74 +1,67 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; +using Microsoft.Extensions.Logging; + namespace Microsoft.EntityFrameworkCore.Infrastructure { /// - /// Values that are used as the eventId when logging messages from the SQL Server Design Entity Framework Core - /// components. + /// + /// Event IDs for relational design events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// /// - public enum SqlServerDesignEventId + public static class SqlServerDesignEventId { - /// - /// Found default schema. - /// - FoundDefaultSchema = 1, - - /// - /// Found type alias. - /// - FoundTypeAlias, - - /// - /// Column name empty on table. - /// - ColumnMustBeNamedWarning, - - /// - /// Index name empty. - /// - IndexMustBeNamedWarning, - - /// - /// Unable to find table for index. - /// - IndexTableMissingWarning, + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Scaffolding events + ColumnFound = CoreEventId.ProviderDesignBaseId, + ForeignKeyColumnFound, + DefaultSchemaFound, + TypeAliasFound, + DataTypeDoesNotAllowSqlServerIdentityStrategyWarning + } - /// - /// Column name empty on index. - /// - IndexColumnMustBeNamedWarning, - - /// - /// Foreign key name empty. - /// - ForeignKeyMustBeNamedWarning, + private static readonly string _scaffoldingPrefix = LoggerCategory.Scaffolding.Name + "."; + private static EventId MakeScaffoldingId(Id id) => new EventId((int)id, _scaffoldingPrefix + id); /// - /// Foreign key column not in selection set. + /// A column was found. + /// This event is in the category. /// - ForeignKeyColumnSkipped, + public static readonly EventId ColumnFound = MakeScaffoldingId(Id.ColumnFound); /// - /// Column name empty on foreign key. + /// A column of a foreign key was found. + /// This event is in the category. /// - ColumnNameEmptyOnForeignKey, + public static readonly EventId ForeignKeyColumnFound = MakeScaffoldingId(Id.ForeignKeyColumnFound); /// - /// Data type does not allow SQL Server identity strategy. + /// A default schema was found. + /// This event is in the category. /// - DataTypeDoesNotAllowSqlServerIdentityStrategyWarning, + public static readonly EventId DefaultSchemaFound = MakeScaffoldingId(Id.DefaultSchemaFound); /// - /// Cannot interpret default value. - /// Note: no longer used. + /// A type alias was found. + /// This event is in the category. /// - CannotInterpretDefaultValueWarning, + public static readonly EventId TypeAliasFound = MakeScaffoldingId(Id.TypeAliasFound); /// - /// Cannot interpret computed value. - /// Note: no longer used. + /// The data type does not support the SQL Server identity strategy. + /// This event is in the category. /// - CannotInterpretComputedValueWarning + public static readonly EventId DataTypeDoesNotAllowSqlServerIdentityStrategyWarning = MakeScaffoldingId(Id.DataTypeDoesNotAllowSqlServerIdentityStrategyWarning); } } diff --git a/src/EFCore.SqlServer.Design/Internal/SqlServerDatabaseModelFactory.cs b/src/EFCore.SqlServer.Design/Internal/SqlServerDatabaseModelFactory.cs index 07a4f12f8f2..d696d59d430 100644 --- a/src/EFCore.SqlServer.Design/Internal/SqlServerDatabaseModelFactory.cs +++ b/src/EFCore.SqlServer.Design/Internal/SqlServerDatabaseModelFactory.cs @@ -57,7 +57,7 @@ public class SqlServerDatabaseModelFactory : IInternalDatabaseModelFactory /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public SqlServerDatabaseModelFactory([NotNull] IInterceptingLogger logger) + public SqlServerDatabaseModelFactory([NotNull] IDiagnosticsLogger logger) { Check.NotNull(logger, nameof(logger)); @@ -68,7 +68,7 @@ public SqlServerDatabaseModelFactory([NotNull] IInterceptingLogger - public virtual IInterceptingLogger Logger { get; } + public virtual IDiagnosticsLogger Logger { get; } private void ResetState() { @@ -153,9 +153,7 @@ private void GetDefaultSchema() var command = _connection.CreateCommand(); command.CommandText = "SELECT SCHEMA_NAME()"; var schema = command.ExecuteScalar() as string ?? "dbo"; - Logger.LogDebug( - SqlServerDesignEventId.FoundDefaultSchema, - () => SqlServerDesignStrings.FoundDefaultSchema(schema)); + Logger.DefaultSchemaFound(schema); _databaseModel.DefaultSchemaName = schema; } @@ -193,9 +191,7 @@ LEFT JOIN [sys].[schemas] AS s1 var aliasSchema = reader.GetValueOrDefault("schema_name"); var alias = reader.GetValueOrDefault("type_name"); var underlyingSystemType = reader.GetValueOrDefault("underlying_system_type"); - Logger.LogDebug( - SqlServerDesignEventId.FoundTypeAlias, - () => SqlServerDesignStrings.FoundTypeAlias(aliasSchema, alias, underlyingSystemType)); + Logger.TypeAliasFound(DisplayName(aliasSchema, alias), underlyingSystemType); typeAliasMap.Add(SchemaQualifiedKey(alias, aliasSchema), underlyingSystemType); } } @@ -233,17 +229,12 @@ private void GetSequences() Max = reader.GetValueOrDefault("maximum_value") }; - Logger.LogDebug( - RelationalDesignEventId.FoundSequence, - () => SqlServerDesignStrings.FoundSequence( - sequence.SchemaName, sequence.Name, sequence.DataType, sequence.IsCyclic, - sequence.IncrementBy, sequence.Start, sequence.Min, sequence.Max)); + Logger.SequenceFound(sequence.DisplayName, sequence.DataType, sequence.IsCyclic, + sequence.IncrementBy, sequence.Start, sequence.Min, sequence.Max); if (string.IsNullOrEmpty(sequence.Name)) { - Logger.LogWarning( - RelationalDesignEventId.SequenceMustBeNamedWarning, - () => RelationalDesignStrings.SequencesRequireName); + Logger.SequenceNotNamedWarning(); continue; } @@ -296,9 +287,7 @@ FROM sys.extended_properties table[SqlServerFullAnnotationNames.Instance.MemoryOptimized] = reader.GetValueOrDefault("is_memory_optimized"); } - Logger.LogDebug( - RelationalDesignEventId.FoundTable, - () => SqlServerDesignStrings.FoundTable(table.SchemaName, table.Name)); + Logger.TableFound(table.DisplayName); if (_tableSelectionSet.Allows(table.SchemaName, table.Name)) { @@ -307,9 +296,7 @@ FROM sys.extended_properties } else { - Logger.LogDebug( - RelationalDesignEventId.TableSkipped, - () => SqlServerDesignStrings.TableNotInSelectionSet(table.SchemaName, table.Name)); + Logger.TableSkipped(table.DisplayName); } } } @@ -373,34 +360,26 @@ WHERE t.name <> '" + HistoryRepository.DefaultTableName + "'" + var isIdentity = reader.GetValueOrDefault("is_identity"); var isComputed = reader.GetValueOrDefault("is_computed"); - Logger.LogDebug( - RelationalDesignEventId.FoundColumn, - () => SqlServerDesignStrings.FoundColumn( - schemaName, tableName, columnName, dataTypeName, dataTypeSchemaName, ordinal, nullable, - primaryKeyOrdinal, defaultValue, computedValue, precision, scale, maxLength, isIdentity, isComputed)); + Logger.ColumnFound( + DisplayName(schemaName, tableName), columnName, DisplayName(dataTypeSchemaName, dataTypeName), ordinal, nullable, + primaryKeyOrdinal, defaultValue, computedValue, precision, scale, maxLength, isIdentity, isComputed); if (!_tableSelectionSet.Allows(schemaName, tableName)) { - Logger.LogDebug( - RelationalDesignEventId.ColumnSkipped, - () => SqlServerDesignStrings.ColumnNotInSelectionSet(columnName, schemaName, tableName)); + Logger.ColumnSkipped(DisplayName(schemaName, tableName), columnName); continue; } if (string.IsNullOrEmpty(columnName)) { - Logger.LogWarning( - SqlServerDesignEventId.ColumnMustBeNamedWarning, - () => SqlServerDesignStrings.ColumnNameEmptyOnTable(schemaName, tableName)); + Logger.ColumnNotNamedWarning(DisplayName(schemaName, tableName)); continue; } TableModel table; if (!_tables.TryGetValue(SchemaQualifiedKey(tableName, schemaName), out table)) { - Logger.LogWarning( - RelationalDesignEventId.MissingTableWarning, - () => SqlServerDesignStrings.UnableToFindTableForColumn(columnName, schemaName, tableName)); + Logger.MissingTableWarning(DisplayName(schemaName, tableName)); continue; } @@ -491,25 +470,18 @@ AND object_name(i.object_id) <> '" + HistoryRepository.DefaultTableName + @"'" + var hasFilter = reader.GetValueOrDefault("has_filter"); var filterDefinition = reader.GetValueOrDefault("filter_definition"); - Logger.LogDebug( - RelationalDesignEventId.FoundIndexColumn, - () => SqlServerDesignStrings.FoundIndexColumn( - schemaName, tableName, indexName, isUnique, typeDesc, columnName, indexOrdinal)); + Logger.IndexColumnFound( + DisplayName(schemaName, tableName), indexName, isUnique, columnName, indexOrdinal); if (!_tableSelectionSet.Allows(schemaName, tableName)) { - Logger.LogDebug( - RelationalDesignEventId.IndexColumnSkipped, - () => SqlServerDesignStrings.IndexColumnNotInSelectionSet( - columnName, indexName, schemaName, tableName)); + Logger.IndexColumnSkipped(columnName, indexName, DisplayName(schemaName, tableName)); continue; } if (string.IsNullOrEmpty(indexName)) { - Logger.LogWarning( - SqlServerDesignEventId.IndexMustBeNamedWarning, - () => SqlServerDesignStrings.IndexNameEmpty(schemaName, tableName)); + Logger.IndexNotNamedWarning(DisplayName(schemaName, tableName)); continue; } @@ -522,9 +494,7 @@ AND object_name(i.object_id) <> '" + HistoryRepository.DefaultTableName + @"'" + TableModel table; if (!_tables.TryGetValue(SchemaQualifiedKey(tableName, schemaName), out table)) { - Logger.LogWarning( - SqlServerDesignEventId.IndexTableMissingWarning, - () => SqlServerDesignStrings.UnableToFindTableForIndex(indexName, schemaName, tableName)); + Logger.IndexTableMissingWarning(indexName, DisplayName(schemaName, tableName)); continue; } @@ -543,17 +513,11 @@ AND object_name(i.object_id) <> '" + HistoryRepository.DefaultTableName + @"'" + ColumnModel column; if (string.IsNullOrEmpty(columnName)) { - Logger.LogWarning( - SqlServerDesignEventId.IndexColumnMustBeNamedWarning, - () => SqlServerDesignStrings.ColumnNameEmptyOnIndex( - schemaName, tableName, indexName)); + Logger.IndexColumnNotNamedWarning(indexName, DisplayName(schemaName, tableName)); } else if (!_tableColumns.TryGetValue(ColumnKey(index.Table, columnName), out column)) { - Logger.LogWarning( - RelationalDesignEventId.IndexColumnsNotMappedWarning, - () => SqlServerDesignStrings.UnableToFindColumnForIndex( - indexName, columnName, schemaName, tableName)); + Logger.IndexColumnsNotMappedWarning(indexName, new [] { columnName }); } else { @@ -607,26 +571,19 @@ FROM sys.foreign_keys AS f var deleteAction = reader.GetValueOrDefault("delete_referential_action_desc"); var ordinal = reader.GetValueOrDefault("constraint_column_id"); - Logger.LogDebug( - RelationalDesignEventId.FoundForeignKeyColumn, - () => SqlServerDesignStrings.FoundForeignKeyColumn( - schemaName, tableName, fkName, principalTableSchemaName, principalTableName, - fromColumnName, toColumnName, updateAction, deleteAction, ordinal)); + Logger.ForeignKeyColumnFound( + DisplayName(schemaName, tableName), fkName, DisplayName(principalTableSchemaName, principalTableName), + fromColumnName, toColumnName, updateAction, deleteAction, ordinal); if (string.IsNullOrEmpty(fkName)) { - Logger.LogWarning( - SqlServerDesignEventId.ForeignKeyMustBeNamedWarning, - () => SqlServerDesignStrings.ForeignKeyNameEmpty(schemaName, tableName)); + Logger.ForeignKeyNotNamedWarning(DisplayName(schemaName, tableName)); continue; } if (!_tableSelectionSet.Allows(schemaName, tableName)) { - Logger.LogDebug( - SqlServerDesignEventId.ForeignKeyColumnSkipped, - () => SqlServerDesignStrings.ForeignKeyColumnNotInSelectionSet( - fromColumnName, fkName, schemaName, tableName)); + Logger.ForeignKeyColumnMissingWarning(fromColumnName, fkName, DisplayName(schemaName, tableName)); continue; } @@ -649,10 +606,8 @@ FROM sys.foreign_keys AS f if (principalTable == null) { - Logger.LogDebug( - RelationalDesignEventId.ForeignKeyReferencesMissingTable, - () => SqlServerDesignStrings.PrincipalTableNotInSelectionSet( - fkName, schemaName, tableName, principalTableSchemaName, principalTableName)); + Logger.ForeignKeyReferencesMissingPrincipalTableWarning( + fkName, DisplayName(schemaName, tableName), DisplayName(principalTableSchemaName, principalTableName)); } fkInfo = new ForeignKeyModel @@ -691,26 +646,23 @@ FROM sys.foreign_keys AS f } } + private static string DisplayName(string schema, string name) + => (!string.IsNullOrEmpty(schema) ? schema + "." : "") + name; + private ColumnModel FindColumnForForeignKey( string columnName, TableModel table, string fkName) { ColumnModel column; if (string.IsNullOrEmpty(columnName)) { - Logger.LogWarning( - SqlServerDesignEventId.ColumnNameEmptyOnForeignKey, - () => SqlServerDesignStrings.ColumnNameEmptyOnForeignKey( - table.SchemaName, table.Name, fkName)); + Logger.ForeignKeyColumnNotNamedWarning(fkName, DisplayName(table.SchemaName, table.Name)); return null; } if (!_tableColumns.TryGetValue( ColumnKey(table, columnName), out column)) { - Logger.LogWarning( - RelationalDesignEventId.ForeignKeyColumnsNotMappedWarning, - () => SqlServerDesignStrings.UnableToFindColumnForForeignKey( - fkName, columnName, table.SchemaName, table.Name)); + Logger.ForeignKeyColumnsNotMappedWarning(fkName, new[] { columnName }); return null; } diff --git a/src/EFCore.SqlServer.Design/Internal/SqlServerDesignLoggerExtensions.cs b/src/EFCore.SqlServer.Design/Internal/SqlServerDesignLoggerExtensions.cs index 9a18a8d01db..c38e7687cdb 100644 --- a/src/EFCore.SqlServer.Design/Internal/SqlServerDesignLoggerExtensions.cs +++ b/src/EFCore.SqlServer.Design/Internal/SqlServerDesignLoggerExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.Logging; @@ -18,20 +17,186 @@ public static class SqlServerDesignLoggerExtensions /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static void LogWarning( - [NotNull] this ILogger logger, - SqlServerDesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Warning, (int)eventId, null, null, (_, __) => formatter()); + public static void ColumnFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName, + [CanBeNull] string columnName, + [CanBeNull] string dataTypeName, + int? ordinal, + bool? nullable, + int? primaryKeyOrdinal, + [CanBeNull] string defaultValue, + [CanBeNull] string computedValue, + int? precision, + int? scale, + int? maxLength, + [CanBeNull] bool? identity, + [CanBeNull] bool? computed) + { + var eventId = SqlServerDesignEventId.ColumnFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + SqlServerDesignStrings.FoundColumn( + tableName, columnName, dataTypeName, ordinal, nullable, + primaryKeyOrdinal, defaultValue, computedValue, precision, scale, maxLength, identity, computed)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName, + ColumnName = columnName, + DataTypeName = dataTypeName, + Ordinal = ordinal, + Nullable = nullable, + PrimaryKeyOrdinal = primaryKeyOrdinal, + DefaultValue = defaultValue, + ComputedValue = computedValue, + Precision = precision, + Scale = scale, + MaxLength = maxLength, + Identity = identity, + Computed = computed + }); + } + } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static void LogDebug( - [NotNull] this ILogger logger, - SqlServerDesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Debug, (int)eventId, null, null, (_, __) => formatter()); + public static void ForeignKeyColumnFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName, + [CanBeNull] string foreignKeyName, + [CanBeNull] string principalTableName, + [CanBeNull] string columnName, + [CanBeNull] string principalColumnName, + [CanBeNull] string updateAction, + [CanBeNull] string deleteAction, + int? ordinal) + { + var eventId = SqlServerDesignEventId.ForeignKeyColumnFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + SqlServerDesignStrings.FoundForeignKeyColumn( + tableName, foreignKeyName, principalTableName, + columnName, principalColumnName, updateAction, deleteAction, ordinal)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName, + ForeignKeyName = foreignKeyName, + PrincipalTableName = principalTableName, + ColumnName = columnName, + PrincipalColumnName = principalColumnName, + UpdateAction = updateAction, + DeleteAction = deleteAction, + Ordinal = ordinal + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void DefaultSchemaFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string schemaName) + { + var eventId = SqlServerDesignEventId.DefaultSchemaFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + SqlServerDesignStrings.FoundDefaultSchema(schemaName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + SchemaName = schemaName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void TypeAliasFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string typeAliasName, + [CanBeNull] string systemTypeName) + { + var eventId = SqlServerDesignEventId.TypeAliasFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + SqlServerDesignStrings.FoundTypeAlias(typeAliasName, systemTypeName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TypeAliasName = typeAliasName, + SystemTypeName = systemTypeName + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void DataTypeDoesNotAllowSqlServerIdentityStrategyWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string columnName, + [CanBeNull] string typeName) + { + var eventId = SqlServerDesignEventId.DataTypeDoesNotAllowSqlServerIdentityStrategyWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + SqlServerDesignStrings.DataTypeDoesNotAllowSqlServerIdentityStrategy(columnName, typeName)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ColumnName = columnName, + TypeName = typeName + }); + } + } } } diff --git a/src/EFCore.SqlServer.Design/Internal/SqlServerScaffoldingModelFactory.cs b/src/EFCore.SqlServer.Design/Internal/SqlServerScaffoldingModelFactory.cs index c9f50dfb312..28a7399902b 100644 --- a/src/EFCore.SqlServer.Design/Internal/SqlServerScaffoldingModelFactory.cs +++ b/src/EFCore.SqlServer.Design/Internal/SqlServerScaffoldingModelFactory.cs @@ -36,7 +36,7 @@ public class SqlServerScaffoldingModelFactory : RelationalScaffoldingModelFactor /// directly from your code. This API may change or be removed in future releases. /// public SqlServerScaffoldingModelFactory( - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, [NotNull] IRelationalTypeMapper typeMapper, [NotNull] IDatabaseModelFactory databaseModelFactory, [NotNull] CandidateNamingService candidateNamingService, @@ -159,10 +159,8 @@ private void VisitTypeMapping(PropertyBuilder propertyBuilder, ColumnModel colum { if (typeof(byte) == propertyBuilder.Metadata.ClrType) { - Logger.LogWarning( - SqlServerDesignEventId.DataTypeDoesNotAllowSqlServerIdentityStrategyWarning, - () => SqlServerDesignStrings.DataTypeDoesNotAllowSqlServerIdentityStrategy( - column.DisplayName, column.DataType)); + Logger.DataTypeDoesNotAllowSqlServerIdentityStrategyWarning( + column.DisplayName, column.DataType); } else { diff --git a/src/EFCore.SqlServer.Design/Properties/SqlServerDesignStrings.Designer.cs b/src/EFCore.SqlServer.Design/Properties/SqlServerDesignStrings.Designer.cs index 220dd905831..01e4583c4ee 100644 --- a/src/EFCore.SqlServer.Design/Properties/SqlServerDesignStrings.Designer.cs +++ b/src/EFCore.SqlServer.Design/Properties/SqlServerDesignStrings.Designer.cs @@ -15,38 +15,6 @@ public static class SqlServerDesignStrings private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.SqlServerDesignStrings", typeof(SqlServerDesignStrings).GetTypeInfo().Assembly); - /// - /// Found a column on foreign key [{schemaName}].[{tableName}].[{fkName}] with an empty or null name. Not including column in foreign key - /// - public static string ColumnNameEmptyOnForeignKey([CanBeNull] object schemaName, [CanBeNull] object tableName, [CanBeNull] object fkName) - => string.Format( - GetString("ColumnNameEmptyOnForeignKey", nameof(schemaName), nameof(tableName), nameof(fkName)), - schemaName, tableName, fkName); - - /// - /// Found a column on index [{schemaName}].[{tableName}].[{indexName}] with an empty or null name. Not including column in index. - /// - public static string ColumnNameEmptyOnIndex([CanBeNull] object schemaName, [CanBeNull] object tableName, [CanBeNull] object indexName) - => string.Format( - GetString("ColumnNameEmptyOnIndex", nameof(schemaName), nameof(tableName), nameof(indexName)), - schemaName, tableName, indexName); - - /// - /// Found a column on table [{schemaName}].[{tableName}] with an empty or null name. Skipping column. - /// - public static string ColumnNameEmptyOnTable([CanBeNull] object schemaName, [CanBeNull] object tableName) - => string.Format( - GetString("ColumnNameEmptyOnTable", nameof(schemaName), nameof(tableName)), - schemaName, tableName); - - /// - /// Column {columnName} belongs to table [{schema}].[{tableName}] which is not included in the selection set. Skipping. - /// - public static string ColumnNotInSelectionSet([CanBeNull] object columnName, [CanBeNull] object schema, [CanBeNull] object tableName) - => string.Format( - GetString("ColumnNotInSelectionSet", nameof(columnName), nameof(schema), nameof(tableName)), - columnName, schema, tableName); - /// /// For column {columnId}. This column is set up as an Identity column, but the SQL Server data type is {sqlServerDataType}. This will be mapped to CLR type byte which does not allow the SqlServerValueGenerationStrategy.IdentityColumn setting. Generating a matching Property but ignoring the Identity setting. /// @@ -55,30 +23,6 @@ public static string DataTypeDoesNotAllowSqlServerIdentityStrategy([CanBeNull] o GetString("DataTypeDoesNotAllowSqlServerIdentityStrategy", nameof(columnId), nameof(sqlServerDataType)), columnId, sqlServerDataType); - /// - /// Foreign key column {columnName} belongs to foreign key {fkName} on table [{schema}].[{tableName}] which is not included in the selection set. Skipping. - /// - public static string ForeignKeyColumnNotInSelectionSet([CanBeNull] object columnName, [CanBeNull] object fkName, [CanBeNull] object schema, [CanBeNull] object tableName) - => string.Format( - GetString("ForeignKeyColumnNotInSelectionSet", nameof(columnName), nameof(fkName), nameof(schema), nameof(tableName)), - columnName, fkName, schema, tableName); - - /// - /// Found a foreign key on table [{schemaName}].[{tableName}] with an empty or null name. Skipping foreign key. - /// - public static string ForeignKeyNameEmpty([CanBeNull] object schemaName, [CanBeNull] object tableName) - => string.Format( - GetString("ForeignKeyNameEmpty", nameof(schemaName), nameof(tableName)), - schemaName, tableName); - - /// - /// Found column with schema: {schema}, table: {tableName}, column name: {columnName}, data type: {dataType}, data type schema: {dataTypeSchema}, ordinal: {ordinal}, nullable: {isNullable}, primary key ordinal: {primaryKeyOrdinal}, default value: {defaultValue}, computed value: {computedValue}, precision: {precision}, scale: {scale}, maximum length: {maxLength}, identity: {isIdentity}, computed: {isComputed}. - /// - public static string FoundColumn([CanBeNull] object schema, [CanBeNull] object tableName, [CanBeNull] object columnName, [CanBeNull] object dataType, [CanBeNull] object dataTypeSchema, [CanBeNull] object ordinal, [CanBeNull] object isNullable, [CanBeNull] object primaryKeyOrdinal, [CanBeNull] object defaultValue, [CanBeNull] object computedValue, [CanBeNull] object precision, [CanBeNull] object scale, [CanBeNull] object maxLength, [CanBeNull] object isIdentity, [CanBeNull] object isComputed) - => string.Format( - GetString("FoundColumn", nameof(schema), nameof(tableName), nameof(columnName), nameof(dataType), nameof(dataTypeSchema), nameof(ordinal), nameof(isNullable), nameof(primaryKeyOrdinal), nameof(defaultValue), nameof(computedValue), nameof(precision), nameof(scale), nameof(maxLength), nameof(isIdentity), nameof(isComputed)), - schema, tableName, columnName, dataType, dataTypeSchema, ordinal, isNullable, primaryKeyOrdinal, defaultValue, computedValue, precision, scale, maxLength, isIdentity, isComputed); - /// /// Found default schema {defaultSchema}. /// @@ -88,124 +32,28 @@ public static string FoundDefaultSchema([CanBeNull] object defaultSchema) defaultSchema); /// - /// Found foreign key column with schema: {schema}, table: {tableName}, foreign key name: {fkName}, principal table schema: {principalTableSchema}, principal table: {principalTableName}, column name: {columnName}, principal column name: {principalColumnName}, update action: {updateAction}, delete action: {deleteAction}, ordinal: {ordinal}. - /// - public static string FoundForeignKeyColumn([CanBeNull] object schema, [CanBeNull] object tableName, [CanBeNull] object fkName, [CanBeNull] object principalTableSchema, [CanBeNull] object principalTableName, [CanBeNull] object columnName, [CanBeNull] object principalColumnName, [CanBeNull] object updateAction, [CanBeNull] object deleteAction, [CanBeNull] object ordinal) - => string.Format( - GetString("FoundForeignKeyColumn", nameof(schema), nameof(tableName), nameof(fkName), nameof(principalTableSchema), nameof(principalTableName), nameof(columnName), nameof(principalColumnName), nameof(updateAction), nameof(deleteAction), nameof(ordinal)), - schema, tableName, fkName, principalTableSchema, principalTableName, columnName, principalColumnName, updateAction, deleteAction, ordinal); - - /// - /// Found index column with schema: {schema}, table: {tableName}, index name: {indexName}, unique: {isUnique}, type description: {typeDesc}, column name: {columnName}, ordinal: {ordinal}. - /// - public static string FoundIndexColumn([CanBeNull] object schema, [CanBeNull] object tableName, [CanBeNull] object indexName, [CanBeNull] object isUnique, [CanBeNull] object typeDesc, [CanBeNull] object columnName, [CanBeNull] object ordinal) - => string.Format( - GetString("FoundIndexColumn", nameof(schema), nameof(tableName), nameof(indexName), nameof(isUnique), nameof(typeDesc), nameof(columnName), nameof(ordinal)), - schema, tableName, indexName, isUnique, typeDesc, columnName, ordinal); - - /// - /// Found sequence with schema: {schema}, name: {name}, data type: {dataType}, cyclic: {isCyclic}, increment: {increment}, start: {start}, minimum: {min}, maximum: {max}. - /// - public static string FoundSequence([CanBeNull] object schema, [CanBeNull] object name, [CanBeNull] object dataType, [CanBeNull] object isCyclic, [CanBeNull] object increment, [CanBeNull] object start, [CanBeNull] object min, [CanBeNull] object max) - => string.Format( - GetString("FoundSequence", nameof(schema), nameof(name), nameof(dataType), nameof(isCyclic), nameof(increment), nameof(start), nameof(min), nameof(max)), - schema, name, dataType, isCyclic, increment, start, min, max); - - /// - /// Found table with schema: {schema}, name: {name}. - /// - public static string FoundTable([CanBeNull] object schema, [CanBeNull] object name) - => string.Format( - GetString("FoundTable", nameof(schema), nameof(name)), - schema, name); - - /// - /// Found type alias with schema: {schema} name: {alias} which maps to underlying data type {dataType}. - /// - public static string FoundTypeAlias([CanBeNull] object schema, [CanBeNull] object alias, [CanBeNull] object dataType) - => string.Format( - GetString("FoundTypeAlias", nameof(schema), nameof(alias), nameof(dataType)), - schema, alias, dataType); - - /// - /// Index column {columnName} belongs to index {indexName} on table [{schema}].[{tableName}] which is not included in the selection set. Skipping. - /// - public static string IndexColumnNotInSelectionSet([CanBeNull] object columnName, [CanBeNull] object indexName, [CanBeNull] object schema, [CanBeNull] object tableName) - => string.Format( - GetString("IndexColumnNotInSelectionSet", nameof(columnName), nameof(indexName), nameof(schema), nameof(tableName)), - columnName, indexName, schema, tableName); - - /// - /// Found an index on table [{schemaName}].[{tableName}] with an empty or null name. Skipping index. - /// - public static string IndexNameEmpty([CanBeNull] object schemaName, [CanBeNull] object tableName) - => string.Format( - GetString("IndexNameEmpty", nameof(schemaName), nameof(tableName)), - schemaName, tableName); - - /// - /// For foreign key {fkName} on table [{schema}].[{tableName}], unable to model the end of the foreign key on principal table [{principalTableSchema}].[{principalTableName}]. This is usually because the principal table was not included in the selection set. - /// - public static string PrincipalTableNotInSelectionSet([CanBeNull] object fkName, [CanBeNull] object schema, [CanBeNull] object tableName, [CanBeNull] object principalTableSchema, [CanBeNull] object principalTableName) - => string.Format( - GetString("PrincipalTableNotInSelectionSet", nameof(fkName), nameof(schema), nameof(tableName), nameof(principalTableSchema), nameof(principalTableName)), - fkName, schema, tableName, principalTableSchema, principalTableName); - - /// - /// Found a sequence in schema [{schemaName}] with an empty or null name. Skipping sequence. - /// - public static string SequenceNameEmpty([CanBeNull] object schemaName) - => string.Format( - GetString("SequenceNameEmpty", nameof(schemaName)), - schemaName); - - /// - /// Table [{schema}].[{tableName}] is not included in the selection set. Skipping. - /// - public static string TableNotInSelectionSet([CanBeNull] object schema, [CanBeNull] object tableName) - => string.Format( - GetString("TableNotInSelectionSet", nameof(schema), nameof(tableName)), - schema, tableName); - - /// - /// For column {columnId} unable to convert default value {defaultValue} into type {propertyType}. Will not generate code setting a default value for the property {propertyName} on entity type {entityTypeName}. - /// - public static string UnableToConvertDefaultValue([CanBeNull] object columnId, [CanBeNull] object defaultValue, [CanBeNull] object propertyType, [CanBeNull] object propertyName, [CanBeNull] object entityTypeName) - => string.Format( - GetString("UnableToConvertDefaultValue", nameof(columnId), nameof(defaultValue), nameof(propertyType), nameof(propertyName), nameof(entityTypeName)), - columnId, defaultValue, propertyType, propertyName, entityTypeName); - - /// - /// Foreign Key {fkName} contains a column named {columnName} which cannot be found on table [{schemaName}].[{tableName}]. Not including column in foreign key. - /// - public static string UnableToFindColumnForForeignKey([CanBeNull] object fkName, [CanBeNull] object columnName, [CanBeNull] object schemaName, [CanBeNull] object tableName) - => string.Format( - GetString("UnableToFindColumnForForeignKey", nameof(fkName), nameof(columnName), nameof(schemaName), nameof(tableName)), - fkName, columnName, schemaName, tableName); - - /// - /// Index {indexName} contains a column named {columnName} which cannot be found on table [{schemaName}].[{tableName}]. Not including column in index. + /// Found type alias with name: {alias} which maps to underlying data type {dataType}. /// - public static string UnableToFindColumnForIndex([CanBeNull] object indexName, [CanBeNull] object columnName, [CanBeNull] object schemaName, [CanBeNull] object tableName) + public static string FoundTypeAlias([CanBeNull] object alias, [CanBeNull] object dataType) => string.Format( - GetString("UnableToFindColumnForIndex", nameof(indexName), nameof(columnName), nameof(schemaName), nameof(tableName)), - indexName, columnName, schemaName, tableName); + GetString("FoundTypeAlias", nameof(alias), nameof(dataType)), + alias, dataType); /// - /// For column {columnName}. Unable to find parent table [{schemaName}].[{tablename}]. Skipping column. + /// Found column with table: {tableName}, column name: {columnName}, data type: {dataType}, ordinal: {ordinal}, nullable: {isNullable}, primary key ordinal: {primaryKeyOrdinal}, default value: {defaultValue}, computed value: {computedValue}, precision: {precision}, scale: {scale}, maximum length: {maxLength}, identity: {isIdentity}, computed: {isComputed}. /// - public static string UnableToFindTableForColumn([CanBeNull] object columnName, [CanBeNull] object schemaName, [CanBeNull] object tablename) + public static string FoundColumn([CanBeNull] object tableName, [CanBeNull] object columnName, [CanBeNull] object dataType, [CanBeNull] object ordinal, [CanBeNull] object isNullable, [CanBeNull] object primaryKeyOrdinal, [CanBeNull] object defaultValue, [CanBeNull] object computedValue, [CanBeNull] object precision, [CanBeNull] object scale, [CanBeNull] object maxLength, [CanBeNull] object isIdentity, [CanBeNull] object isComputed) => string.Format( - GetString("UnableToFindTableForColumn", nameof(columnName), nameof(schemaName), nameof(tablename)), - columnName, schemaName, tablename); + GetString("FoundColumn", nameof(tableName), nameof(columnName), nameof(dataType), nameof(ordinal), nameof(isNullable), nameof(primaryKeyOrdinal), nameof(defaultValue), nameof(computedValue), nameof(precision), nameof(scale), nameof(maxLength), nameof(isIdentity), nameof(isComputed)), + tableName, columnName, dataType, ordinal, isNullable, primaryKeyOrdinal, defaultValue, computedValue, precision, scale, maxLength, isIdentity, isComputed); /// - /// For index {indexName}. Unable to find parent table [{schemaName}].[{tableName}]. Skipping index. + /// Found foreign key column with table: {tableName}, foreign key name: {fkName}, principal table: {principalTableName}, column name: {columnName}, principal column name: {principalColumnName}, update action: {updateAction}, delete action: {deleteAction}, ordinal: {ordinal}. /// - public static string UnableToFindTableForIndex([CanBeNull] object indexName, [CanBeNull] object schemaName, [CanBeNull] object tableName) + public static string FoundForeignKeyColumn([CanBeNull] object tableName, [CanBeNull] object fkName, [CanBeNull] object principalTableName, [CanBeNull] object columnName, [CanBeNull] object principalColumnName, [CanBeNull] object updateAction, [CanBeNull] object deleteAction, [CanBeNull] object ordinal) => string.Format( - GetString("UnableToFindTableForIndex", nameof(indexName), nameof(schemaName), nameof(tableName)), - indexName, schemaName, tableName); + GetString("FoundForeignKeyColumn", nameof(tableName), nameof(fkName), nameof(principalTableName), nameof(columnName), nameof(principalColumnName), nameof(updateAction), nameof(deleteAction), nameof(ordinal)), + tableName, fkName, principalTableName, columnName, principalColumnName, updateAction, deleteAction, ordinal); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/EFCore.SqlServer.Design/Properties/SqlServerDesignStrings.resx b/src/EFCore.SqlServer.Design/Properties/SqlServerDesignStrings.resx index 745a39dd384..152842c9324 100644 --- a/src/EFCore.SqlServer.Design/Properties/SqlServerDesignStrings.resx +++ b/src/EFCore.SqlServer.Design/Properties/SqlServerDesignStrings.resx @@ -117,76 +117,19 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Found a column on foreign key [{schemaName}].[{tableName}].[{fkName}] with an empty or null name. Not including column in foreign key - - - Found a column on index [{schemaName}].[{tableName}].[{indexName}] with an empty or null name. Not including column in index. - - - Found a column on table [{schemaName}].[{tableName}] with an empty or null name. Skipping column. - - - Column {columnName} belongs to table [{schema}].[{tableName}] which is not included in the selection set. Skipping. - For column {columnId}. This column is set up as an Identity column, but the SQL Server data type is {sqlServerDataType}. This will be mapped to CLR type byte which does not allow the SqlServerValueGenerationStrategy.IdentityColumn setting. Generating a matching Property but ignoring the Identity setting. - - Foreign key column {columnName} belongs to foreign key {fkName} on table [{schema}].[{tableName}] which is not included in the selection set. Skipping. - - - Found a foreign key on table [{schemaName}].[{tableName}] with an empty or null name. Skipping foreign key. - - - Found column with schema: {schema}, table: {tableName}, column name: {columnName}, data type: {dataType}, data type schema: {dataTypeSchema}, ordinal: {ordinal}, nullable: {isNullable}, primary key ordinal: {primaryKeyOrdinal}, default value: {defaultValue}, computed value: {computedValue}, precision: {precision}, scale: {scale}, maximum length: {maxLength}, identity: {isIdentity}, computed: {isComputed}. - Found default schema {defaultSchema}. - - Found foreign key column with schema: {schema}, table: {tableName}, foreign key name: {fkName}, principal table schema: {principalTableSchema}, principal table: {principalTableName}, column name: {columnName}, principal column name: {principalColumnName}, update action: {updateAction}, delete action: {deleteAction}, ordinal: {ordinal}. - - - Found index column with schema: {schema}, table: {tableName}, index name: {indexName}, unique: {isUnique}, type description: {typeDesc}, column name: {columnName}, ordinal: {ordinal}. - - - Found sequence with schema: {schema}, name: {name}, data type: {dataType}, cyclic: {isCyclic}, increment: {increment}, start: {start}, minimum: {min}, maximum: {max}. - - - Found table with schema: {schema}, name: {name}. - - Found type alias with schema: {schema} name: {alias} which maps to underlying data type {dataType}. - - - Index column {columnName} belongs to index {indexName} on table [{schema}].[{tableName}] which is not included in the selection set. Skipping. - - - Found an index on table [{schemaName}].[{tableName}] with an empty or null name. Skipping index. - - - For foreign key {fkName} on table [{schema}].[{tableName}], unable to model the end of the foreign key on principal table [{principalTableSchema}].[{principalTableName}]. This is usually because the principal table was not included in the selection set. - - - Found a sequence in schema [{schemaName}] with an empty or null name. Skipping sequence. - - - Table [{schema}].[{tableName}] is not included in the selection set. Skipping. + Found type alias with name: {alias} which maps to underlying data type {dataType}. - - For column {columnId} unable to convert default value {defaultValue} into type {propertyType}. Will not generate code setting a default value for the property {propertyName} on entity type {entityTypeName}. - - - Foreign Key {fkName} contains a column named {columnName} which cannot be found on table [{schemaName}].[{tableName}]. Not including column in foreign key. - - - Index {indexName} contains a column named {columnName} which cannot be found on table [{schemaName}].[{tableName}]. Not including column in index. - - - For column {columnName}. Unable to find parent table [{schemaName}].[{tablename}]. Skipping column. + + Found column with table: {tableName}, column name: {columnName}, data type: {dataType}, ordinal: {ordinal}, nullable: {isNullable}, primary key ordinal: {primaryKeyOrdinal}, default value: {defaultValue}, computed value: {computedValue}, precision: {precision}, scale: {scale}, maximum length: {maxLength}, identity: {isIdentity}, computed: {isComputed}. - - For index {indexName}. Unable to find parent table [{schemaName}].[{tableName}]. Skipping index. + + Found foreign key column with table: {tableName}, foreign key name: {fkName}, principal table: {principalTableName}, column name: {columnName}, principal column name: {principalColumnName}, update action: {updateAction}, delete action: {deleteAction}, ordinal: {ordinal}. \ No newline at end of file diff --git a/src/EFCore.SqlServer/Infrastructure/SqlServerEventId.cs b/src/EFCore.SqlServer/Infrastructure/SqlServerEventId.cs index 5c75d691463..2d4f0678466 100644 --- a/src/EFCore.SqlServer/Infrastructure/SqlServerEventId.cs +++ b/src/EFCore.SqlServer/Infrastructure/SqlServerEventId.cs @@ -1,23 +1,46 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Infrastructure { /// - /// Values that are used as the eventId when logging messages from the SQL Server provider via . + /// + /// Event IDs for SQL Server events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// /// - public enum SqlServerEventId + public static class SqlServerEventId { + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Model validation events + DecimalTypeDefaultWarning = CoreEventId.ProviderBaseId, + ByteIdentityColumnWarning + } + + private static readonly string _validationPrefix = LoggerCategory.Model.Validation.Name + "."; + private static EventId MakeValidationId(Id id) => new EventId((int)id, _validationPrefix + id); + /// - /// No explicit type for a decimal column + /// No explicit type for a decimal column. + /// This event is in the category. /// - DefaultDecimalTypeWarning = 1, + public static readonly EventId DecimalTypeDefaultWarning = MakeValidationId(Id.DecimalTypeDefaultWarning); /// - /// A byte property is set up to use a SQL Server identity column + /// A byte property is set up to use a SQL Server identity column. + /// This event is in the category. /// - ByteIdentityColumn = 2 + public static readonly EventId ByteIdentityColumnWarning = MakeValidationId(Id.ByteIdentityColumnWarning); } } diff --git a/src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs index a9bb2867718..d7e9af99dac 100644 --- a/src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs @@ -7,7 +7,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Internal { @@ -31,43 +30,40 @@ public override void Validate(IModel model) protected virtual void EnsureNoDefaultDecimalMapping([NotNull] IModel model) { - foreach (var property in model.GetEntityTypes().SelectMany(t => t.GetDeclaredProperties()) - .Where(p => p.ClrType.UnwrapNullableType() == typeof(decimal) - && p.SqlServer().ColumnType == null)) + foreach (var property in model.GetEntityTypes() + .SelectMany(t => t.GetDeclaredProperties()) + .Where( + p => p.ClrType.UnwrapNullableType() == typeof(decimal) + && p.SqlServer().ColumnType == null)) { - ShowWarning(SqlServerEventId.DefaultDecimalTypeWarning, - SqlServerStrings.DefaultDecimalTypeColumn(property.Name, property.DeclaringEntityType.DisplayName())); + Dependencies.Logger.DecimalTypeDefaultWarning(property); } } protected virtual void EnsureNoByteIdentityMapping([NotNull] IModel model) { - foreach (var property in model.GetEntityTypes().SelectMany(t => t.GetDeclaredProperties()) - .Where(p => p.ClrType.UnwrapNullableType() == typeof(byte) - && p.SqlServer().ValueGenerationStrategy == SqlServerValueGenerationStrategy.IdentityColumn)) + foreach (var property in model.GetEntityTypes() + .SelectMany(t => t.GetDeclaredProperties()) + .Where( + p => p.ClrType.UnwrapNullableType() == typeof(byte) + && p.SqlServer().ValueGenerationStrategy == SqlServerValueGenerationStrategy.IdentityColumn)) { - ShowWarning(SqlServerEventId.ByteIdentityColumn, - SqlServerStrings.ByteIdentityColumn(property.Name, property.DeclaringEntityType.DisplayName())); + Dependencies.Logger.ByteIdentityColumnWarning(property); } } protected virtual void EnsureNoNonKeyValueGeneration([NotNull] IModel model) { - foreach (var property in model.GetEntityTypes().SelectMany(t => t.GetDeclaredProperties()) - .Where(p => - (((SqlServerPropertyAnnotations)p.SqlServer()).GetSqlServerValueGenerationStrategy(fallbackToModel: false) == SqlServerValueGenerationStrategy.SequenceHiLo - || ((SqlServerPropertyAnnotations)p.SqlServer()).GetSqlServerValueGenerationStrategy(fallbackToModel: false) == SqlServerValueGenerationStrategy.IdentityColumn) - && !p.IsKey())) + foreach (var property in model.GetEntityTypes() + .SelectMany(t => t.GetDeclaredProperties()) + .Where( + p => + (((SqlServerPropertyAnnotations)p.SqlServer()).GetSqlServerValueGenerationStrategy(fallbackToModel: false) == SqlServerValueGenerationStrategy.SequenceHiLo + || ((SqlServerPropertyAnnotations)p.SqlServer()).GetSqlServerValueGenerationStrategy(fallbackToModel: false) == SqlServerValueGenerationStrategy.IdentityColumn) + && !p.IsKey())) { ShowError(SqlServerStrings.NonKeyValueGeneration(property.Name, property.DeclaringEntityType.DisplayName())); } } - - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - protected virtual void ShowWarning(SqlServerEventId eventId, [NotNull] string message) - => Dependencies.Logger.LogWarning(eventId, () => message); } } diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerLoggerExtensions.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerLoggerExtensions.cs index d5ac1d37a72..e41ba4395ec 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerLoggerExtensions.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerLoggerExtensions.cs @@ -1,12 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Extensions.Logging; -namespace Microsoft.EntityFrameworkCore.Storage.Internal +namespace Microsoft.EntityFrameworkCore.Internal { public static class SqlServerLoggerExtensions { @@ -14,30 +15,27 @@ public static class SqlServerLoggerExtensions /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static void LogDebug( - [NotNull] this ILogger logger, - SqlServerEventId eventId, - [NotNull] Func formatter) + public static void DecimalTypeDefaultWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IProperty property) { - if (logger.IsEnabled(LogLevel.Debug)) + var eventId = SqlServerEventId.DecimalTypeDefaultWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) { - logger.Log(LogLevel.Debug, (int)eventId, null, null, (_, __) => formatter()); + diagnostics.Logger.LogWarning( + eventId, + SqlServerStrings.DefaultDecimalTypeColumn(property.Name, property.DeclaringEntityType.DisplayName())); } - } - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static void LogDebug( - [NotNull] this ILogger logger, - SqlServerEventId eventId, - [CanBeNull] TState state, - [NotNull] Func formatter) - { - if (logger.IsEnabled(LogLevel.Debug)) + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) { - logger.Log(LogLevel.Debug, (int)eventId, state, null, (s, __) => formatter(s)); + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Property = property + }); } } @@ -45,28 +43,27 @@ public static void LogDebug( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static void LogWarning( - [NotNull] this ILogger logger, - SqlServerEventId eventId, - [NotNull] Func formatter) + public static void ByteIdentityColumnWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IProperty property) { - // Always call Log for Warnings because Warnings as Errors should work even - // if LogLevel.Warning is not enabled. - logger.Log(LogLevel.Warning, (int)eventId, eventId, null, (_, __) => formatter()); - } + var eventId = SqlServerEventId.ByteIdentityColumnWarning; - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static void LogInformation( - [NotNull] this ILogger logger, - SqlServerEventId eventId, - [NotNull] Func formatter) - { - if (logger.IsEnabled(LogLevel.Information)) + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + SqlServerStrings.ByteIdentityColumn(property.Name, property.DeclaringEntityType.DisplayName())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) { - logger.Log(LogLevel.Information, (int)eventId, null, null, (_, __) => formatter()); + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Property = property + }); } } } diff --git a/src/EFCore.Sqlite.Core/Infrastructure/SqliteEventId.cs b/src/EFCore.Sqlite.Core/Infrastructure/SqliteEventId.cs index 2641549ce10..dbcb82d2de1 100644 --- a/src/EFCore.Sqlite.Core/Infrastructure/SqliteEventId.cs +++ b/src/EFCore.Sqlite.Core/Infrastructure/SqliteEventId.cs @@ -1,23 +1,46 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Infrastructure { /// - /// Values that are used as the eventId when logging messages from the SQLite provider via . + /// + /// Event IDs for SQLite events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// /// - public enum SqliteEventId + public static class SqliteEventId { + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Model validation events + SchemaConfiguredWarning = CoreEventId.ProviderBaseId, + SequenceConfiguredWarning + } + + private static readonly string _validationPrefix = LoggerCategory.Model.Validation.Name + "."; + private static EventId MakeValidationId(Id id) => new EventId((int)id, _validationPrefix + id); + /// - /// A schema was configured for an entity type + /// A schema was configured for an entity type, but SQLite does not support schemas. + /// This event is in the category. /// - SchemaConfiguredWarning = 1, + public static readonly EventId SchemaConfiguredWarning = MakeValidationId(Id.SchemaConfiguredWarning); /// - /// A sequence was configured + /// A sequence was configured for an entity type, but SQLite does not support sequences. + /// This event is in the category. /// - SequenceWarning = 2 + public static readonly EventId SequenceConfiguredWarning = MakeValidationId(Id.SequenceConfiguredWarning); } } diff --git a/src/EFCore.Sqlite.Core/Internal/SqliteModelValidator.cs b/src/EFCore.Sqlite.Core/Internal/SqliteModelValidator.cs index 69c4d37fa45..56e72c71973 100644 --- a/src/EFCore.Sqlite.Core/Internal/SqliteModelValidator.cs +++ b/src/EFCore.Sqlite.Core/Internal/SqliteModelValidator.cs @@ -5,7 +5,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Internal @@ -31,9 +30,7 @@ protected virtual void EnsureNoSchemas([NotNull] IModel model) { foreach (var entityType in model.GetEntityTypes().Where(e => e.Sqlite().Schema != null)) { - ShowWarning( - SqliteEventId.SchemaConfiguredWarning, - SqliteStrings.SchemaConfigured(entityType.DisplayName(), entityType.Sqlite().Schema)); + Dependencies.Logger.SchemaConfiguredWarning(entityType, entityType.Sqlite().Schema); } } @@ -41,11 +38,8 @@ protected virtual void EnsureNoSequences([NotNull] IModel model) { foreach (var sequence in model.Sqlite().Sequences) { - ShowWarning(SqliteEventId.SequenceWarning, SqliteStrings.SequenceConfigured(sequence.Name)); + Dependencies.Logger.SequenceConfiguredWarning(sequence); } } - - protected virtual void ShowWarning(SqliteEventId eventId, [NotNull] string message) - => Dependencies.Logger.LogWarning(eventId, () => message); } } diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteLoggerExtensions.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteLoggerExtensions.cs index 5c7c45c2c5e..f6e62f98a12 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteLoggerExtensions.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteLoggerExtensions.cs @@ -1,19 +1,72 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Extensions.Logging; -namespace Microsoft.EntityFrameworkCore.Storage.Internal +namespace Microsoft.EntityFrameworkCore.Internal { public static class SqliteLoggerExtensions { - public static void LogWarning( - [NotNull] this ILogger logger, - SqliteEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Warning, (int)eventId, eventId, null, (_, __) => formatter()); + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SchemaConfiguredWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IEntityType entityType, + [NotNull] string schema) + { + var eventId = SqliteEventId.SchemaConfiguredWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + SqliteStrings.SchemaConfigured(entityType.DisplayName(), schema)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + EntityType = entityType, + Schema = schema + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SequenceConfiguredWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] ISequence sequence) + { + var eventId = SqliteEventId.SequenceConfiguredWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + SqliteStrings.SequenceConfigured(sequence.Name)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + Sequence = sequence + }); + } + } } } diff --git a/src/EFCore.Sqlite.Design/Infrastructure/SqliteDesignEventId.cs b/src/EFCore.Sqlite.Design/Infrastructure/SqliteDesignEventId.cs index c13385f965a..b23986d5936 100644 --- a/src/EFCore.Sqlite.Design/Infrastructure/SqliteDesignEventId.cs +++ b/src/EFCore.Sqlite.Design/Infrastructure/SqliteDesignEventId.cs @@ -1,27 +1,53 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; +using Microsoft.Extensions.Logging; + namespace Microsoft.EntityFrameworkCore.Infrastructure { /// - /// Values that are used as the eventId when logging messages from the SQLite Design Entity Framework Core - /// components. + /// + /// Event IDs for relational design events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// /// - public enum SqliteDesignEventId + public static class SqliteDesignEventId { + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Scaffolding events + ColumnFound = CoreEventId.ProviderDesignBaseId, + ForeignKeyColumnFound, + SchemasNotSupportedWarning + } + + private static readonly string _scaffoldingPrefix = LoggerCategory.Scaffolding.Name + "."; + private static EventId MakeScaffoldingId(Id id) => new EventId((int)id, _scaffoldingPrefix + id); + /// - /// Column name empty on index. + /// A column was found. + /// This event is in the category. /// - IndexMissingColumnNameWarning = 1, + public static readonly EventId ColumnFound = MakeScaffoldingId(Id.ColumnFound); /// - /// Principal column not found. + /// A column of a foreign key was found. + /// This event is in the category. /// - ForeignKeyReferencesMissingColumn, + public static readonly EventId ForeignKeyColumnFound = MakeScaffoldingId(Id.ForeignKeyColumnFound); /// - /// Using schema selections warning. + /// SQLite does not support schemas. + /// This event is in the category. /// - SchemasNotSupportedWarning + public static readonly EventId SchemasNotSupportedWarning = MakeScaffoldingId(Id.SchemasNotSupportedWarning); } } diff --git a/src/EFCore.Sqlite.Design/Internal/SqliteDatabaseModelFactory.cs b/src/EFCore.Sqlite.Design/Internal/SqliteDatabaseModelFactory.cs index 536b6341be9..065c9bbabd1 100644 --- a/src/EFCore.Sqlite.Design/Internal/SqliteDatabaseModelFactory.cs +++ b/src/EFCore.Sqlite.Design/Internal/SqliteDatabaseModelFactory.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Globalization; using System.IO; using JetBrains.Annotations; using Microsoft.Data.Sqlite; @@ -26,7 +27,7 @@ public class SqliteDatabaseModelFactory : IInternalDatabaseModelFactory /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public SqliteDatabaseModelFactory([NotNull] IInterceptingLogger logger) + public SqliteDatabaseModelFactory([NotNull] IDiagnosticsLogger logger) { Check.NotNull(logger, nameof(logger)); @@ -37,7 +38,7 @@ public SqliteDatabaseModelFactory([NotNull] IInterceptingLogger - public virtual IInterceptingLogger Logger { get; } + public virtual IDiagnosticsLogger Logger { get; } private DbConnection _connection; private TableSelectionSet _tableSelectionSet; @@ -136,9 +137,7 @@ private void GetTables() { var name = reader.GetValueOrDefault("name"); - Logger.LogDebug( - RelationalDesignEventId.FoundTable, - () => SqliteDesignStrings.FoundTable(name)); + Logger.TableFound(name); if (_tableSelectionSet.Allows(name)) { @@ -153,9 +152,7 @@ private void GetTables() } else { - Logger.LogDebug( - RelationalDesignEventId.TableSkipped, - () => SqliteDesignStrings.TableNotInSelectionSet(name)); + Logger.TableSkipped(name); } } } @@ -181,11 +178,9 @@ private void GetColumns() var notNull = reader.GetValueOrDefault("notnull"); var defaultValue = reader.GetValueOrDefault("dflt_value"); - Logger.LogDebug( - RelationalDesignEventId.FoundColumn, - () => SqliteDesignStrings.FoundColumn( + Logger.ColumnFound( table.Name, columnName, dataType, ordinal, - notNull, primaryKeyOrdinal, defaultValue)); + notNull, primaryKeyOrdinal, defaultValue); var isPk = primaryKeyOrdinal != 0; var column = new ColumnModel @@ -226,9 +221,7 @@ private void GetIndexes() IsUnique = reader.GetValueOrDefault("unique") }; - Logger.LogDebug( - RelationalDesignEventId.FoundIndex, - () => SqliteDesignStrings.FoundIndex(index.Name, table.Name, index.IsUnique)); + Logger.IndexFound(index.Name, table.Name, index.IsUnique); table.Indexes.Add(index); } @@ -247,16 +240,12 @@ private void GetIndexes() var columnName = reader.GetValueOrDefault("name"); var indexOrdinal = reader.GetValueOrDefault("seqno"); - Logger.LogDebug( - RelationalDesignEventId.FoundIndexColumn, - () => SqliteDesignStrings.FoundIndexColumn( - index.Name, table.Name, columnName, indexOrdinal)); + Logger.IndexColumnFound( + table.Name, index.Name, index.IsUnique, columnName, indexOrdinal); if (string.IsNullOrEmpty(columnName)) { - Logger.LogWarning( - SqliteDesignEventId.IndexMissingColumnNameWarning, - () => SqliteDesignStrings.ColumnNameEmptyOnIndex(index.Name, table.Name)); + Logger.IndexColumnNotNamedWarning(index.Name, table.Name); continue; } @@ -297,11 +286,9 @@ private void GetForeignKeys() var deleteAction = reader.GetValueOrDefault("on_delete"); var fkOrdinal = reader.GetValueOrDefault("seq"); - Logger.LogDebug( - RelationalDesignEventId.FoundForeignKeyColumn, - () => SqliteDesignStrings.FoundForeignKeyColumn( + Logger.ForeignKeyColumnFound( dependentTable.Name, id, principalTableName, fromColumnName, - toColumnName, deleteAction, fkOrdinal)); + toColumnName, deleteAction, fkOrdinal); ForeignKeyModel foreignKey; if (!tableForeignKeys.TryGetValue(id, out foreignKey)) @@ -309,10 +296,7 @@ private void GetForeignKeys() TableModel principalTable; if (!_tables.TryGetValue(principalTableName, out principalTable)) { - Logger.LogDebug( - RelationalDesignEventId.ForeignKeyReferencesMissingTable, - () => SqliteDesignStrings.PrincipalTableNotFound( - id, dependentTable.Name, principalTableName)); + Logger.ForeignKeyReferencesMissingTableWarning(id.ToString(CultureInfo.InvariantCulture)); continue; } @@ -333,10 +317,8 @@ private void GetForeignKeys() ColumnModel toColumn; if (!_tableColumns.TryGetValue(ColumnKey(foreignKey.PrincipalTable, toColumnName), out toColumn)) { - Logger.LogDebug( - SqliteDesignEventId.ForeignKeyReferencesMissingColumn, - () => SqliteDesignStrings.PrincipalColumnNotFound( - id, dependentTable.Name, toColumnName, principalTableName)); + Logger.ForeignKeyPrincipalColumnMissingWarning( + id.ToString(), dependentTable.Name, toColumnName, principalTableName); continue; } fkColumn.PrincipalColumn = toColumn; diff --git a/src/EFCore.Sqlite.Design/Internal/SqliteDesignLoggerExtensions.cs b/src/EFCore.Sqlite.Design/Internal/SqliteDesignLoggerExtensions.cs index ee952d6572b..b4d0ae41d0a 100644 --- a/src/EFCore.Sqlite.Design/Internal/SqliteDesignLoggerExtensions.cs +++ b/src/EFCore.Sqlite.Design/Internal/SqliteDesignLoggerExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.Logging; @@ -18,20 +17,106 @@ public static class SqliteDesignLoggerExtensions /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static void LogWarning( - [NotNull] this ILogger logger, - SqliteDesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Warning, (int)eventId, null, null, (_, __) => formatter()); + public static void ColumnFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName, + [CanBeNull] string columnName, + [CanBeNull] string dataTypeName, + int? ordinal, + bool? notNull, + int? primaryKeyOrdinal, + [CanBeNull] string defaultValue) + { + var eventId = SqliteDesignEventId.ColumnFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + SqliteDesignStrings.FoundColumn( + tableName, columnName, dataTypeName, ordinal, + notNull, primaryKeyOrdinal, defaultValue)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName, + ColumnName = columnName, + DataTypeName = dataTypeName, + Ordinal = ordinal, + NotNull = notNull, + PrimaryKeyOrdinal = primaryKeyOrdinal, + DefaultValue = defaultValue + }); + } + } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static void LogDebug( - [NotNull] this ILogger logger, - SqliteDesignEventId eventId, - [NotNull] Func formatter) - => logger.Log(LogLevel.Debug, (int)eventId, null, null, (_, __) => formatter()); + public static void ForeignKeyColumnFound( + [NotNull] this IDiagnosticsLogger diagnostics, + [CanBeNull] string tableName, + int id, + [CanBeNull] string principalTableName, + [CanBeNull] string columnName, + [CanBeNull] string principalColumnName, + [CanBeNull] string deleteAction, + int? ordinal) + { + var eventId = SqliteDesignEventId.ForeignKeyColumnFound; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + SqliteDesignStrings.FoundForeignKeyColumn( + tableName, id, principalTableName, columnName, + principalColumnName, deleteAction, ordinal)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + TableName = tableName, + Id = id, + PrincipalTableName = principalTableName, + ColumnName = columnName, + PrincipalColumnName = principalColumnName, + DeleteAction = deleteAction, + Ordinal = ordinal + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SchemasNotSupportedWarning( + [NotNull] this IDiagnosticsLogger diagnostics) + { + var eventId = SqliteDesignEventId.SchemasNotSupportedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + SqliteDesignStrings.UsingSchemaSelectionsWarning); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write(eventId.Name, null); + } + } } } diff --git a/src/EFCore.Sqlite.Design/Internal/SqliteScaffoldingModelFactory.cs b/src/EFCore.Sqlite.Design/Internal/SqliteScaffoldingModelFactory.cs index b3950926c58..a1b104f2d6c 100644 --- a/src/EFCore.Sqlite.Design/Internal/SqliteScaffoldingModelFactory.cs +++ b/src/EFCore.Sqlite.Design/Internal/SqliteScaffoldingModelFactory.cs @@ -21,7 +21,7 @@ public class SqliteScaffoldingModelFactory : RelationalScaffoldingModelFactory /// directly from your code. This API may change or be removed in future releases. /// public SqliteScaffoldingModelFactory( - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, [NotNull] IRelationalTypeMapper typeMapper, [NotNull] IDatabaseModelFactory databaseModelFactory, [NotNull] CandidateNamingService candidateNamingService, @@ -39,9 +39,7 @@ public override IModel Create(string connectionString, TableSelectionSet tableSe if (tableSelectionSet != null && tableSelectionSet.Schemas.Any()) { - Logger.LogWarning( - SqliteDesignEventId.SchemasNotSupportedWarning, - () => SqliteDesignStrings.UsingSchemaSelectionsWarning); + Logger.SchemasNotSupportedWarning(); // we've logged a general warning above that sqlite ignores all // schema selections so mark all of them as matched so that we don't diff --git a/src/EFCore.Sqlite.Design/Properties/SqliteDesignStrings.Designer.cs b/src/EFCore.Sqlite.Design/Properties/SqliteDesignStrings.Designer.cs index a0741ef24aa..97d30eac26c 100644 --- a/src/EFCore.Sqlite.Design/Properties/SqliteDesignStrings.Designer.cs +++ b/src/EFCore.Sqlite.Design/Properties/SqliteDesignStrings.Designer.cs @@ -16,12 +16,10 @@ private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.SqliteDesignStrings", typeof(SqliteDesignStrings).GetTypeInfo().Assembly); /// - /// Found a column on index {indexName} on table {tableName} with an empty or null name. Not including column in index. + /// Scaffolding from a SQLite database will ignore any schema selection arguments. /// - public static string ColumnNameEmptyOnIndex([CanBeNull] object indexName, [CanBeNull] object tableName) - => string.Format( - GetString("ColumnNameEmptyOnIndex", nameof(indexName), nameof(tableName)), - indexName, tableName); + public static string UsingSchemaSelectionsWarning + => GetString("UsingSchemaSelectionsWarning"); /// /// Found column on table: {tableName}, column name: {columnName}, data type: {dataType}, ordinal: {ordinal}, not nullable: {isNotNullable}, primary key ordinal: {primaryKeyOrdinal}, default value: {defaultValue}. @@ -39,60 +37,6 @@ public static string FoundForeignKeyColumn([CanBeNull] object tableName, [CanBeN GetString("FoundForeignKeyColumn", nameof(tableName), nameof(id), nameof(principalTableName), nameof(columnName), nameof(principalColumnName), nameof(deleteAction), nameof(ordinal)), tableName, id, principalTableName, columnName, principalColumnName, deleteAction, ordinal); - /// - /// Found index with name: {indexName}, table: {tableName}, is unique: {isUnique}. - /// - public static string FoundIndex([CanBeNull] object indexName, [CanBeNull] object tableName, [CanBeNull] object isUnique) - => string.Format( - GetString("FoundIndex", nameof(indexName), nameof(tableName), nameof(isUnique)), - indexName, tableName, isUnique); - - /// - /// Found index column on index {indexName} on table {tableName}, column name: {columnName}, ordinal: {ordinal}. - /// - public static string FoundIndexColumn([CanBeNull] object indexName, [CanBeNull] object tableName, [CanBeNull] object columnName, [CanBeNull] object ordinal) - => string.Format( - GetString("FoundIndexColumn", nameof(indexName), nameof(tableName), nameof(columnName), nameof(ordinal)), - indexName, tableName, columnName, ordinal); - - /// - /// Found table with name: {name}. - /// - public static string FoundTable([CanBeNull] object name) - => string.Format( - GetString("FoundTable", nameof(name)), - name); - - /// - /// For foreign key with identity {id} on table {tableName}, unable to find the column called {principalColumnName} on the foreign key's principal table, {principalTableName}. Skipping foreign key. - /// - public static string PrincipalColumnNotFound([CanBeNull] object id, [CanBeNull] object tableName, [CanBeNull] object principalColumnName, [CanBeNull] object principalTableName) - => string.Format( - GetString("PrincipalColumnNotFound", nameof(id), nameof(tableName), nameof(principalColumnName), nameof(principalTableName)), - id, tableName, principalColumnName, principalTableName); - - /// - /// For foreign key with identity {id} on table {tableName}, unable to find the principal table {principalTableName}. Either the principal table is missing from the database or it was not included in the selection set. Skipping foreign key. - /// - public static string PrincipalTableNotFound([CanBeNull] object id, [CanBeNull] object tableName, [CanBeNull] object principalTableName) - => string.Format( - GetString("PrincipalTableNotFound", nameof(id), nameof(tableName), nameof(principalTableName)), - id, tableName, principalTableName); - - /// - /// Table {tableName} is not included in the selection set. Skipping. - /// - public static string TableNotInSelectionSet([CanBeNull] object tableName) - => string.Format( - GetString("TableNotInSelectionSet", nameof(tableName)), - tableName); - - /// - /// Scaffolding from a SQLite database will ignore any schema selection arguments. - /// - public static string UsingSchemaSelectionsWarning - => GetString("UsingSchemaSelectionsWarning"); - private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/EFCore.Sqlite.Design/Properties/SqliteDesignStrings.resx b/src/EFCore.Sqlite.Design/Properties/SqliteDesignStrings.resx index d1232adfcba..69c05480d4d 100644 --- a/src/EFCore.Sqlite.Design/Properties/SqliteDesignStrings.resx +++ b/src/EFCore.Sqlite.Design/Properties/SqliteDesignStrings.resx @@ -117,8 +117,8 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Found a column on index {indexName} on table {tableName} with an empty or null name. Not including column in index. + + Scaffolding from a SQLite database will ignore any schema selection arguments. Found column on table: {tableName}, column name: {columnName}, data type: {dataType}, ordinal: {ordinal}, not nullable: {isNotNullable}, primary key ordinal: {primaryKeyOrdinal}, default value: {defaultValue}. @@ -126,25 +126,4 @@ Found foreign key column on table: {tableName}, id: {id}, principal table: {principalTableName}, column name: {columnName}, principal column name: {principalColumnName}, delete action: {deleteAction}, ordinal: {ordinal}. - - Found index with name: {indexName}, table: {tableName}, is unique: {isUnique}. - - - Found index column on index {indexName} on table {tableName}, column name: {columnName}, ordinal: {ordinal}. - - - Found table with name: {name}. - - - For foreign key with identity {id} on table {tableName}, unable to find the column called {principalColumnName} on the foreign key's principal table, {principalTableName}. Skipping foreign key. - - - For foreign key with identity {id} on table {tableName}, unable to find the principal table {principalTableName}. Either the principal table is missing from the database or it was not included in the selection set. Skipping foreign key. - - - Table {tableName} is not included in the selection set. Skipping. - - - Scaffolding from a SQLite database will ignore any schema selection arguments. - \ No newline at end of file diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs index ade3bd5f9a2..11ef7ff3850 100644 --- a/src/EFCore/DbContext.cs +++ b/src/EFCore/DbContext.cs @@ -15,7 +15,6 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -56,7 +55,7 @@ public class DbContext : private DbContextDependencies _dbContextDependencies; private DatabaseFacade _database; private ChangeTracker _changeTracker; - private IInterceptingLogger _updateLogger; + private IDiagnosticsLogger _updateLogger; private IServiceScope _serviceScope; private IDbContextPool _dbContextPool; @@ -213,7 +212,7 @@ private IDbContextServices InitializeServices() contextServices.Initialize(scopedServiceProvider, options, this); - _updateLogger = scopedServiceProvider.GetRequiredService>(); + _updateLogger = scopedServiceProvider.GetRequiredService>(); return contextServices; } @@ -303,11 +302,7 @@ public virtual int SaveChanges(bool acceptAllChangesOnSuccess) } catch (Exception exception) { - _updateLogger.LogError( - CoreEventId.DatabaseError, - () => new DatabaseErrorLogState(GetType()), - exception, - e => CoreStrings.LogExceptionDuringSaveChanges(Environment.NewLine, e)); + _updateLogger.SaveChangesFailed(GetType(), exception); throw; } @@ -379,11 +374,7 @@ public virtual async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, } catch (Exception exception) { - _updateLogger.LogError( - CoreEventId.DatabaseError, - () => new DatabaseErrorLogState(GetType()), - exception, - e => CoreStrings.LogExceptionDuringSaveChanges(Environment.NewLine, e)); + _updateLogger.SaveChangesFailed(GetType(), exception); throw; } diff --git a/src/EFCore/EFCore.csproj b/src/EFCore/EFCore.csproj index f33156a61f3..44545286c7a 100644 --- a/src/EFCore/EFCore.csproj +++ b/src/EFCore/EFCore.csproj @@ -18,6 +18,7 @@ Microsoft.EntityFrameworkCore.DbSet + diff --git a/src/EFCore/Extensions/Internal/CoreLoggerExtensions.cs b/src/EFCore/Extensions/Internal/CoreLoggerExtensions.cs index a750544263f..62e716aea7b 100644 --- a/src/EFCore/Extensions/Internal/CoreLoggerExtensions.cs +++ b/src/EFCore/Extensions/Internal/CoreLoggerExtensions.cs @@ -2,36 +2,441 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; +using Remotion.Linq; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Internal { - internal static class CoreLoggerExtensions + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static class CoreLoggerExtensions { - public static void LogError( - this ILogger logger, CoreEventId eventId, Func state, Exception exception, Func formatter) + private const int QueryModelStringLengthLimit = 100; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SaveChangesFailed( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Type contextType, + [NotNull] Exception exception) + { + var eventId = CoreEventId.SaveChangesFailed; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Error)) + { + diagnostics.Logger.Log( + LogLevel.Error, + eventId, + new DatabaseErrorLogState(contextType), + exception, + (_, e) => CoreStrings.LogExceptionDuringSaveChanges(Environment.NewLine, e)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ContextType = contextType, + Exception = exception + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void QueryIterationFailed( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Type contextType, + [NotNull] Exception exception) { - if (logger.IsEnabled(LogLevel.Error)) + var eventId = CoreEventId.QueryIterationFailed; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Error)) + { + diagnostics.Logger.Log( + LogLevel.Error, + eventId, + new DatabaseErrorLogState(contextType), + exception, + (_, e) => CoreStrings.LogExceptionDuringQueryIteration(Environment.NewLine, e)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) { - logger.Log(LogLevel.Error, (int)eventId, state(), exception, (_, e) => formatter(e)); + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + ContextType = contextType, + Exception = exception + }); } } - public static void LogDebug(this ILogger logger, CoreEventId eventId, Func formatter) + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void QueryModelCompiling( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] QueryModel queryModel) { - if (logger.IsEnabled(LogLevel.Debug)) + var eventId = CoreEventId.QueryModelCompiling; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + CoreStrings.LogCompilingQueryModel(Environment.NewLine, queryModel.Print())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) { - logger.Log(LogLevel.Debug, (int)eventId, null, null, (_, __) => formatter()); + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + QueryModel = queryModel + }); } } - public static void LogWarning(this ILogger logger, CoreEventId eventId, Func formatter) + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void RowLimitingOperationWithoutOrderByWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] QueryModel queryModel) { - // Always call Log for Warnings because Warnings as Errors should work even - // if LogLevel.Warning is not enabled. - logger.Log(LogLevel.Warning, (int)eventId, eventId, null, (_, __) => formatter()); + var eventId = CoreEventId.RowLimitingOperationWithoutOrderByWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + CoreStrings.RowLimitingOperationWithoutOrderBy( + queryModel.Print(removeFormatting: true, characterLimit: QueryModelStringLengthLimit))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + QueryModel = queryModel + }); + } } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void FirstWithoutOrderByAndFilterWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] QueryModel queryModel) + { + var eventId = CoreEventId.FirstWithoutOrderByAndFilterWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + CoreStrings.FirstWithoutOrderByAndFilter( + queryModel.Print(removeFormatting: true, characterLimit: QueryModelStringLengthLimit))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + QueryModel = queryModel + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void QueryModelOptimized( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] QueryModel queryModel) + { + var eventId = CoreEventId.QueryModelOptimized; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + CoreStrings.LogOptimizedQueryModel(Environment.NewLine, queryModel.Print())); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + QueryModel = queryModel + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void NavigationIncluded( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] string includeSpecification) + { + var eventId = CoreEventId.NavigationIncluded; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + CoreStrings.LogIncludingNavigation(includeSpecification)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + IncludeSpecification = includeSpecification + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void QueryExecutionPlanned( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IExpressionPrinter expressionPrinter, + [NotNull] Expression queryExecutorExpression) + { + var eventId = CoreEventId.QueryExecutionPlanned; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + expressionPrinter.Print(queryExecutorExpression)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + QueryExecutorExpression = queryExecutorExpression + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void SensitiveDataLoggingEnabledWarning( + [NotNull] this IDiagnosticsLogger diagnostics) + where TLoggerCategory : LoggerCategory, new() + { + var eventId = CoreEventId.SensitiveDataLoggingEnabledWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + CoreStrings.SensitiveDataLoggingEnabled); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + null); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void IncludeIgnoredWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] string includeSpecification) + { + var eventId = CoreEventId.IncludeIgnoredWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + CoreStrings.LogIgnoredInclude(includeSpecification)); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + IncludeSpecification = includeSpecification + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ModelValidationShadowKeyWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IEntityType entityType, + [NotNull] IKey key) + { + var eventId = CoreEventId.ModelValidationShadowKeyWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + CoreStrings.ShadowKey( + Property.Format(key.Properties), + entityType.DisplayName(), + Property.Format(key.Properties))); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + EntityType = entityType, + Key = key + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ServiceProviderCreated( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IServiceProvider serviceProvider) + { + var eventId = CoreEventId.ServiceProviderCreated; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Debug)) + { + diagnostics.Logger.LogDebug( + eventId, + CoreStrings.ServiceProviderCreated); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + IServiceProvider = serviceProvider + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ManyServiceProvidersCreatedWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] ICollection serviceProviders) + { + var eventId = CoreEventId.ManyServiceProvidersCreatedWarning; + + if (diagnostics.Logger.IsEnabled(eventId, LogLevel.Warning)) + { + diagnostics.Logger.LogWarning( + eventId, + CoreStrings.ManyServiceProvidersCreated); + } + + if (diagnostics.DiagnosticSource.IsEnabled(eventId.Name)) + { + diagnostics.DiagnosticSource.Write( + eventId.Name, + new + { + IServiceProviders = serviceProviders + }); + } + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void LogDebug( + [NotNull] this IInterceptingLogger logger, EventId eventId, [NotNull] string message) + where TLoggerCategory : LoggerCategory, new() + => logger.Log(LogLevel.Debug, eventId, null, null, (_, __) => message); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void LogWarning( + [NotNull] this IInterceptingLogger logger, EventId eventId, [NotNull] string message) + where TLoggerCategory : LoggerCategory, new() + => logger.Log(LogLevel.Warning, eventId, null, null, (_, __) => message); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void LogError( + [NotNull] this IInterceptingLogger logger, EventId eventId, [NotNull] Exception exception, [NotNull] string message) + where TLoggerCategory : LoggerCategory, new() + => logger.Log(LogLevel.Error, eventId, null, exception, (_, __) => message); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void LogInformation( + [NotNull] this IInterceptingLogger logger, EventId eventId, [NotNull] string message) + where TLoggerCategory : LoggerCategory, new() + => logger.Log(LogLevel.Information, eventId, null, null, (_, __) => message); } } diff --git a/src/EFCore/Infrastructure/CoreEventId.cs b/src/EFCore/Infrastructure/CoreEventId.cs index 4cbf7b07736..c4956780666 100644 --- a/src/EFCore/Infrastructure/CoreEventId.cs +++ b/src/EFCore/Infrastructure/CoreEventId.cs @@ -1,62 +1,168 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; +using Microsoft.Extensions.Logging; + namespace Microsoft.EntityFrameworkCore.Infrastructure { /// - /// Values that are used as the eventId when logging messages from the core Entity Framework components. + /// + /// Event IDs for events that correspond to messages logged to an + /// and events sent to a . + /// + /// + /// These IDs are also used to with configure the + /// behavior of warnings. + /// /// - public enum CoreEventId + public static class CoreEventId { /// - /// An error occurred while accessing the database. + /// The lower-bound for event IDs used by any Entity Framework or provider code. + /// + public const int CoreBaseId = 100000; + + /// + /// The lower-bound for event IDs used by any Entity Framework or provider code design-time and tooling. + /// + public const int CoreDesignBaseId = 100000; + + /// + /// The lower-bound for event IDs used by any relational database provider. + /// + public const int RelationalBaseId = 200000; + + /// + /// The lower-bound for event IDs used by any relational database provider design-time and tooling. /// - DatabaseError = 1, + public const int RelationalDesignBaseId = 200000; /// - /// A LINQ query is being compiled. + /// The lower-bound for event IDs used only by database providers. /// - CompilingQueryModel, + public const int ProviderBaseId = 300000; /// - /// An object model representing a LINQ query was optimized. + /// The lower-bound for event IDs used only by database provider design-time and tooling. /// - OptimizedQueryModel, + public const int ProviderDesignBaseId = 300000; + + // Warning: These values must not change between releases. + // Only add new values to the end of sections, never in the middle. + // Try to use naming and be consistent with existing names. + private enum Id + { + // Update events + SaveChangesFailed = CoreBaseId, + + // Query events + QueryIterationFailed = CoreBaseId + 100, + QueryModelCompiling, + RowLimitingOperationWithoutOrderByWarning, + FirstWithoutOrderByAndFilterWarning, + QueryModelOptimized, + NavigationIncluded, + IncludeIgnoredWarning, + QueryExecutionPlanned, + + // Model validation events + ModelValidationShadowKeyWarning = CoreBaseId + 300, + + // Infrastucture events + SensitiveDataLoggingEnabledWarning = CoreBaseId + 400, + ServiceProviderCreated, + ManyServiceProvidersCreatedWarning + } + + private static readonly string _updatePrefix = LoggerCategory.Update.Name + "."; + private static EventId MakeUpdateId(Id id) => new EventId((int)id, _updatePrefix + id); + + /// + /// An error occurred while attempting to save changes to the database. + /// This event is in the category. + /// + public static readonly EventId SaveChangesFailed = MakeUpdateId(Id.SaveChangesFailed); + + private static readonly string _queryPrefix = LoggerCategory.Query.Name + "."; + private static EventId MakeQueryId(Id id) => new EventId((int)id, _queryPrefix + id); /// - /// A navigation property that was included in a LINQ query is being processed. + /// An error occurred while processing the results of a query. + /// This event is in the category. /// - IncludingNavigation, + public static readonly EventId QueryIterationFailed = MakeQueryId(Id.QueryIterationFailed); /// - /// An execution expression was calculated by compiling a LINQ query. + /// A query model is being compiled. + /// This event is in the category. /// - QueryPlan, + public static readonly EventId QueryModelCompiling = MakeQueryId(Id.QueryModelCompiling); /// - /// A query specified an Include operation that was ignored because the included navigation was not reachable in the final query result. + /// A query uses a row limiting operation (Skip/Take) without OrderBy which may lead to unpredictable results. + /// This event is in the category. /// - IncludeIgnoredWarning, + public static readonly EventId RowLimitingOperationWithoutOrderByWarning = MakeQueryId(Id.RowLimitingOperationWithoutOrderByWarning); /// - /// A warning that sensitive data logging is enabled. + /// A query uses First/FirstOrDefault operation without OrderBy and filter which may lead to unpredictable results. + /// This event is in the category. /// - SensitiveDataLoggingEnabledWarning, + public static readonly EventId FirstWithoutOrderByAndFilterWarning = MakeQueryId(Id.FirstWithoutOrderByAndFilterWarning); + + /// + /// A query model was optimized. + /// This event is in the category. + /// + public static readonly EventId QueryModelOptimized = MakeQueryId(Id.QueryModelOptimized); + + /// + /// A navigation was included in the query. + /// This event is in the category. + /// + public static readonly EventId NavigationIncluded = MakeQueryId(Id.NavigationIncluded); + + /// + /// A navigation was ignored while compiling a query. + /// This event is in the category. + /// + public static readonly EventId IncludeIgnoredWarning = MakeQueryId(Id.IncludeIgnoredWarning); + + /// + /// A navigation was ignored while compiling a query. + /// This event is in the category. + /// + public static readonly EventId QueryExecutionPlanned = MakeQueryId(Id.QueryExecutionPlanned); + + private static readonly string _validationPrefix = LoggerCategory.Model.Validation.Name + "."; + private static EventId MakeValidationId(Id id) => new EventId((int)id, _validationPrefix + id); /// /// A warning during model validation indicating a key is configured on shadow properties. + /// This event is in the category. + /// + public static readonly EventId ModelValidationShadowKeyWarning = MakeValidationId(Id.ModelValidationShadowKeyWarning); + + private static readonly string _infraPrefix = LoggerCategory.Infrastructure.Name + "."; + private static EventId MakeInfraId(Id id) => new EventId((int)id, _infraPrefix + id); + + /// + /// A warning indicating that sensitive data logging is enabled and may be logged. + /// This event may be in different categories depending on where sensitive data is being logged. /// - ModelValidationShadowKeyWarning, + public static readonly EventId SensitiveDataLoggingEnabledWarning = MakeInfraId(Id.SensitiveDataLoggingEnabledWarning); /// - /// An 'IServiceProvider' was created for internal use by Entity Framework. + /// A service provider was created for internal use by Entity Framework. + /// This event is in the category. /// - ServiceProviderCreated, + public static readonly EventId ServiceProviderCreated = MakeInfraId(Id.ServiceProviderCreated); /// - /// More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. - /// Consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. + /// Many service proviers were created in a single app domain. + /// This event is in the category. /// - ManyServiceProvidersCreated + public static readonly EventId ManyServiceProvidersCreatedWarning = MakeInfraId(Id.ManyServiceProvidersCreatedWarning); } } diff --git a/src/EFCore/Infrastructure/CoreModelValidator.cs b/src/EFCore/Infrastructure/CoreModelValidator.cs index e439f673db8..757c6d26e42 100644 --- a/src/EFCore/Infrastructure/CoreModelValidator.cs +++ b/src/EFCore/Infrastructure/CoreModelValidator.cs @@ -89,10 +89,7 @@ protected virtual void EnsureNoShadowKeys([NotNull] IModel model) continue; } - ShowWarning(CoreEventId.ModelValidationShadowKeyWarning, CoreStrings.ShadowKey( - Property.Format(key.Properties), - entityType.DisplayName(), - Property.Format(key.Properties))); + Dependencies.Logger.ModelValidationShadowKeyWarning(entityType, key); } } } @@ -271,12 +268,5 @@ protected virtual void EnsureFieldMapping([NotNull] IModel model) } } } - - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - protected virtual void ShowWarning(CoreEventId eventId, [NotNull] string message) - => Dependencies.Logger.LogWarning(eventId, () => message); } } diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index a6114424e1a..00ee5445326 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -75,6 +76,7 @@ private static readonly IDictionary _coreServices { typeof(IExpressionPrinter), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IProjectionExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IInterceptingLogger<>), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IDiagnosticsLogger<>), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IKeyPropagator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(INavigationFixer), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(ILocalViewListener), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -221,6 +223,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(typeof(IInterceptingLogger<>), typeof(InterceptingLogger<>)); + TryAdd(typeof(IDiagnosticsLogger<>), typeof(DiagnosticsLogger<>)); TryAdd(); TryAdd(p => p.GetService()); TryAdd(p => GetContextServices(p).Model); @@ -235,6 +238,9 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(p => p.GetService()); TryAdd(p => p.GetService()); + ServiceCollectionMap + .TryAddSingleton(new DiagnosticListener(LoggerCategory.Root)); + ServiceCollectionMap.GetInfrastructure() .AddDependencySingleton() .AddDependencySingleton() diff --git a/src/EFCore/Infrastructure/IDiagnosticsLogger.cs b/src/EFCore/Infrastructure/IDiagnosticsLogger.cs new file mode 100644 index 00000000000..b524a25e0e3 --- /dev/null +++ b/src/EFCore/Infrastructure/IDiagnosticsLogger.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// Combines and + /// for use by all EF Core logging so that events can be sent to both + /// for ASP.NET and for everything else. + /// + public interface IDiagnosticsLogger + where TLoggerCategory : LoggerCategory, new() + { + /// + /// The . + /// + IInterceptingLogger Logger { get; } + + /// + /// The . + /// + DiagnosticSource DiagnosticSource { get; } + } +} diff --git a/src/EFCore/Infrastructure/IInterceptingLogger.cs b/src/EFCore/Infrastructure/IInterceptingLogger.cs index f007fdcd98d..28cb403a33e 100644 --- a/src/EFCore/Infrastructure/IInterceptingLogger.cs +++ b/src/EFCore/Infrastructure/IInterceptingLogger.cs @@ -1,20 +1,51 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Infrastructure { /// - /// A specialed that intercepts messages such that warnings + /// A specialed logger that intercepts messages such that warnings /// can be either logged or thrown, and such that a decision as to whether to log /// sensitive data or not can be made. /// /// The category of this logger. - // ReSharper disable once UnusedTypeParameter - public interface IInterceptingLogger : ILogger + public interface IInterceptingLogger where TLoggerCategory : LoggerCategory, new() { + /// + /// Writes a log entry. + /// + /// The log level to use. + /// Id of the event. + /// State associated with the event. + /// The exception related to this event, or null if none. + /// Function to create a string message for the event. + void Log( + LogLevel logLevel, + EventId eventId, + [CanBeNull] TState state, + [CanBeNull] Exception exception, + [NotNull] Func formatter); + + /// + /// Checks if the given is enabled or the given event. + /// + /// The event ID that will be logged, if enabled. + /// The logging level to which the event will be logged. + /// true if enabled. + bool IsEnabled(EventId eventId, LogLevel logLevel); + + /// + /// Begins a logical operation scope. + /// + /// The identifier for the scope. + /// An IDisposable that ends the logical operation scope on dispose. + IDisposable BeginScope([CanBeNull] TState state); + /// /// Entity Framework logging options. /// @@ -25,6 +56,6 @@ public interface IInterceptingLogger : ILogger /// to the underlying logger. This also has the side effect of writing a warning /// to the log the first time sensitive data is logged. /// - bool LogSensitiveData { get; } + bool ShouldLogSensitiveData([NotNull] IDiagnosticsLogger diagnostics); } } diff --git a/src/EFCore/Infrastructure/ModelValidatorDependencies.cs b/src/EFCore/Infrastructure/ModelValidatorDependencies.cs index a13e6fd3cdb..cee75d66653 100644 --- a/src/EFCore/Infrastructure/ModelValidatorDependencies.cs +++ b/src/EFCore/Infrastructure/ModelValidatorDependencies.cs @@ -43,7 +43,7 @@ public sealed class ModelValidatorDependencies /// /// /// The logger. - public ModelValidatorDependencies([NotNull] IInterceptingLogger logger) + public ModelValidatorDependencies([NotNull] IDiagnosticsLogger logger) { Check.NotNull(logger, nameof(logger)); @@ -53,14 +53,14 @@ public ModelValidatorDependencies([NotNull] IInterceptingLogger /// The logger. /// - public IInterceptingLogger Logger { get; } + public IDiagnosticsLogger Logger { get; } /// /// Clones this dependency parameter object with one service replaced. /// /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. - public ModelValidatorDependencies With([NotNull] IInterceptingLogger logger) + public ModelValidatorDependencies With([NotNull] IDiagnosticsLogger logger) => new ModelValidatorDependencies(logger); } } diff --git a/src/EFCore/Infrastructure/WarningsConfiguration.cs b/src/EFCore/Infrastructure/WarningsConfiguration.cs index 69abfe8425e..330eaeb2fd7 100644 --- a/src/EFCore/Infrastructure/WarningsConfiguration.cs +++ b/src/EFCore/Infrastructure/WarningsConfiguration.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Infrastructure { @@ -20,8 +21,8 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure /// public class WarningsConfiguration { - private Dictionary _explicitBehaviors - = new Dictionary(); + private Dictionary _explicitBehaviors + = new Dictionary(); private WarningBehavior _defaultBehavior = WarningBehavior.Log; @@ -80,15 +81,15 @@ public virtual WarningsConfiguration WithDefaultBehavior(WarningBehavior warning /// The behavior to set. /// A new instance with the behaviors set. public virtual WarningsConfiguration WithExplicit( - [NotNull] IEnumerable eventIds, WarningBehavior warningBehavior) + [NotNull] IEnumerable eventIds, WarningBehavior warningBehavior) { var clone = Clone(); - clone._explicitBehaviors = new Dictionary(_explicitBehaviors); + clone._explicitBehaviors = new Dictionary(_explicitBehaviors); foreach (var eventId in eventIds) { - clone._explicitBehaviors[eventId] = warningBehavior; + clone._explicitBehaviors[eventId.Id] = warningBehavior; } _serviceProviderHash = null; @@ -100,12 +101,12 @@ public virtual WarningsConfiguration WithExplicit( /// Gets the set for the given event ID, or the /// if no explicit behavior has been set. /// - public virtual WarningBehavior GetBehavior([NotNull] object eventId) + public virtual WarningBehavior? GetBehavior(EventId eventId) { WarningBehavior warningBehavior; - return _explicitBehaviors.TryGetValue(eventId, out warningBehavior) - ? warningBehavior - : _defaultBehavior; + return _explicitBehaviors.TryGetValue(eventId.Id, out warningBehavior) + ? (WarningBehavior?)warningBehavior + : null; } /// @@ -116,8 +117,8 @@ public virtual WarningBehavior GetBehavior([NotNull] object eventId) /// The event ID for which the behavior should be set. /// The behavior to set. /// A new instance with the behavior set, or this instance if a behavior was already set. - public virtual WarningsConfiguration TryWithExplicit([NotNull] object eventId, WarningBehavior warningBehavior) - => _explicitBehaviors.ContainsKey(eventId) + public virtual WarningsConfiguration TryWithExplicit(EventId eventId, WarningBehavior warningBehavior) + => _explicitBehaviors.ContainsKey(eventId.Id) ? this : WithExplicit(new[] { eventId }, warningBehavior); diff --git a/src/EFCore/Infrastructure/WarningsConfigurationBuilder.cs b/src/EFCore/Infrastructure/WarningsConfigurationBuilder.cs index 5d093ee7102..e875c44391a 100644 --- a/src/EFCore/Infrastructure/WarningsConfigurationBuilder.cs +++ b/src/EFCore/Infrastructure/WarningsConfigurationBuilder.cs @@ -2,29 +2,29 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Infrastructure { /// /// - /// Configures the runtime behavior of warnings generated by Entity Framework. You can set a default behavior and behaviors for - /// each warning type. + /// Configures the runtime behavior of warnings generated by Entity Framework. + /// You can set a default behavior and behaviors for each warning type. /// /// - /// This class is used within the + /// This class is used within the + /// /// API and it is not designed to be directly constructed in your application code. /// /// public class WarningsConfigurationBuilder { - private DbContextOptionsBuilder _optionsBuilder; + private readonly DbContextOptionsBuilder _optionsBuilder; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The options builder to which the warnings configuration will be applied. public WarningsConfigurationBuilder([NotNull] DbContextOptionsBuilder optionsBuilder) @@ -34,65 +34,60 @@ public WarningsConfigurationBuilder([NotNull] DbContextOptionsBuilder optionsBui _optionsBuilder = optionsBuilder; } - /// - /// - /// - public virtual DbContextOptionsBuilder OptionsBuilder => _optionsBuilder; - /// /// Sets the default behavior when a warning is generated. /// /// The desired behavior. /// The same builder instance so that multiple calls can be chained. - public virtual WarningsConfigurationBuilder Default(WarningBehavior warningBehavior) + public virtual WarningsConfigurationBuilder Default(WarningBehavior warningBehavior) => WithOption(e => e.WithDefaultBehavior(warningBehavior)); /// /// Causes an exception to be thrown when the specified core warnings are generated. Database providers (and other extensions) /// may provide extension method overloads of this method to configure this behavior for warnings they generate. /// - /// - /// The (s) for the warnings. + /// + /// The and 'RelationalEventId' for the warnings. /// /// The same builder instance so that multiple calls can be chained. public virtual WarningsConfigurationBuilder Throw( - [NotNull] params CoreEventId[] coreEventIds) + [NotNull] params EventId[] eventIds) { - Check.NotNull(coreEventIds, nameof(coreEventIds)); + Check.NotNull(eventIds, nameof(eventIds)); - return WithOption(e => e.WithExplicit(coreEventIds.Cast(), WarningBehavior.Throw)); + return WithOption(e => e.WithExplicit(eventIds, WarningBehavior.Throw)); } /// /// Causes a warning to be logged when the specified core warnings are generated. Database providers (and other extensions) /// may provide extension method overloads of this method to configure this behavior for warnings they generate. /// - /// - /// The (s) for the warnings. + /// + /// The and 'RelationalEventId' for the warnings. /// /// The same builder instance so that multiple calls can be chained. public virtual WarningsConfigurationBuilder Log( - [NotNull] params CoreEventId[] coreEventIds) + [NotNull] params EventId[] eventIds) { - Check.NotNull(coreEventIds, nameof(coreEventIds)); + Check.NotNull(eventIds, nameof(eventIds)); - return WithOption(e => e.WithExplicit(coreEventIds.Cast(), WarningBehavior.Log)); + return WithOption(e => e.WithExplicit(eventIds, WarningBehavior.Log)); } /// /// Causes nothing to happen when the specified core warnings are generated. Database providers (and other extensions) /// may provide extension method overloads of this method to configure this behavior for warnings they generate. /// - /// - /// The (s) for the warnings. + /// + /// The and 'RelationalEventId' for the warnings. /// /// The same builder instance so that multiple calls can be chained. public virtual WarningsConfigurationBuilder Ignore( - [NotNull] params CoreEventId[] coreEventIds) + [NotNull] params EventId[] eventIds) { - Check.NotNull(coreEventIds, nameof(coreEventIds)); + Check.NotNull(eventIds, nameof(eventIds)); - return WithOption(e => e.WithExplicit(coreEventIds.Cast(), WarningBehavior.Ignore)); + return WithOption(e => e.WithExplicit(eventIds, WarningBehavior.Ignore)); } private WarningsConfigurationBuilder WithOption(Func withFunc) diff --git a/src/EFCore/Internal/DiagnosticsLogger.cs b/src/EFCore/Internal/DiagnosticsLogger.cs new file mode 100644 index 00000000000..e7282f4d7d2 --- /dev/null +++ b/src/EFCore/Internal/DiagnosticsLogger.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class DiagnosticsLogger : IDiagnosticsLogger + where TLoggerCategory : LoggerCategory, new() + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public DiagnosticsLogger( + [NotNull] IInterceptingLogger logger, + [NotNull] DiagnosticSource diagnosticSource) + { + Check.NotNull(logger, nameof(logger)); + Check.NotNull(diagnosticSource, nameof(diagnosticSource)); + + Logger = logger; + DiagnosticSource = diagnosticSource; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IInterceptingLogger Logger { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual DiagnosticSource DiagnosticSource { get; } + } +} diff --git a/src/EFCore/Internal/InterceptingLogger.cs b/src/EFCore/Internal/InterceptingLogger.cs index a0fef330fd6..9750944d86e 100644 --- a/src/EFCore/Internal/InterceptingLogger.cs +++ b/src/EFCore/Internal/InterceptingLogger.cs @@ -16,6 +16,7 @@ public class InterceptingLogger : IInterceptingLogger, new() { private readonly ILogger _logger; + private readonly WarningsConfiguration _warningsConfiguration; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -28,6 +29,7 @@ public InterceptingLogger( _logger = loggerFactory.CreateLogger(new TLoggerCategory().ToString()); Options = loggingOptions; + _warningsConfiguration = loggingOptions?.WarningsConfiguration; } /// @@ -47,67 +49,64 @@ public virtual void Log( Exception exception, Func formatter) { - if (logLevel == LogLevel.Warning - && state != null - && Options?.WarningsConfiguration != null) - { - var warningBehavior = Options.WarningsConfiguration.GetBehavior(state); + var warningBehavior = _warningsConfiguration?.GetBehavior(eventId); - if (warningBehavior == WarningBehavior.Throw) + if (warningBehavior != WarningBehavior.Ignore) + { + if (ShouldThrow(logLevel, warningBehavior)) { throw new InvalidOperationException( CoreStrings.WarningAsErrorTemplate( - $"{state.GetType().Name}.{state}", formatter(state, exception))); + eventId.ToString(), formatter(state, exception))); } - if (warningBehavior == WarningBehavior.Log - && IsEnabled(logLevel)) + if (_logger.IsEnabled(logLevel)) { - _logger.Log(logLevel, eventId, state, exception, - (s, _) => CoreStrings.WarningLogTemplate( - formatter(s, _), $"{state.GetType().Name}.{state}")); + _logger.Log(logLevel, eventId, state, exception, formatter); } } - else if (IsEnabled(logLevel)) - { - _logger.Log(logLevel, eventId, state, exception, formatter); - } } + private bool ShouldThrow(LogLevel logLevel, WarningBehavior? warningBehavior) + => warningBehavior == WarningBehavior.Throw + || (logLevel == LogLevel.Warning + && _warningsConfiguration?.DefaultBehavior == WarningBehavior.Throw); + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual bool LogSensitiveData + public virtual bool ShouldLogSensitiveData(IDiagnosticsLogger diagnostics) { - get + var options = Options; + if (options == null) { - var options = Options; - if (options == null) - { - return false; - } - - if (options.SensitiveDataLoggingEnabled - && !options.SensitiveDataLoggingWarned) - { - this.LogWarning( - CoreEventId.SensitiveDataLoggingEnabledWarning, - () => CoreStrings.SensitiveDataLoggingEnabled); + return false; + } - options.SensitiveDataLoggingWarned = true; - } + if (options.SensitiveDataLoggingEnabled + && !options.SensitiveDataLoggingWarned) + { + diagnostics.SensitiveDataLoggingEnabledWarning(); - return options.SensitiveDataLoggingEnabled; + options.SensitiveDataLoggingWarned = true; } + + return options.SensitiveDataLoggingEnabled; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual bool IsEnabled(LogLevel logLevel) - => _logger.IsEnabled(logLevel); + public virtual bool IsEnabled(EventId eventId, LogLevel logLevel) + { + var warningBehavior = _warningsConfiguration?.GetBehavior(eventId); + + return warningBehavior != WarningBehavior.Ignore + && (ShouldThrow(logLevel, warningBehavior) + || _logger.IsEnabled(logLevel)); + } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/Internal/ServiceProviderCache.cs b/src/EFCore/Internal/ServiceProviderCache.cs index 70d4973f4a9..0857cd06e6c 100644 --- a/src/EFCore/Internal/ServiceProviderCache.cs +++ b/src/EFCore/Internal/ServiceProviderCache.cs @@ -96,17 +96,13 @@ public virtual IServiceProvider GetOrAdd([NotNull] IDbContextOptions options, bo .EnsureInitialized(serviceProvider, options); } - var logger = serviceProvider.GetRequiredService>(); + var logger = serviceProvider.GetRequiredService>(); - logger.LogDebug( - CoreEventId.ServiceProviderCreated, - () => CoreStrings.ServiceProviderCreated); + logger.ServiceProviderCreated(serviceProvider); if (_configurations.Count >= 20) { - logger.LogWarning( - CoreEventId.ManyServiceProvidersCreated, - () => CoreStrings.ManyServiceProvidersCreated); + logger.ManyServiceProvidersCreatedWarning(_configurations.Values); } return serviceProvider; diff --git a/src/EFCore/LoggerCategory.cs b/src/EFCore/LoggerCategory.cs index fcedd383174..607fc3316a8 100644 --- a/src/EFCore/LoggerCategory.cs +++ b/src/EFCore/LoggerCategory.cs @@ -14,8 +14,8 @@ namespace Microsoft.EntityFrameworkCore /// LoggerCategory.Database.Sql.Name. /// /// - /// Use these types with to - /// create a logger. + /// Use these types with or + /// to create a logger. /// /// public static class LoggerCategory @@ -37,6 +37,13 @@ public class Connection : LoggerCategory { } + /// + /// Logger category for messages related to data reader operations. + /// + public class DataReader : LoggerCategory + { + } + /// /// Logger category for SQL sent to the database. /// diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 4b3630c74eb..6e42e2f6f65 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -251,12 +251,6 @@ public static string PropertyDoesNotBelong([CanBeNull] object property, [CanBeNu GetString("PropertyDoesNotBelong", nameof(property), nameof(entityType), nameof(expectedType)), property, entityType, expectedType); - /// - /// Cannot change ObservableHashSet during a CollectionChanged event. - /// - public static string ObservableCollectionReentrancy - => GetString("ObservableCollectionReentrancy"); - /// /// The specified field '{field}' could not be found for property '{property}' on entity type '{entityType}'. /// @@ -719,12 +713,6 @@ public static string AnnotationNotFound([CanBeNull] object annotation) GetString("AnnotationNotFound", nameof(annotation)), annotation); - /// - /// The Include operator is not implemented by the current database provider. - /// - public static string IncludeNotImplemented - => GetString("IncludeNotImplemented"); - /// /// The property '{property}' is not a navigation property of entity type '{entityType}'. The 'Include(string)' method can only be used with a '.' separated list of navigation property names. /// @@ -1355,18 +1343,6 @@ public static string DatabaseGeneratedNull([CanBeNull] object property, [CanBeNu GetString("DatabaseGeneratedNull", nameof(property), nameof(entityType)), property, entityType); - /// - /// Sequence contains more than one element - /// - public static string MoreThanOneElement - => GetString("MoreThanOneElement"); - - /// - /// Sequence contains no elements - /// - public static string NoElements - => GetString("NoElements"); - /// /// A parameterless constructor was not found on entity type '{entityType}'. In order to create an instance of '{entityType}' EF requires that a parameterless constructor be declared. /// @@ -1399,14 +1375,6 @@ public static string WarningAsErrorTemplate([CanBeNull] object eventId, [CanBeNu GetString("WarningAsErrorTemplate", nameof(eventId), nameof(message)), eventId, message); - /// - /// {message} To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id '{eventId}'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider. - /// - public static string WarningLogTemplate([CanBeNull] object message, [CanBeNull] object eventId) - => string.Format( - GetString("WarningLogTemplate", nameof(message), nameof(eventId)), - message, eventId); - /// /// Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index f1060d0a0b0..ea0a83186a5 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -207,9 +207,6 @@ The property '{property}' belongs to entity type '{entityType}' but is being used with an instance of entity type '{expectedType}'. - - Cannot change ObservableHashSet during a CollectionChanged event. - The specified field '{field}' could not be found for property '{property}' on entity type '{entityType}'. @@ -387,9 +384,6 @@ The annotation '{annotation}' was not found. Ensure that the annotation has been added. - - The Include operator is not implemented by the current database provider. - The property '{property}' is not a navigation property of entity type '{entityType}'. The 'Include(string)' method can only be used with a '.' separated list of navigation property names. @@ -633,12 +627,6 @@ The database generated a null value for non-nullable property '{property}' of entity type '{entityType}'. Ensure value generation configuration in the database matches the configuration in the model. - - Sequence contains more than one element - - - Sequence contains no elements - A parameterless constructor was not found on entity type '{entityType}'. In order to create an instance of '{entityType}' EF requires that a parameterless constructor be declared. @@ -651,9 +639,6 @@ Warning as error exception for warning '{eventId}': {message} To suppress this Exception use the DbContextOptionsBuilder.ConfigureWarnings API. ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider. - - {message} To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id '{eventId}'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider. - Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. diff --git a/src/EFCore/Query/EntityQueryModelVisitor.cs b/src/EFCore/Query/EntityQueryModelVisitor.cs index da6970a8305..e52c225d624 100644 --- a/src/EFCore/Query/EntityQueryModelVisitor.cs +++ b/src/EFCore/Query/EntityQueryModelVisitor.cs @@ -181,12 +181,9 @@ public virtual Func> CreateQueryExecutor CoreStrings.LogCompilingQueryModel(Environment.NewLine, queryModel.Print())); + QueryCompilationContext.Logger.QueryModelCompiling(queryModel); _blockTaskExpressions = false; @@ -220,12 +217,9 @@ public virtual Func> CreateAsyncQueryExe { Check.NotNull(queryModel, nameof(queryModel)); - using (QueryCompilationContext.Logger.BeginScope(this)) + using (QueryCompilationContext.Logger.Logger.BeginScope(this)) { - QueryCompilationContext.Logger - .LogDebug( - CoreEventId.CompilingQueryModel, - () => CoreStrings.LogCompilingQueryModel(Environment.NewLine, queryModel.Print())); + QueryCompilationContext.Logger.QueryModelCompiling(queryModel); _blockTaskExpressions = false; @@ -316,18 +310,14 @@ protected virtual void OptimizeQueryModel( // Log results - QueryCompilationContext.Logger - .LogDebug( - CoreEventId.OptimizedQueryModel, - () => CoreStrings.LogOptimizedQueryModel(Environment.NewLine, queryModel.Print())); + QueryCompilationContext.Logger.QueryModelOptimized(queryModel); } private class NondeterministicResultCheckingVisitor : QueryModelVisitorBase { - private const int QueryModelStringLengthLimit = 100; - private readonly IInterceptingLogger _logger; + private readonly IDiagnosticsLogger _logger; - public NondeterministicResultCheckingVisitor([NotNull] IInterceptingLogger logger) + public NondeterministicResultCheckingVisitor([NotNull] IDiagnosticsLogger logger) => _logger = logger; public override void VisitQueryModel(QueryModel queryModel) @@ -342,20 +332,14 @@ protected override void VisitResultOperators(ObservableCollection o is SkipResultOperator || o is TakeResultOperator) && !queryModel.BodyClauses.OfType().Any()) { - _logger.LogWarning( - CoreEventId.CompilingQueryModel, - () => CoreStrings.RowLimitingOperationWithoutOrderBy( - queryModel.Print(removeFormatting: true, characterLimit: QueryModelStringLengthLimit))); + _logger.RowLimitingOperationWithoutOrderByWarning(queryModel); } if (resultOperators.Any(o => o is FirstResultOperator) && !queryModel.BodyClauses.OfType().Any() && !queryModel.BodyClauses.OfType().Any()) { - _logger.LogWarning( - CoreEventId.CompilingQueryModel, - () => CoreStrings.FirstWithoutOrderByAndFilter( - queryModel.Print(removeFormatting: true, characterLimit: QueryModelStringLengthLimit))); + _logger.FirstWithoutOrderByAndFilterWarning(queryModel); } base.VisitResultOperators(resultOperators, queryModel); @@ -547,14 +531,7 @@ var queryExecutorExpression } finally { - QueryCompilationContext.Logger.LogDebug( - CoreEventId.QueryPlan, - () => - { - var queryPlan = _expressionPrinter.Print(queryExecutorExpression); - - return queryPlan; - }); + QueryCompilationContext.Logger.QueryExecutionPlanned(_expressionPrinter, queryExecutorExpression); } } diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/ParameterExtractingExpressionVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/ParameterExtractingExpressionVisitor.cs index 92671e2a3f8..a33e7122b50 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/ParameterExtractingExpressionVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/ParameterExtractingExpressionVisitor.cs @@ -29,7 +29,7 @@ public static Expression ExtractParameters( [NotNull] Expression expression, [NotNull] QueryContext queryContext, [NotNull] IEvaluatableExpressionFilter evaluatableExpressionFilter, - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, bool parameterize) { var visitor @@ -44,7 +44,7 @@ var visitor private readonly IEvaluatableExpressionFilter _evaluatableExpressionFilter; private readonly QueryContext _queryContext; - private readonly IInterceptingLogger _logger; + private readonly IDiagnosticsLogger _logger; private readonly bool _parameterize; private PartialEvaluationInfo _partialEvaluationInfo; @@ -58,7 +58,7 @@ var visitor public ParameterExtractingExpressionVisitor( [NotNull] IEvaluatableExpressionFilter evaluatableExpressionFilter, [NotNull] QueryContext queryContext, - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, bool parameterize) { _evaluatableExpressionFilter = evaluatableExpressionFilter; @@ -456,7 +456,7 @@ public virtual object Evaluate([CanBeNull] Expression expression, [CanBeNull] ou catch (Exception exception) { throw new InvalidOperationException( - _logger.LogSensitiveData + _logger.Logger.ShouldLogSensitiveData(_logger) ? CoreStrings.ExpressionParameterizationExceptionSensitive(expression) : CoreStrings.ExpressionParameterizationException, exception); diff --git a/src/EFCore/Query/Internal/AsyncLinqOperatorProvider.cs b/src/EFCore/Query/Internal/AsyncLinqOperatorProvider.cs index 05e92fd52be..3c61a1ac0db 100644 --- a/src/EFCore/Query/Internal/AsyncLinqOperatorProvider.cs +++ b/src/EFCore/Query/Internal/AsyncLinqOperatorProvider.cs @@ -13,7 +13,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Clauses; @@ -107,18 +106,18 @@ private static readonly MethodInfo _interceptExceptions [UsedImplicitly] // ReSharper disable once InconsistentNaming private static IAsyncEnumerable _InterceptExceptions( - IAsyncEnumerable source, Type contextType, IInterceptingLogger logger, QueryContext queryContext) + IAsyncEnumerable source, Type contextType, IDiagnosticsLogger logger, QueryContext queryContext) => new ExceptionInterceptor(source, contextType, logger, queryContext); private sealed class ExceptionInterceptor : IAsyncEnumerable { private readonly IAsyncEnumerable _innerAsyncEnumerable; private readonly Type _contextType; - private readonly IInterceptingLogger _logger; + private readonly IDiagnosticsLogger _logger; private readonly QueryContext _queryContext; public ExceptionInterceptor( - IAsyncEnumerable innerAsyncEnumerable, Type contextType, IInterceptingLogger logger, QueryContext queryContext) + IAsyncEnumerable innerAsyncEnumerable, Type contextType, IDiagnosticsLogger logger, QueryContext queryContext) { _innerAsyncEnumerable = innerAsyncEnumerable; _contextType = contextType; @@ -153,12 +152,7 @@ public async Task MoveNext(CancellationToken cancellationToken) } catch (Exception exception) { - _exceptionInterceptor._logger - .LogError( - CoreEventId.DatabaseError, - () => new DatabaseErrorLogState(_exceptionInterceptor._contextType), - exception, - e => CoreStrings.LogExceptionDuringQueryIteration(Environment.NewLine, e)); + _exceptionInterceptor._logger.QueryIterationFailed(_exceptionInterceptor._contextType, exception); throw; } diff --git a/src/EFCore/Query/Internal/IncludeCompiler.cs b/src/EFCore/Query/Internal/IncludeCompiler.cs index 53343431313..6f1ddec99f9 100644 --- a/src/EFCore/Query/Internal/IncludeCompiler.cs +++ b/src/EFCore/Query/Internal/IncludeCompiler.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Extensions.Internal; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; @@ -109,10 +108,7 @@ public virtual void LogIgnoredIncludes() { foreach (var includeResultOperator in _includeResultOperators) { - _queryCompilationContext.Logger - .LogWarning( - CoreEventId.IncludeIgnoredWarning, - () => CoreStrings.LogIgnoredInclude(includeResultOperator.DisplayString())); + _queryCompilationContext.Logger.IncludeIgnoredWarning(includeResultOperator.DisplayString()); } } @@ -152,10 +148,7 @@ var includeLoadTree includeLoadTree.AddLoadPath(navigationPath); - _queryCompilationContext.Logger - .LogDebug( - CoreEventId.IncludingNavigation, - () => CoreStrings.LogIncludingNavigation(includeResultOperator.DisplayString())); + _queryCompilationContext.Logger.NavigationIncluded(includeResultOperator.DisplayString()); _includeResultOperators.Remove(includeResultOperator); } diff --git a/src/EFCore/Query/Internal/LinqOperatorProvider.cs b/src/EFCore/Query/Internal/LinqOperatorProvider.cs index 198f4d6e642..8679ac21bc6 100644 --- a/src/EFCore/Query/Internal/LinqOperatorProvider.cs +++ b/src/EFCore/Query/Internal/LinqOperatorProvider.cs @@ -11,7 +11,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Clauses; @@ -87,18 +86,18 @@ private static readonly MethodInfo _interceptExceptions [UsedImplicitly] // ReSharper disable once InconsistentNaming private static IEnumerable _InterceptExceptions( - IEnumerable source, Type contextType, IInterceptingLogger logger, QueryContext queryContext) + IEnumerable source, Type contextType, IDiagnosticsLogger logger, QueryContext queryContext) => new ExceptionInterceptor(source, contextType, logger, queryContext); private sealed class ExceptionInterceptor : IEnumerable { private readonly IEnumerable _innerEnumerable; private readonly Type _contextType; - private readonly IInterceptingLogger _logger; + private readonly IDiagnosticsLogger _logger; private readonly QueryContext _queryContext; public ExceptionInterceptor( - IEnumerable innerEnumerable, Type contextType, IInterceptingLogger logger, QueryContext queryContext) + IEnumerable innerEnumerable, Type contextType, IDiagnosticsLogger logger, QueryContext queryContext) { _innerEnumerable = innerEnumerable; _contextType = contextType; @@ -136,12 +135,7 @@ public bool MoveNext() } catch (Exception exception) { - _exceptionInterceptor._logger - .LogError( - CoreEventId.DatabaseError, - () => new DatabaseErrorLogState(_exceptionInterceptor._contextType), - exception, - e => CoreStrings.LogExceptionDuringQueryIteration(Environment.NewLine, e)); + _exceptionInterceptor._logger.QueryIterationFailed(_exceptionInterceptor._contextType, exception); throw; } diff --git a/src/EFCore/Query/Internal/QueryCompilationContextDependencies.cs b/src/EFCore/Query/Internal/QueryCompilationContextDependencies.cs index d711d5639d8..92605531cd9 100644 --- a/src/EFCore/Query/Internal/QueryCompilationContextDependencies.cs +++ b/src/EFCore/Query/Internal/QueryCompilationContextDependencies.cs @@ -22,7 +22,7 @@ public sealed class QueryCompilationContextDependencies /// public QueryCompilationContextDependencies( [NotNull] IModel model, - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, [NotNull] IEntityQueryModelVisitorFactory entityQueryModelVisitorFactory, [NotNull] IRequiresMaterializationExpressionVisitorFactory requiresMaterializationExpressionVisitorFactory, [NotNull] ICurrentDbContext currentContext) @@ -50,7 +50,7 @@ public QueryCompilationContextDependencies( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public IInterceptingLogger Logger { get; } + public IDiagnosticsLogger Logger { get; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -76,7 +76,7 @@ public QueryCompilationContextDependencies( /// public QueryCompilationContextDependencies With([NotNull] IModel model) => new QueryCompilationContextDependencies( - Check.NotNull(model, nameof(model)), + model, Logger, EntityQueryModelVisitorFactory, RequiresMaterializationExpressionVisitorFactory, @@ -86,45 +86,49 @@ public QueryCompilationContextDependencies With([NotNull] IModel model) /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public QueryCompilationContextDependencies With([NotNull] IInterceptingLogger logger) => new QueryCompilationContextDependencies( - Model, - Check.NotNull(logger, nameof(logger)), - EntityQueryModelVisitorFactory, - RequiresMaterializationExpressionVisitorFactory, - CurrentContext); + public QueryCompilationContextDependencies With([NotNull] IDiagnosticsLogger logger) + => new QueryCompilationContextDependencies( + Model, + logger, + EntityQueryModelVisitorFactory, + RequiresMaterializationExpressionVisitorFactory, + CurrentContext); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public QueryCompilationContextDependencies With([NotNull] IEntityQueryModelVisitorFactory entityQueryModelVisitorFactory) => new QueryCompilationContextDependencies( - Model, - Logger, - Check.NotNull(entityQueryModelVisitorFactory, nameof(entityQueryModelVisitorFactory)), - RequiresMaterializationExpressionVisitorFactory, - CurrentContext); + public QueryCompilationContextDependencies With([NotNull] IEntityQueryModelVisitorFactory entityQueryModelVisitorFactory) + => new QueryCompilationContextDependencies( + Model, + Logger, + entityQueryModelVisitorFactory, + RequiresMaterializationExpressionVisitorFactory, + CurrentContext); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public QueryCompilationContextDependencies With( - [NotNull] IRequiresMaterializationExpressionVisitorFactory requiresMaterializationExpressionVisitorFactory) => new QueryCompilationContextDependencies( - Model, - Logger, - EntityQueryModelVisitorFactory, - Check.NotNull(requiresMaterializationExpressionVisitorFactory, nameof(requiresMaterializationExpressionVisitorFactory)), - CurrentContext); + [NotNull] IRequiresMaterializationExpressionVisitorFactory requiresMaterializationExpressionVisitorFactory) + => new QueryCompilationContextDependencies( + Model, + Logger, + EntityQueryModelVisitorFactory, + requiresMaterializationExpressionVisitorFactory, + CurrentContext); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public QueryCompilationContextDependencies With([NotNull] ICurrentDbContext currentContext) => new QueryCompilationContextDependencies( - Model, - Logger, - EntityQueryModelVisitorFactory, - RequiresMaterializationExpressionVisitorFactory, - Check.NotNull(currentContext, nameof(currentContext))); + public QueryCompilationContextDependencies With([NotNull] ICurrentDbContext currentContext) + => new QueryCompilationContextDependencies( + Model, + Logger, + EntityQueryModelVisitorFactory, + RequiresMaterializationExpressionVisitorFactory, + currentContext); } } diff --git a/src/EFCore/Query/Internal/QueryCompiler.cs b/src/EFCore/Query/Internal/QueryCompiler.cs index 56e2ed4cc30..bb5c68e1e55 100644 --- a/src/EFCore/Query/Internal/QueryCompiler.cs +++ b/src/EFCore/Query/Internal/QueryCompiler.cs @@ -40,7 +40,7 @@ private static readonly IEvaluatableExpressionFilter _evaluatableExpressionFilte private readonly ICompiledQueryCache _compiledQueryCache; private readonly ICompiledQueryCacheKeyGenerator _compiledQueryCacheKeyGenerator; private readonly IDatabase _database; - private readonly IInterceptingLogger _logger; + private readonly IDiagnosticsLogger _logger; private readonly INodeTypeProviderFactory _nodeTypeProviderFactory; private readonly Type _contextType; @@ -55,7 +55,7 @@ public QueryCompiler( [NotNull] ICompiledQueryCache compiledQueryCache, [NotNull] ICompiledQueryCacheKeyGenerator compiledQueryCacheKeyGenerator, [NotNull] IDatabase database, - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, [NotNull] INodeTypeProviderFactory nodeTypeProviderFactory, [NotNull] ICurrentDbContext currentContext) { @@ -115,7 +115,7 @@ public virtual Func CreateCompiledQuery(Expressi } private static Func CompileQueryCore( - Expression query, INodeTypeProvider nodeTypeProvider, IDatabase database, IInterceptingLogger logger, Type contextType) + Expression query, INodeTypeProvider nodeTypeProvider, IDatabase database, IDiagnosticsLogger logger, Type contextType) { var queryModel = CreateQueryParser(nodeTypeProvider) @@ -138,12 +138,7 @@ var resultItemType } catch (Exception exception) { - logger - .LogError( - CoreEventId.DatabaseError, - () => new DatabaseErrorLogState(contextType), - exception, - e => CoreStrings.LogExceptionDuringQueryIteration(Environment.NewLine, e)); + logger.QueryIterationFailed(contextType, exception); throw; } @@ -208,7 +203,7 @@ public virtual Func> CreateCompiledAsyncTaskQuery> CreateCompiledSingletonAsyncQuery( - Func> compiledQuery, IInterceptingLogger logger, Type contextType) + Func> compiledQuery, IDiagnosticsLogger logger, Type contextType) => qc => ExecuteSingletonAsyncQuery(qc, compiledQuery, logger, contextType); /// @@ -233,7 +228,7 @@ public virtual Task ExecuteAsync(Expression query, Cancellatio private static async Task ExecuteSingletonAsyncQuery( QueryContext queryContext, Func> compiledQuery, - IInterceptingLogger logger, + IDiagnosticsLogger logger, Type contextType) { try @@ -249,12 +244,7 @@ private static async Task ExecuteSingletonAsyncQuery( } catch (Exception exception) { - logger - .LogError( - CoreEventId.DatabaseError, - () => new DatabaseErrorLogState(contextType), - exception, - e => CoreStrings.LogExceptionDuringQueryIteration(Environment.NewLine, e)); + logger.QueryIterationFailed(contextType, exception); throw; } diff --git a/src/EFCore/Query/QueryCompilationContext.cs b/src/EFCore/Query/QueryCompilationContext.cs index 14bae4ea201..d86ed3a6389 100644 --- a/src/EFCore/Query/QueryCompilationContext.cs +++ b/src/EFCore/Query/QueryCompilationContext.cs @@ -73,7 +73,7 @@ public QueryCompilationContext( /// /// The logger. /// - public virtual IInterceptingLogger Logger { get; } + public virtual IDiagnosticsLogger Logger { get; } /// /// Gets the linq operator provider. diff --git a/src/EFCore/Storage/ExecutionStrategy.cs b/src/EFCore/Storage/ExecutionStrategy.cs index fa82d061826..789cc5e534b 100644 --- a/src/EFCore/Storage/ExecutionStrategy.cs +++ b/src/EFCore/Storage/ExecutionStrategy.cs @@ -95,7 +95,7 @@ protected ExecutionStrategy( /// /// The logger for this . /// - protected virtual IInterceptingLogger Logger { get; } + protected virtual IDiagnosticsLogger Logger { get; } private static readonly AsyncLocal _suspended = new AsyncLocal(); diff --git a/src/EFCore/Storage/ExecutionStrategyContext.cs b/src/EFCore/Storage/ExecutionStrategyContext.cs index 1fbf219db80..b07f5621b5b 100644 --- a/src/EFCore/Storage/ExecutionStrategyContext.cs +++ b/src/EFCore/Storage/ExecutionStrategyContext.cs @@ -36,6 +36,6 @@ public ExecutionStrategyContext([NotNull] ExecutionStrategyContextDependencies d /// /// The logger for the . /// - public virtual IInterceptingLogger Logger => Dependencies.Logger; + public virtual IDiagnosticsLogger Logger => Dependencies.Logger; } } diff --git a/src/EFCore/Storage/ExecutionStrategyContextDependencies.cs b/src/EFCore/Storage/ExecutionStrategyContextDependencies.cs index 59260f6c10f..eac7e1538fe 100644 --- a/src/EFCore/Storage/ExecutionStrategyContextDependencies.cs +++ b/src/EFCore/Storage/ExecutionStrategyContextDependencies.cs @@ -46,7 +46,7 @@ public sealed class ExecutionStrategyContextDependencies public ExecutionStrategyContextDependencies( [NotNull] ICurrentDbContext currentDbContext, [CanBeNull] IDbContextOptions options, - [CanBeNull] IInterceptingLogger logger) + [CanBeNull] IDiagnosticsLogger logger) { Check.NotNull(currentDbContext, nameof(currentDbContext)); @@ -68,7 +68,7 @@ public ExecutionStrategyContextDependencies( /// /// The logger. /// - public IInterceptingLogger Logger { get; } + public IDiagnosticsLogger Logger { get; } /// /// Clones this dependency parameter object with one service replaced. @@ -91,7 +91,7 @@ public ExecutionStrategyContextDependencies With([NotNull] IDbContextOptions opt /// /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. - public ExecutionStrategyContextDependencies With([NotNull] IInterceptingLogger logger) + public ExecutionStrategyContextDependencies With([NotNull] IDiagnosticsLogger logger) => new ExecutionStrategyContextDependencies(CurrentDbContext, Options, logger); } } diff --git a/test/EFCore.Design.Tests/Design/Internal/OperationLoggerTest.cs b/test/EFCore.Design.Tests/Design/Internal/OperationLoggerTest.cs index 4b9c75755b3..df568604a22 100644 --- a/test/EFCore.Design.Tests/Design/Internal/OperationLoggerTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/OperationLoggerTest.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Design.TestUtilities; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.Extensions.Logging; using Xunit; @@ -19,10 +18,10 @@ public void Log_dampens_logLevel_when_ExecutedCommand() var loggerFactory = new LoggerFactory(); loggerFactory.AddProvider(new LoggerProvider(categoryName => new OperationLogger(categoryName, reporter))); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(LoggerCategory.Database.Sql.Name); logger.Log( LogLevel.Information, - (int)RelationalEventId.ExecutedCommand, + RelationalEventId.CommandExecuted, null, null, (_, __) => "-- Can't stop the SQL"); diff --git a/test/EFCore.Design.Tests/DesignEventIdTest.cs b/test/EFCore.Design.Tests/DesignEventIdTest.cs new file mode 100644 index 00000000000..c5dd1f58ac9 --- /dev/null +++ b/test/EFCore.Design.Tests/DesignEventIdTest.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.InMemory.FunctionalTests; +using Microsoft.EntityFrameworkCore.Internal; +using Xunit; +using Microsoft.EntityFrameworkCore.Migrations; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Migrations.Design; +using Microsoft.EntityFrameworkCore.Migrations.Operations; + +namespace Microsoft.EntityFrameworkCore.Design.Tests +{ + public class DesignEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var fakeFactories = new Dictionary> + { + { typeof(Type), () => typeof(object) }, + { typeof(Migration), () => new FakeMigration() }, + { typeof(ModelSnapshot), () => new FakeModelSnapshot() }, + { typeof(ScaffoldedMigration), () => new FakeScaffoldedMigration() }, + { typeof(string), () => "Fake" }, + { typeof(IEnumerable), () => new List() } + }; + + InMemoryTestHelpers.Instance.TestEventLogging(typeof(DesignEventId), typeof(DesignLoggerExtensions), fakeFactories); + } + + private class FakeScaffoldedMigration : ScaffoldedMigration + { + public FakeScaffoldedMigration() + : base("A", "B", "C", "D", "E", "F", "G", "H", "I") + { + } + } + + private class FakeMigration : Migration + { + protected override void Up([NotNull] MigrationBuilder migrationBuilder) => throw new NotImplementedException(); + } + + private class FakeModelSnapshot : ModelSnapshot + { + protected override void BuildModel([NotNull] ModelBuilder modelBuilder) => throw new NotImplementedException(); + } + } +} diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index 8c324f9e6fb..36b15efe4fc 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -62,7 +63,9 @@ private MigrationsScaffolder CreateMigrationScaffolder() idGenerator, new CSharpMigrationsGenerator(code, new CSharpMigrationOperationGenerator(code), new CSharpSnapshotGenerator(code)), new MockHistoryRepository(), - new LoggerFactory().CreateLogger(), + new DiagnosticsLogger( + new InterceptingLogger(new LoggerFactory(), new LoggingOptions()), + new DiagnosticListener("Fake")), new MockProvider()); } diff --git a/test/EFCore.InMemory.FunctionalTests/WarningsTest.cs b/test/EFCore.InMemory.FunctionalTests/WarningsTest.cs index 21568b3ecfe..9eb7df0e7eb 100644 --- a/test/EFCore.InMemory.FunctionalTests/WarningsTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/WarningsTest.cs @@ -23,7 +23,7 @@ var optionsBuilder { Assert.Equal( CoreStrings.WarningAsErrorTemplate( - $"{nameof(InMemoryEventId)}.{nameof(InMemoryEventId.TransactionIgnoredWarning)}", + InMemoryEventId.TransactionIgnoredWarning, InMemoryStrings.TransactionsNotSupported), Assert.Throws( () => context.Database.BeginTransaction()).Message); @@ -51,7 +51,7 @@ public void Throws_when_warning_as_error_all() { Assert.Equal( CoreStrings.WarningAsErrorTemplate( - $"{nameof(CoreEventId)}.{nameof(CoreEventId.IncludeIgnoredWarning)}", + CoreEventId.IncludeIgnoredWarning.ToString(), CoreStrings.LogIgnoredInclude("[e].Nav")), Assert.Throws(() => context.WarningAsErrorEntities.Include(e => e.Nav).OrderBy(e => e.Id).Select(e => e.Id).ToList()).Message); @@ -65,7 +65,7 @@ public void Throws_when_warning_as_error_specific() { Assert.Equal( CoreStrings.WarningAsErrorTemplate( - $"{nameof(CoreEventId)}.{nameof(CoreEventId.IncludeIgnoredWarning)}", + CoreEventId.IncludeIgnoredWarning.ToString(), CoreStrings.LogIgnoredInclude("[e].Nav")), Assert.Throws(() => context.WarningAsErrorEntities.Include(e => e.Nav).Skip(1).Select(e => e.Id).ToList()).Message); @@ -84,9 +84,9 @@ public void No_throw_when_event_id_not_registered() private class WarningAsErrorContext : DbContext { - private readonly CoreEventId[] _eventIds; + private readonly EventId[] _eventIds; - public WarningAsErrorContext(params CoreEventId[] eventIds) + public WarningAsErrorContext(params EventId[] eventIds) { _eventIds = eventIds; } diff --git a/test/EFCore.InMemory.Tests/InMemoryDatabaseCreatorTest.cs b/test/EFCore.InMemory.Tests/InMemoryDatabaseCreatorTest.cs index 49ecad91bbe..8b094588678 100644 --- a/test/EFCore.InMemory.Tests/InMemoryDatabaseCreatorTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryDatabaseCreatorTest.cs @@ -110,7 +110,7 @@ private class FraggleContext : DbContext { public DbSet Fraggles { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseInMemoryDatabase(nameof(FraggleContext)); } @@ -124,11 +124,12 @@ private static IModel CreateModel() { var modelBuilder = new ModelBuilder(new ConventionSet()); - modelBuilder.Entity(b => - { - b.HasKey(c => c.Id); - b.Property(c => c.Name); - }); + modelBuilder.Entity( + b => + { + b.HasKey(c => c.Id); + b.Property(c => c.Name); + }); return modelBuilder.Model; } diff --git a/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs b/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs index 5ad14cc367f..0d6d844ffb9 100644 --- a/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs @@ -151,8 +151,8 @@ public async Task Should_log_writes() mockLogger.Verify( l => l.Log( LogLevel.Information, - (int)InMemoryEventId.SavedChanges, - 1, + InMemoryEventId.ChangesSaved, + null, null, It.IsAny>()), Times.Once); diff --git a/test/EFCore.InMemory.Tests/InMemoryEventIdTest.cs b/test/EFCore.InMemory.Tests/InMemoryEventIdTest.cs new file mode 100644 index 00000000000..3ccaaef8c38 --- /dev/null +++ b/test/EFCore.InMemory.Tests/InMemoryEventIdTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.InMemory.FunctionalTests; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Update; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.InMemory.Tests +{ + public class InMemoryEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var fakeFactories = new Dictionary> + { + { typeof(IEnumerable), () => new List() } + }; + + InMemoryTestHelpers.Instance.TestEventLogging( + typeof(InMemoryEventId), + typeof(InMemoryLoggerExtensions), + fakeFactories); + } + } +} diff --git a/test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs b/test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs index 22696d5f669..dd3842b5bf3 100644 --- a/test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryTransactionManagerTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; @@ -19,7 +20,10 @@ public void CurrentTransaction_returns_null() var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseTransientInMemoryDatabase(); - var transactionManager = new InMemoryTransactionManager(new FakeLogger()); + var transactionManager = new InMemoryTransactionManager( + new DiagnosticsLogger( + new FakeLogger(), + new DiagnosticListener("Fake"))); Assert.Null(transactionManager.CurrentTransaction); } @@ -30,7 +34,10 @@ public void Throws_on_BeginTransaction() var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseTransientInMemoryDatabase(); - var transactionManager = new InMemoryTransactionManager(new FakeLogger()); + var transactionManager = new InMemoryTransactionManager( + new DiagnosticsLogger( + new FakeLogger(), + new DiagnosticListener("Fake"))); Assert.Equal( InMemoryStrings.TransactionsNotSupported, @@ -44,7 +51,10 @@ public async Task Throws_on_BeginTransactionAsync() var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseTransientInMemoryDatabase(); - var transactionManager = new InMemoryTransactionManager(new FakeLogger()); + var transactionManager = new InMemoryTransactionManager( + new DiagnosticsLogger( + new FakeLogger(), + new DiagnosticListener("Fake"))); Assert.Equal( InMemoryStrings.TransactionsNotSupported, @@ -58,7 +68,10 @@ public void Throws_on_CommitTransaction() var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseTransientInMemoryDatabase(); - var transactionManager = new InMemoryTransactionManager(new FakeLogger()); + var transactionManager = new InMemoryTransactionManager( + new DiagnosticsLogger( + new FakeLogger(), + new DiagnosticListener("Fake"))); Assert.Equal( InMemoryStrings.TransactionsNotSupported, @@ -72,7 +85,10 @@ public void Throws_on_RollbackTransaction() var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseTransientInMemoryDatabase(); - var transactionManager = new InMemoryTransactionManager(new FakeLogger()); + var transactionManager = new InMemoryTransactionManager( + new DiagnosticsLogger( + new FakeLogger(), + new DiagnosticListener("Fake"))); Assert.Equal( InMemoryStrings.TransactionsNotSupported, @@ -88,13 +104,13 @@ public void Log( throw new InvalidOperationException(formatter(state, exception)); } - public bool IsEnabled(LogLevel logLevel) => true; + public bool IsEnabled(EventId eventId, LogLevel logLevel) => true; public IDisposable BeginScope(TState state) => null; public ILoggingOptions Options { get; } - public bool LogSensitiveData { get; } + public bool ShouldLogSensitiveData(IDiagnosticsLogger diagnostics) => false; } } } diff --git a/test/EFCore.Relational.Design.Tests/RelationalDesignEventIdTest.cs b/test/EFCore.Relational.Design.Tests/RelationalDesignEventIdTest.cs new file mode 100644 index 00000000000..30676f2589a --- /dev/null +++ b/test/EFCore.Relational.Design.Tests/RelationalDesignEventIdTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Relational.Design +{ + public class RelationalDesignEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var fakeFactories = new Dictionary> + { + { typeof(IList), () => new List { "A", "B" } }, + { typeof(string), () => "Fake" } + }; + + RelationalTestHelpers.Instance.TestEventLogging( + typeof(RelationalDesignEventId), + typeof(RelationalDesignLoggerExtensions), + fakeFactories); + } + } +} diff --git a/test/EFCore.Relational.Design.Tests/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.Relational.Design.Tests/RelationalScaffoldingModelFactoryTest.cs index 51510032a44..4ec58a268c8 100644 --- a/test/EFCore.Relational.Design.Tests/RelationalScaffoldingModelFactoryTest.cs +++ b/test/EFCore.Relational.Design.Tests/RelationalScaffoldingModelFactoryTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Internal; @@ -28,7 +29,10 @@ public RelationalDatabaseModelFactoryTest() { _logger = new TestLogger(); - _factory = new FakeScaffoldingModelFactory(_logger); + _factory = new FakeScaffoldingModelFactory( + new DiagnosticsLogger( + _logger, + new DiagnosticListener("Fake"))); } [Fact] @@ -866,7 +870,12 @@ public void Pluralization_of_entity_and_DbSet() } }; - var factory = new FakeScaffoldingModelFactory(new TestLogger(), new FakePluralizer()); + var factory = new FakeScaffoldingModelFactory( + new DiagnosticsLogger( + new TestLogger(), + new DiagnosticListener("Fake")), + new FakePluralizer()); + var model = factory.Create(info); Assert.Collection(model.GetEntityTypes().OrderBy(t => t.Name).Cast(), @@ -921,7 +930,12 @@ public void Pluralization_of_collection_navigations() Tables = { blogTable, postTable } }; - var factory = new FakeScaffoldingModelFactory(new TestLogger(), new FakePluralizer()); + var factory = new FakeScaffoldingModelFactory( + new DiagnosticsLogger( + new TestLogger(), + new DiagnosticListener("Fake")), + new FakePluralizer()); + var model = factory.Create(info); Assert.Collection(model.GetEntityTypes().OrderBy(t => t.Name).Cast(), @@ -944,12 +958,12 @@ public class FakeScaffoldingModelFactory : RelationalScaffoldingModelFactory public IModel Create(DatabaseModel databaseModel) => CreateFromDatabaseModel(databaseModel); public FakeScaffoldingModelFactory( - [NotNull] IInterceptingLogger logger) + [NotNull] IDiagnosticsLogger logger) : this(logger, new NullPluralizer()) { } public FakeScaffoldingModelFactory( - [NotNull] IInterceptingLogger logger, + [NotNull] IDiagnosticsLogger logger, [NotNull] IPluralizer pluralizer) : base(logger, new TestTypeMapper(new RelationalTypeMapperDependencies()), diff --git a/test/EFCore.Relational.Design.Tests/TestLogger.cs b/test/EFCore.Relational.Design.Tests/TestLogger.cs index 6fef960c7a3..bd45d85f735 100644 --- a/test/EFCore.Relational.Design.Tests/TestLogger.cs +++ b/test/EFCore.Relational.Design.Tests/TestLogger.cs @@ -15,7 +15,7 @@ public class TestLogger : IInterceptingLogger public string FullLog => _sb.ToString(); private readonly StringBuilder _sb = new StringBuilder(); - public bool IsEnabled(LogLevel logLevel) => true; + public bool IsEnabled(EventId eventId, LogLevel logLevel) => true; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) => _sb.Append(logLevel) @@ -24,7 +24,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public ILoggingOptions Options { get; } - public bool LogSensitiveData { get; } + public bool ShouldLogSensitiveData(IDiagnosticsLogger diagnostics) => false; public class NullScope : IDisposable { diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs index a7c2bcf2361..4d8df82c8db 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Internal; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities; @@ -303,8 +304,12 @@ private IRelationalCommand CreateRelationalCommand( string commandText = "Command Text", IReadOnlyList parameters = null) => new RelationalCommand( - logger ?? new FakeInterceptingLogger(), - diagnosticSource ?? new DiagnosticListener("Fake"), + new DiagnosticsLogger( + logger ?? new FakeInterceptingLogger(), + diagnosticSource ?? new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + diagnosticSource ?? new DiagnosticListener("Fake")), commandText, parameters ?? new IRelationalParameter[0]); } diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs index 14b8533b959..a9dcce1d1a3 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities.FakeProvider; @@ -119,8 +120,12 @@ public void MigrationCommandListBuilder_ignores_empty_batches(bool suppressTrans private MigrationCommandListBuilder CreateBuilder() => new MigrationCommandListBuilder( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new FakeRelationalTypeMapper(new RelationalTypeMapperDependencies()))); } } diff --git a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs new file mode 100644 index 00000000000..2bc17e163e3 --- /dev/null +++ b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs @@ -0,0 +1,198 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities; +using Microsoft.EntityFrameworkCore.Storage; +using Remotion.Linq; +using Remotion.Linq.Clauses; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Relational.Tests +{ + public class RelationalEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var constantExpression = Expression.Constant("A"); + var entityType = new EntityType(typeof(object), new Model(new ConventionSet()), ConfigurationSource.Convention); + var property = new Property("A", typeof(int), null, null, entityType, ConfigurationSource.Convention, ConfigurationSource.Convention); + + var queryModel = new QueryModel(new MainFromClause("A", typeof(object), constantExpression), new SelectClause(constantExpression)); + + var fakeFactories = new Dictionary> + { + { typeof(string), () => "Fake" }, + { typeof(IRelationalConnection), () => new FakeRelationalConnection() }, + { typeof(DbCommand), () => new FakeDbCommand() }, + { typeof(DbTransaction), () => new FakeDbTransaction() }, + { typeof(DbDataReader), () => new FakeDbDataReader() }, + { typeof(IMigrator), () => new FakeMigrator() }, + { typeof(Migration), () => new FakeMigration() }, + { typeof(QueryModel), () => queryModel }, + { typeof(MethodCallExpression), () => Expression.Call(constantExpression, typeof(object).GetMethod("ToString")) }, + { typeof(Expression), () => constantExpression }, + { typeof(IProperty), () => property } + }; + + RelationalTestHelpers.Instance.TestEventLogging( + typeof(RelationalEventId), + typeof(RelationalLoggerExtensions), + fakeFactories); + } + + private class FakeMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) => throw new NotImplementedException(); + } + + private class FakeMigrator : IMigrator + { + public void Migrate(string targetMigration = null) => throw new NotImplementedException(); + public Task MigrateAsync(string targetMigration = null, CancellationToken cancellationToken = new CancellationToken()) => throw new NotImplementedException(); + public string GenerateScript(string fromMigration = null, string toMigration = null, bool idempotent = false) => throw new NotImplementedException(); + } + + private class FakeRelationalConnection : IRelationalConnection + { + public string ConnectionString => throw new NotImplementedException(); + public DbConnection DbConnection => new FakeDbConnection(); + public Guid ConnectionId => Guid.NewGuid(); + public int? CommandTimeout { get; set; } + public bool IsMultipleActiveResultSetsEnabled => throw new NotImplementedException(); + public IValueBufferCursor ActiveCursor { get; set; } + public IDbContextTransaction CurrentTransaction => throw new NotImplementedException(); + public IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel) => throw new NotImplementedException(); + public IDbContextTransaction BeginTransaction() => throw new NotImplementedException(); + public Task BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default(CancellationToken)) => throw new NotImplementedException(); + public Task BeginTransactionAsync(CancellationToken cancellationToken = default(CancellationToken)) => throw new NotImplementedException(); + public bool Close() => throw new NotImplementedException(); + public void CommitTransaction() => throw new NotImplementedException(); + public void Dispose() => throw new NotImplementedException(); + public bool Open() => throw new NotImplementedException(); + public Task OpenAsync(CancellationToken cancellationToken = default(CancellationToken)) => throw new NotImplementedException(); + public void Reset() => throw new NotImplementedException(); + public void RollbackTransaction() => throw new NotImplementedException(); + public IDbContextTransaction UseTransaction([CanBeNull] DbTransaction transaction) => throw new NotImplementedException(); + } + + private class FakeDbConnection : DbConnection + { + public override string ConnectionString { get; set; } + public override string Database => "Database"; + public override string DataSource => "DataSource"; + public override string ServerVersion => throw new NotImplementedException(); + public override ConnectionState State => throw new NotImplementedException(); + public override void ChangeDatabase(string databaseName) => throw new NotImplementedException(); + public override void Close() => throw new NotImplementedException(); + public override void Open() => throw new NotImplementedException(); + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => throw new NotImplementedException(); + protected override DbCommand CreateDbCommand() => throw new NotImplementedException(); + } + + private class FakeDbCommand : DbCommand + { + public override string CommandText + { + get => "CommandText"; + set { } + } + + public override int CommandTimeout { get; set; } + public override CommandType CommandType { get; set; } + public override bool DesignTimeVisible { get; set; } + public override UpdateRowSource UpdatedRowSource { get; set; } + protected override DbConnection DbConnection { get; set; } + protected override DbParameterCollection DbParameterCollection => new FakeDbParameterCollection(); + protected override DbTransaction DbTransaction { get; set; } + public override void Cancel() => throw new NotImplementedException(); + public override int ExecuteNonQuery() => throw new NotImplementedException(); + public override object ExecuteScalar() => throw new NotImplementedException(); + public override void Prepare() => throw new NotImplementedException(); + protected override DbParameter CreateDbParameter() => throw new NotImplementedException(); + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => throw new NotImplementedException(); + } + + private class FakeDbParameterCollection : DbParameterCollection + { + public override int Count => 0; + public override object SyncRoot => throw new NotImplementedException(); + public override int Add(object value) => throw new NotImplementedException(); + public override void AddRange(Array values) => throw new NotImplementedException(); + public override void Clear() => throw new NotImplementedException(); + public override bool Contains(object value) => throw new NotImplementedException(); + public override bool Contains(string value) => throw new NotImplementedException(); + public override void CopyTo(Array array, int index) => throw new NotImplementedException(); + public override IEnumerator GetEnumerator() => new List().GetEnumerator(); + public override int IndexOf(object value) => throw new NotImplementedException(); + public override int IndexOf(string parameterName) => throw new NotImplementedException(); + public override void Insert(int index, object value) => throw new NotImplementedException(); + public override void Remove(object value) => throw new NotImplementedException(); + public override void RemoveAt(int index) => throw new NotImplementedException(); + public override void RemoveAt(string parameterName) => throw new NotImplementedException(); + protected override DbParameter GetParameter(int index) => throw new NotImplementedException(); + protected override DbParameter GetParameter(string parameterName) => throw new NotImplementedException(); + protected override void SetParameter(int index, DbParameter value) => throw new NotImplementedException(); + protected override void SetParameter(string parameterName, DbParameter value) => throw new NotImplementedException(); + } + + private class FakeDbTransaction : DbTransaction + { + public override IsolationLevel IsolationLevel => IsolationLevel.Chaos; + protected override DbConnection DbConnection => throw new NotImplementedException(); + public override void Commit() => throw new NotImplementedException(); + public override void Rollback() => throw new NotImplementedException(); + } + + private class FakeDbDataReader : DbDataReader + { + public override bool GetBoolean(int ordinal) => throw new NotImplementedException(); + public override byte GetByte(int ordinal) => throw new NotImplementedException(); + public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => throw new NotImplementedException(); + public override char GetChar(int ordinal) => throw new NotImplementedException(); + public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) => throw new NotImplementedException(); + public override string GetDataTypeName(int ordinal) => throw new NotImplementedException(); + public override DateTime GetDateTime(int ordinal) => throw new NotImplementedException(); + public override decimal GetDecimal(int ordinal) => throw new NotImplementedException(); + public override double GetDouble(int ordinal) => throw new NotImplementedException(); + public override Type GetFieldType(int ordinal) => throw new NotImplementedException(); + public override float GetFloat(int ordinal) => throw new NotImplementedException(); + public override Guid GetGuid(int ordinal) => throw new NotImplementedException(); + public override short GetInt16(int ordinal) => throw new NotImplementedException(); + public override int GetInt32(int ordinal) => throw new NotImplementedException(); + public override long GetInt64(int ordinal) => throw new NotImplementedException(); + public override string GetName(int ordinal) => throw new NotImplementedException(); + public override int GetOrdinal(string name) => throw new NotImplementedException(); + public override string GetString(int ordinal) => throw new NotImplementedException(); + public override object GetValue(int ordinal) => throw new NotImplementedException(); + public override int GetValues(object[] values) => throw new NotImplementedException(); + public override bool IsDBNull(int ordinal) => throw new NotImplementedException(); + public override int FieldCount => throw new NotImplementedException(); + public override object this[int ordinal] => throw new NotImplementedException(); + public override object this[string name] => throw new NotImplementedException(); + public override int RecordsAffected => throw new NotImplementedException(); + public override bool HasRows => throw new NotImplementedException(); + public override bool IsClosed => throw new NotImplementedException(); + public override bool NextResult() => throw new NotImplementedException(); + public override bool Read() => throw new NotImplementedException(); + public override int Depth => throw new NotImplementedException(); + public override IEnumerator GetEnumerator() => throw new NotImplementedException(); + } + } +} diff --git a/test/EFCore.Relational.Tests/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/RelationalModelValidatorTest.cs index 743edeb855f..a7aa0a86129 100644 --- a/test/EFCore.Relational.Tests/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/RelationalModelValidatorTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure.Tests; using Microsoft.EntityFrameworkCore.Internal; @@ -613,9 +614,11 @@ protected class Customer : Person protected override ModelValidator CreateModelValidator() => new RelationalModelValidator( new ModelValidatorDependencies( - new InterceptingLogger( - new ListLoggerFactory(Log, l => l == LoggerCategory.Model.Validation.Name), - new LoggingOptions())), + new DiagnosticsLogger( + new InterceptingLogger( + new ListLoggerFactory(Log, l => l == LoggerCategory.Model.Validation.Name), + new LoggingOptions()), + new DiagnosticListener("Fake"))), new RelationalModelValidatorDependencies( new TestAnnotationProvider(), new TestRelationalTypeMapper( diff --git a/test/EFCore.Relational.Tests/Storage/RawSqlCommandBuilderTest.cs b/test/EFCore.Relational.Tests/Storage/RawSqlCommandBuilderTest.cs index 0ddd9b422ba..ab289aabe93 100644 --- a/test/EFCore.Relational.Tests/Storage/RawSqlCommandBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RawSqlCommandBuilderTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities.FakeProvider; using Microsoft.EntityFrameworkCore.Storage; @@ -17,8 +18,12 @@ public virtual void Builds_RelationalCommand_without_optional_parameters() { var builder = new RawSqlCommandBuilder( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new FakeRelationalTypeMapper(new RelationalTypeMapperDependencies())), new RelationalSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new ParameterNameGeneratorFactory(new ParameterNameGeneratorDependencies())); @@ -34,8 +39,12 @@ public virtual void Builds_RelationalCommand_with_empty_parameter_list() { var builder = new RawSqlCommandBuilder( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new FakeRelationalTypeMapper(new RelationalTypeMapperDependencies())), new RelationalSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new ParameterNameGeneratorFactory(new ParameterNameGeneratorDependencies())); @@ -52,8 +61,12 @@ public virtual void Builds_RelationalCommand_with_parameters() { var builder = new RawSqlCommandBuilder( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new FakeRelationalTypeMapper(new RelationalTypeMapperDependencies())), new RelationalSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new ParameterNameGeneratorFactory(new ParameterNameGeneratorDependencies())); diff --git a/test/EFCore.Relational.Tests/Storage/RelationalCommandBuilderTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalCommandBuilderTest.cs index ba236793943..e71f636f6ae 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalCommandBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalCommandBuilderTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities.FakeProvider; using Microsoft.EntityFrameworkCore.Storage.Internal; @@ -15,8 +16,12 @@ public class RelationalCommandBuilderTest public void Builds_simple_command() { var commandBuilder = new RelationalCommandBuilder( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new FakeRelationalTypeMapper(new RelationalTypeMapperDependencies())); var command = commandBuilder.Build(); @@ -29,8 +34,12 @@ public void Builds_simple_command() public void Build_command_with_parameter() { var commandBuilder = new RelationalCommandBuilder( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new FakeRelationalTypeMapper(new RelationalTypeMapperDependencies())); commandBuilder.ParameterBuilder.AddParameter( diff --git a/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs index 68a2480ca6b..9a779bea2ab 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs @@ -6,6 +6,7 @@ using System.Data; using System.Data.Common; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; @@ -349,38 +350,44 @@ public static TheoryData CommandActions => new TheoryData { { - new CommandAction((connection, command, parameterValues) - => command.ExecuteNonQuery(connection, parameterValues)), + new CommandAction( + (connection, command, parameterValues) + => command.ExecuteNonQuery(connection, parameterValues)), "ExecuteNonQuery", false }, { - new CommandAction((connection, command, parameterValues) - => command.ExecuteScalar(connection, parameterValues)), + new CommandAction( + (connection, command, parameterValues) + => command.ExecuteScalar(connection, parameterValues)), "ExecuteScalar", false }, { - new CommandAction((connection, command, parameterValues) - => command.ExecuteReader(connection, parameterValues)), + new CommandAction( + (connection, command, parameterValues) + => command.ExecuteReader(connection, parameterValues)), "ExecuteReader", false }, { - new CommandFunc((connection, command, parameterValues) - => command.ExecuteNonQueryAsync(connection, parameterValues)), + new CommandFunc( + (connection, command, parameterValues) + => command.ExecuteNonQueryAsync(connection, parameterValues)), "ExecuteNonQuery", true }, { - new CommandFunc((connection, command, parameterValues) - => command.ExecuteScalarAsync(connection, parameterValues)), + new CommandFunc( + (connection, command, parameterValues) + => command.ExecuteScalarAsync(connection, parameterValues)), "ExecuteScalar", true }, { - new CommandFunc((connection, command, parameterValues) - => command.ExecuteReaderAsync(connection, parameterValues)), + new CommandFunc( + (connection, command, parameterValues) + => command.ExecuteReaderAsync(connection, parameterValues)), "ExecuteReader", true } @@ -407,15 +414,18 @@ public async Task Throws_when_parameters_are_configured_and_parameter_values_is_ { Assert.Equal( RelationalStrings.MissingParameterValue("FirstInvariant"), - (await Assert.ThrowsAsync(async () - => await ((CommandFunc)commandDelegate)(fakeConnection, relationalCommand, null))).Message); + (await Assert.ThrowsAsync( + async () + => await ((CommandFunc)commandDelegate)(fakeConnection, relationalCommand, null))).Message); } else { Assert.Equal( RelationalStrings.MissingParameterValue("FirstInvariant"), - Assert.Throws(() - => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, null)).Message); + Assert.Throws( + () + => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, null)) + .Message); } } @@ -446,15 +456,18 @@ public async Task Throws_when_parameters_are_configured_and_value_is_missing( { Assert.Equal( RelationalStrings.MissingParameterValue("ThirdInvariant"), - (await Assert.ThrowsAsync(async () - => await ((CommandFunc)commandDelegate)(fakeConnection, relationalCommand, parameterValues))).Message); + (await Assert.ThrowsAsync( + async () + => await ((CommandFunc)commandDelegate)(fakeConnection, relationalCommand, parameterValues))).Message); } else { Assert.Equal( RelationalStrings.MissingParameterValue("ThirdInvariant"), - Assert.Throws(() - => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues)).Message); + Assert.Throws( + () + => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues)) + .Message); } } @@ -679,15 +692,18 @@ public async Task Throws_when_composite_parameters_are_configured_and_value_is_m { Assert.Equal( RelationalStrings.MissingParameterValue("ThirdInvariant"), - (await Assert.ThrowsAsync(async () - => await ((CommandFunc)commandDelegate)(fakeConnection, relationalCommand, parameterValues))).Message); + (await Assert.ThrowsAsync( + async () + => await ((CommandFunc)commandDelegate)(fakeConnection, relationalCommand, parameterValues))).Message); } else { Assert.Equal( RelationalStrings.MissingParameterValue("ThirdInvariant"), - Assert.Throws(() - => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues)).Message); + Assert.Throws( + () + => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues)) + .Message); } } @@ -720,15 +736,18 @@ public async Task Throws_when_composite_parameters_are_configured_and_value_is_n { Assert.Equal( RelationalStrings.ParameterNotObjectArray("CompositeInvariant"), - (await Assert.ThrowsAsync(async () - => await ((CommandFunc)commandDelegate)(fakeConnection, relationalCommand, parameterValues))).Message); + (await Assert.ThrowsAsync( + async () + => await ((CommandFunc)commandDelegate)(fakeConnection, relationalCommand, parameterValues))).Message); } else { Assert.Equal( RelationalStrings.ParameterNotObjectArray("CompositeInvariant"), - Assert.Throws(() - => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues)).Message); + Assert.Throws( + () + => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues)) + .Message); } } @@ -767,8 +786,9 @@ await Assert.ThrowsAsync( } else { - Assert.Throws(() - => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, null)); + Assert.Throws( + () + => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, null)); } Assert.Equal(1, fakeDbConnection.DbCommands[0].DisposeCount); @@ -811,8 +831,9 @@ await Assert.ThrowsAsync( } else { - Assert.Throws(() - => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, null)); + Assert.Throws( + () + => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, null)); Assert.Equal(1, fakeDbConnection.OpenCount); } @@ -857,8 +878,9 @@ await Assert.ThrowsAsync( } else { - Assert.Throws(() - => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, null)); + Assert.Throws( + () + => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, null)); Assert.Equal(1, fakeDbConnection.OpenCount); } @@ -903,12 +925,18 @@ public async Task Logs_commands_without_parameter_values( ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues); } - Assert.Equal(1, log.Count); - Assert.Equal(LogLevel.Information, log[0].Item1); - Assert.EndsWith( - @"[Parameters=[FirstParameter='?'], CommandType='0', CommandTimeout='30'] + Assert.Equal(2, log.Count); + + Assert.Equal(LogLevel.Debug, log[0].Item1); + Assert.Equal(LogLevel.Information, log[1].Item1); + + foreach (var item in log) + { + Assert.EndsWith( + @"[Parameters=[FirstParameter='?'], CommandType='0', CommandTimeout='30'] Logged Command", - log[0].Item2.Replace(Environment.NewLine, FileLineEnding)); + item.Item2.Replace(Environment.NewLine, FileLineEnding)); + } } [Theory] @@ -950,15 +978,20 @@ public async Task Logs_commands_parameter_values( ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues); } - Assert.Equal(2, log.Count); + Assert.Equal(3, log.Count); Assert.Equal(LogLevel.Warning, log[0].Item1); Assert.Equal(CoreStrings.SensitiveDataLoggingEnabled, log[0].Item2); - Assert.Equal(LogLevel.Information, log[1].Item1); - Assert.EndsWith( - @"ms) [Parameters=[FirstParameter='17'], CommandType='0', CommandTimeout='30'] + Assert.Equal(LogLevel.Debug, log[1].Item1); + Assert.Equal(LogLevel.Information, log[2].Item1); + + foreach (var item in log.Skip(1)) + { + Assert.EndsWith( + @"[Parameters=[FirstParameter='17'], CommandType='0', CommandTimeout='30'] Logged Command", - log[1].Item2.Replace(Environment.NewLine, FileLineEnding)); + item.Item2.Replace(Environment.NewLine, FileLineEnding)); + } } [Theory] @@ -996,8 +1029,8 @@ public async Task Reports_command_diagnostic( } Assert.Equal(2, diagnostic.Count); - Assert.Equal(RelationalDiagnostics.BeforeExecuteCommand, diagnostic[0].Item1); - Assert.Equal(RelationalDiagnostics.AfterExecuteCommand, diagnostic[1].Item1); + Assert.Equal(RelationalEventId.CommandExecuting.Name, diagnostic[0].Item1); + Assert.Equal(RelationalEventId.CommandExecuted.Name, diagnostic[1].Item1); var beforeData = (RelationalDiagnosticSourceBeforeMessage)diagnostic[0].Item2; var afterData = (RelationalDiagnosticSourceAfterMessage)diagnostic[1].Item2; @@ -1059,13 +1092,14 @@ await Assert.ThrowsAsync( } else { - Assert.Throws(() - => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues)); + Assert.Throws( + () + => ((CommandAction)commandDelegate)(fakeConnection, relationalCommand, parameterValues)); } Assert.Equal(2, diagnostic.Count); - Assert.Equal(RelationalDiagnostics.BeforeExecuteCommand, diagnostic[0].Item1); - Assert.Equal(RelationalDiagnostics.CommandExecutionError, diagnostic[1].Item1); + Assert.Equal(RelationalEventId.CommandExecuting.Name, diagnostic[0].Item1); + Assert.Equal(RelationalEventId.CommandError.Name, diagnostic[1].Item1); var beforeData = (RelationalDiagnosticSourceBeforeMessage)diagnostic[0].Item2; var afterData = (RelationalDiagnosticSourceAfterMessage)diagnostic[1].Item2; @@ -1093,8 +1127,9 @@ private static IDbContextOptions CreateOptions( var optionsBuilder = new DbContextOptionsBuilder(); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder) - .AddOrUpdateExtension(optionsExtension - ?? new FakeRelationalOptionsExtension().WithConnectionString(ConnectionString)); + .AddOrUpdateExtension( + optionsExtension + ?? new FakeRelationalOptionsExtension().WithConnectionString(ConnectionString)); return optionsBuilder.Options; } @@ -1118,15 +1153,19 @@ public void Validate(IDbContextOptions options) public bool SensitiveDataLoggingWarned { get; set; } public WarningsConfiguration WarningsConfiguration => null; } - + private IRelationalCommand CreateRelationalCommand( IInterceptingLogger logger = null, DiagnosticSource diagnosticSource = null, string commandText = "Command Text", IReadOnlyList parameters = null) => new RelationalCommand( - logger ?? new FakeInterceptingLogger(), - diagnosticSource ?? new DiagnosticListener("Fake"), + new DiagnosticsLogger( + logger ?? new FakeInterceptingLogger(), + diagnosticSource ?? new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + diagnosticSource ?? new DiagnosticListener("Fake")), commandText, parameters ?? new IRelationalParameter[0]); } diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTransactionExtensionsTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalTransactionExtensionsTest.cs index 00c6051a269..c944dc2dc41 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTransactionExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTransactionExtensionsTest.cs @@ -25,11 +25,14 @@ public void GetDbTransaction_returns_the_DbTransaction() var connection = new FakeRelationalConnection( CreateOptions((FakeRelationalOptionsExtension)new FakeRelationalOptionsExtension().WithConnection(dbConnection))); + var loggerFactory = new ListLoggerFactory(new List>()); + var transaction = new RelationalTransaction( connection, dbTransaction, - new InterceptingLogger(new ListLoggerFactory(new List>()), new LoggingOptions()), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new InterceptingLogger(loggerFactory, new LoggingOptions()), + new DiagnosticListener("Fake")), false); Assert.Equal(dbTransaction, transaction.GetDbTransaction()); diff --git a/test/EFCore.Relational.Tests/TestUtilities/FakeInterceptingLogger.cs b/test/EFCore.Relational.Tests/TestUtilities/FakeInterceptingLogger.cs index 00a6d3b58fb..fa62d132955 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/FakeInterceptingLogger.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/FakeInterceptingLogger.cs @@ -12,13 +12,13 @@ public class FakeInterceptingLogger : IInterceptingLogger { public ILoggingOptions Options { get; } - public bool LogSensitiveData { get; } + public bool ShouldLogSensitiveData(IDiagnosticsLogger diagnostics) => false; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } - public bool IsEnabled(LogLevel logLevel) => true; + public bool IsEnabled(EventId eventId, LogLevel logLevel) => true; public IDisposable BeginScope(object state) { diff --git a/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs b/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs index 2e28ebb857a..4507f837658 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs @@ -18,9 +18,16 @@ public class FakeRelationalConnection : RelationalConnection public FakeRelationalConnection(IDbContextOptions options) : base(new RelationalConnectionDependencies( options, - new InterceptingLogger(new LoggerFactory(), new LoggingOptions()), - new InterceptingLogger(new LoggerFactory(), new LoggingOptions()), - new DiagnosticListener("FakeDiagnosticListener"))) + new DiagnosticsLogger( + new InterceptingLogger( + new LoggerFactory(), + new LoggingOptions()), + new DiagnosticListener("FakeDiagnosticListener")), + new DiagnosticsLogger( + new InterceptingLogger( + new LoggerFactory(), + new LoggingOptions()), + new DiagnosticListener("FakeDiagnosticListener")))) { } diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index f0ab65c9c86..6e6b42c9f07 100644 --- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs +++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs @@ -570,8 +570,12 @@ public ModificationCommandBatchFake( IUpdateSqlGenerator sqlGenerator = null) : base( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new FakeRelationalTypeMapper(new RelationalTypeMapperDependencies())), new RelationalSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), sqlGenerator ?? new FakeSqlGenerator(), diff --git a/test/EFCore.SqlServer.Design.FunctionalTests/ReverseEngineering/SqlServerE2ETests.cs b/test/EFCore.SqlServer.Design.FunctionalTests/ReverseEngineering/SqlServerE2ETests.cs index d373f834abf..b94db23e000 100644 --- a/test/EFCore.SqlServer.Design.FunctionalTests/ReverseEngineering/SqlServerE2ETests.cs +++ b/test/EFCore.SqlServer.Design.FunctionalTests/ReverseEngineering/SqlServerE2ETests.cs @@ -116,10 +116,13 @@ public void E2ETest_UseAttributesInsteadOfFluentApi() .Concat(_expectedEntityTypeFiles).ToList() }; + var indexWarn = _reporter.Messages.Warn.Single(m => m.Contains("PK__Filtered__")); + AssertLog(new LoggerMessages { Warn = { + indexWarn, RelationalDesignStrings.CannotFindTypeMappingForColumn("dbo.AllDataTypes.geographyColumn", "geography"), RelationalDesignStrings.CannotFindTypeMappingForColumn("dbo.AllDataTypes.geometryColumn", "geometry"), RelationalDesignStrings.CannotFindTypeMappingForColumn("dbo.AllDataTypes.hierarchyidColumn", "hierarchyid"), @@ -164,10 +167,13 @@ public void E2ETest_AllFluentApi() .Concat(_expectedEntityTypeFiles).ToList() }; + var indexWarn = _reporter.Messages.Warn.Single(m => m.Contains("PK__Filtered__")); + AssertLog(new LoggerMessages { Warn = { + indexWarn, RelationalDesignStrings.CannotFindTypeMappingForColumn("dbo.AllDataTypes.geographyColumn", "geography"), RelationalDesignStrings.CannotFindTypeMappingForColumn("dbo.AllDataTypes.geometryColumn", "geometry"), RelationalDesignStrings.CannotFindTypeMappingForColumn("dbo.AllDataTypes.hierarchyidColumn", "hierarchyid"), diff --git a/test/EFCore.SqlServer.Design.FunctionalTests/SqlServerDatabaseModelFactoryTest.cs b/test/EFCore.SqlServer.Design.FunctionalTests/SqlServerDatabaseModelFactoryTest.cs index d7c058d13f2..3069f309532 100644 --- a/test/EFCore.SqlServer.Design.FunctionalTests/SqlServerDatabaseModelFactoryTest.cs +++ b/test/EFCore.SqlServer.Design.FunctionalTests/SqlServerDatabaseModelFactoryTest.cs @@ -73,7 +73,7 @@ public void It_filters_views() var dbModel = CreateModel(sql, selectionSet, logger); Assert.Single(dbModel.Tables); - Assert.DoesNotContain(logger.Items, i => i.logLevel == LogLevel.Warning); + Assert.DoesNotContain(logger.Items, i => ((string)i.message).Contains("ShortHills")); } [Fact] diff --git a/test/EFCore.SqlServer.Design.FunctionalTests/SqlServerDatabaseModelFixture.cs b/test/EFCore.SqlServer.Design.FunctionalTests/SqlServerDatabaseModelFixture.cs index f63b8ec2019..37ddb54eb88 100644 --- a/test/EFCore.SqlServer.Design.FunctionalTests/SqlServerDatabaseModelFixture.cs +++ b/test/EFCore.SqlServer.Design.FunctionalTests/SqlServerDatabaseModelFixture.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -24,9 +25,11 @@ public DatabaseModel CreateModel(string createSql, TableSelectionSet selection = TestStore.ExecuteNonQuery(createSql); return new SqlServerDatabaseModelFactory( - new InterceptingLogger( - new TestLoggerFactory(logger), - new LoggingOptions())) + new DiagnosticsLogger( + new InterceptingLogger( + new TestLoggerFactory(logger), + new LoggingOptions()), + new DiagnosticListener("Fake"))) .Create(TestStore.ConnectionString, selection ?? TableSelectionSet.All); } @@ -70,7 +73,7 @@ public class TestLogger : ILogger public IDisposable BeginScope(TState state) => NullScope.Instance; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - => Items.Add(new { logLevel, eventId, state, exception }); + => Items.Add(new { logLevel, eventId, state, exception, message = formatter(state, exception) }); public bool IsEnabled(LogLevel logLevel) => true; diff --git a/test/EFCore.SqlServer.Design.Tests/SqlServerDesignEventIdTest.cs b/test/EFCore.SqlServer.Design.Tests/SqlServerDesignEventIdTest.cs new file mode 100644 index 00000000000..ebe12d90c86 --- /dev/null +++ b/test/EFCore.SqlServer.Design.Tests/SqlServerDesignEventIdTest.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Design +{ + public class SqlServerDesignEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var fakeFactories = new Dictionary> + { + { typeof(string), () => "Fake" } + }; + + SqlServerTestHelpers.Instance.TestEventLogging( + typeof(SqlServerDesignEventId), + typeof(SqlServerDesignLoggerExtensions), + fakeFactories); + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs index 187d7be7891..0d5fd1a41a0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs @@ -34,7 +34,7 @@ public virtual ModelBuilder Default_for_key_string_column_throws() Assert.Equal( CoreStrings.WarningAsErrorTemplate( - nameof(RelationalEventId) + "." + nameof(RelationalEventId.ModelValidationKeyDefaultValueWarning), + RelationalEventId.ModelValidationKeyDefaultValueWarning, RelationalStrings.KeyHasDefaultValue(nameof(Login1.UserName), nameof(Login1))), Assert.Throws(() => Validate(modelBuilder)).Message); diff --git a/test/EFCore.SqlServer.FunctionalTests/Utilities/SqlServerDatabaseCleaner.cs b/test/EFCore.SqlServer.FunctionalTests/Utilities/SqlServerDatabaseCleaner.cs index 99bd93c9155..03a0a98edfa 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Utilities/SqlServerDatabaseCleaner.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Utilities/SqlServerDatabaseCleaner.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Operations; @@ -15,9 +16,11 @@ public class SqlServerDatabaseCleaner : RelationalDatabaseCleaner { protected override IInternalDatabaseModelFactory CreateDatabaseModelFactory(ILoggerFactory loggerFactory) => new SqlServerDatabaseModelFactory( - new InterceptingLogger( - loggerFactory, - new LoggingOptions())); + new DiagnosticsLogger( + new InterceptingLogger( + loggerFactory, + new LoggingOptions()), + new DiagnosticListener("Fake"))); protected override bool AcceptIndex(IndexModel index) => false; diff --git a/test/EFCore.SqlServer.FunctionalTests/Utilities/TestRelationalCommandBuilderFactory.cs b/test/EFCore.SqlServer.FunctionalTests/Utilities/TestRelationalCommandBuilderFactory.cs index d80b000ba70..e04761d7ebc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Utilities/TestRelationalCommandBuilderFactory.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Utilities/TestRelationalCommandBuilderFactory.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -14,38 +13,35 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests.Utilities { public class TestRelationalCommandBuilderFactory : IRelationalCommandBuilderFactory { - private readonly IInterceptingLogger _logger; - private readonly DiagnosticSource _diagnosticSource; + private readonly IDiagnosticsLogger _logger; + private readonly IDiagnosticsLogger _readerLogger; private readonly IRelationalTypeMapper _typeMapper; public TestRelationalCommandBuilderFactory( - IInterceptingLogger logger, - DiagnosticSource diagnosticSource, + IDiagnosticsLogger logger, + IDiagnosticsLogger readerLogger, IRelationalTypeMapper typeMapper) { _logger = logger; - _diagnosticSource = diagnosticSource; + _readerLogger = readerLogger; _typeMapper = typeMapper; } public virtual IRelationalCommandBuilder Create() - => new TestRelationalCommandBuilder( - _logger, - _diagnosticSource, - _typeMapper); + => new TestRelationalCommandBuilder(_logger, _readerLogger, _typeMapper); private class TestRelationalCommandBuilder : IRelationalCommandBuilder { - private readonly IInterceptingLogger _logger; - private readonly DiagnosticSource _diagnosticSource; + private readonly IDiagnosticsLogger _logger; + private readonly IDiagnosticsLogger _readerLogger; public TestRelationalCommandBuilder( - IInterceptingLogger logger, - DiagnosticSource diagnosticSource, + IDiagnosticsLogger logger, + IDiagnosticsLogger readerLogger, IRelationalTypeMapper typeMapper) { _logger = logger; - _diagnosticSource = diagnosticSource; + _readerLogger = readerLogger; ParameterBuilder = new RelationalParameterBuilder(typeMapper); } @@ -56,7 +52,7 @@ public TestRelationalCommandBuilder( public IRelationalCommand Build() => new TestRelationalCommand( _logger, - _diagnosticSource, + _readerLogger, ((IInfrastructure)this).Instance.ToString(), ParameterBuilder.Parameters); } @@ -66,12 +62,12 @@ private class TestRelationalCommand : IRelationalCommand private readonly RelationalCommand _realRelationalCommand; public TestRelationalCommand( - IInterceptingLogger logger, - DiagnosticSource diagnosticSource, + IDiagnosticsLogger logger, + IDiagnosticsLogger readerLogger, string commandText, IReadOnlyList parameters) { - _realRelationalCommand = new RelationalCommand(logger, diagnosticSource, commandText, parameters); + _realRelationalCommand = new RelationalCommand(logger, readerLogger, commandText, parameters); } public string CommandText => _realRelationalCommand.CommandText; diff --git a/test/EFCore.SqlServer.FunctionalTests/Utilities/TestRelationalTransaction.cs b/test/EFCore.SqlServer.FunctionalTests/Utilities/TestRelationalTransaction.cs index 10a02b9a003..8b7683d57c1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Utilities/TestRelationalTransaction.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Utilities/TestRelationalTransaction.cs @@ -23,8 +23,9 @@ public TestRelationalTransaction( new RelationalTransaction( connection, transaction, - new InterceptingLogger(loggerFactory, new LoggingOptions()), - diagnosticSource, + new DiagnosticsLogger( + new InterceptingLogger(loggerFactory, new LoggingOptions()), + diagnosticSource), transactionOwned)) { } diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs index 45dc5358611..a1bd36c531e 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerHistoryRepositoryTest.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Internal; @@ -145,8 +146,12 @@ private static IHistoryRepository CreateHistoryRepository(string schema = null) var typeMapper = new SqlServerTypeMapper(new RelationalTypeMapperDependencies()); var commandBuilderFactory = new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), typeMapper); return new SqlServerHistoryRepository( diff --git a/test/EFCore.SqlServer.Tests/SqlServerConnectionTest.cs b/test/EFCore.SqlServer.Tests/SqlServerConnectionTest.cs index 10bbcd1528b..5166320bbf2 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerConnectionTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerConnectionTest.cs @@ -80,9 +80,16 @@ public static RelationalConnectionDependencies CreateDependencies(DbContextOptio return new RelationalConnectionDependencies( options, - new InterceptingLogger(new LoggerFactory(), new LoggingOptions()), - new InterceptingLogger(new LoggerFactory(), new LoggingOptions()), - new DiagnosticListener("Fake")); + new DiagnosticsLogger( + new InterceptingLogger( + new LoggerFactory(), + new LoggingOptions()), + new DiagnosticListener("FakeDiagnosticListener")), + new DiagnosticsLogger( + new InterceptingLogger( + new LoggerFactory(), + new LoggingOptions()), + new DiagnosticListener("FakeDiagnosticListener"))); } } } diff --git a/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs b/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs index c41ff3f9f20..7fef39f5276 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Data.SqlClient; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -14,7 +13,6 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Xunit; // ReSharper disable UnassignedGetOnlyAutoProperty @@ -160,18 +158,11 @@ private async Task Create_checks_for_existence_and_ultimately_gives_up_waiting_t private class FakeSqlServerConnection : SqlServerConnection { private readonly IDbContextOptions _options; - private readonly ILoggerFactory _loggerFactory; - - public FakeSqlServerConnection(IDbContextOptions options, ILoggerFactory loggerFactory, DiagnosticSource diagnosticSource) - : base( - new RelationalConnectionDependencies( - options, - new InterceptingLogger(new LoggerFactory(), new LoggingOptions()), - new InterceptingLogger(new LoggerFactory(), new LoggingOptions()), - diagnosticSource)) + + public FakeSqlServerConnection(IDbContextOptions options, RelationalConnectionDependencies dependencies) + : base(dependencies) { _options = options; - _loggerFactory = loggerFactory; } public int ErrorNumber { get; set; } @@ -201,7 +192,8 @@ public override bool Open() return await Task.FromResult(true); } - public override ISqlServerConnection CreateMasterConnection() => new FakeSqlServerConnection(_options, _loggerFactory, Dependencies.DiagnosticSource); + public override ISqlServerConnection CreateMasterConnection() + => new FakeSqlServerConnection(_options, Dependencies); } private class FakeRelationalCommandBuilderFactory : IRelationalCommandBuilderFactory diff --git a/test/EFCore.SqlServer.Tests/SqlServerEventIdTest.cs b/test/EFCore.SqlServer.Tests/SqlServerEventIdTest.cs new file mode 100644 index 00000000000..d20d32c5c1c --- /dev/null +++ b/test/EFCore.SqlServer.Tests/SqlServerEventIdTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Xunit; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Tests +{ + public class SqlServerEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var entityType = new EntityType(typeof(object), new Model(new ConventionSet()), ConfigurationSource.Convention); + var property = new Property("A", typeof(int), null, null, entityType, ConfigurationSource.Convention, ConfigurationSource.Convention); + + var fakeFactories = new Dictionary> + { + { typeof(IProperty), () => property } + }; + + SqlServerTestHelpers.Instance.TestEventLogging( + typeof(SqlServerEventId), + typeof(SqlServerLoggerExtensions), + fakeFactories); + } + + private class FakeSequence : ISequence + { + public string Name => "SequenceName"; + public string Schema => throw new NotImplementedException(); + public long StartValue => throw new NotImplementedException(); + public int IncrementBy => throw new NotImplementedException(); + public long? MinValue => throw new NotImplementedException(); + public long? MaxValue => throw new NotImplementedException(); + public Type ClrType => throw new NotImplementedException(); + public IModel Model => throw new NotImplementedException(); + public bool IsCyclic => throw new NotImplementedException(); + } + } +} diff --git a/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs index c83a9d64484..804ef077709 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerModelValidatorTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; @@ -190,9 +191,11 @@ private class Cheese protected override ModelValidator CreateModelValidator() => new SqlServerModelValidator( new ModelValidatorDependencies( - new InterceptingLogger( - new ListLoggerFactory(Log, l => l == LoggerCategory.Model.Validation.Name), - new LoggingOptions())), + new DiagnosticsLogger( + new InterceptingLogger( + new ListLoggerFactory(Log, l => l == LoggerCategory.Model.Validation.Name), + new LoggingOptions()), + new DiagnosticListener("Fake"))), new RelationalModelValidatorDependencies( new TestSqlServerAnnotationProvider(), new SqlServerTypeMapper(new RelationalTypeMapperDependencies()))); diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs index 7933650cca1..bae65b17026 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; @@ -21,8 +22,12 @@ public void Uses_MaxBatchSize_specified_in_SqlServerOptionsExtension() var factory = new SqlServerModificationCommandBatchFactory( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new SqlServerTypeMapper( new RelationalTypeMapperDependencies())), new SqlServerSqlGenerationHelper( @@ -51,8 +56,12 @@ public void MaxBatchSize_is_optional() var factory = new SqlServerModificationCommandBatchFactory( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new SqlServerTypeMapper( new RelationalTypeMapperDependencies())), new SqlServerSqlGenerationHelper( diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs index 8fd4b2526ec..00f07df596e 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Relational.Tests.TestUtilities; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; @@ -18,8 +19,12 @@ public void AddCommand_returns_false_when_max_batch_size_is_reached() { var batch = new SqlServerModificationCommandBatch( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), new SqlServerTypeMapper(new RelationalTypeMapperDependencies())), new SqlServerSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), diff --git a/test/EFCore.Sqlite.Design.FunctionalTests/SqliteDatabaseModelFactoryTest.cs b/test/EFCore.Sqlite.Design.FunctionalTests/SqliteDatabaseModelFactoryTest.cs index bacf9749daf..177fe991929 100644 --- a/test/EFCore.Sqlite.Design.FunctionalTests/SqliteDatabaseModelFactoryTest.cs +++ b/test/EFCore.Sqlite.Design.FunctionalTests/SqliteDatabaseModelFactoryTest.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -26,13 +24,9 @@ public SqliteDatabaseModelFactoryTest() { _testStore = SqliteTestStore.CreateScratch(); - var serviceCollection = new ServiceCollection().AddLogging(); + var serviceCollection = new ServiceCollection().AddScaffolding().AddLogging(); new SqliteDesignTimeServices().ConfigureDesignTimeServices(serviceCollection); - serviceCollection - .AddSingleton() - .AddSingleton(typeof(IInterceptingLogger<>), typeof(InterceptingLogger<>)); - var serviceProvider = serviceCollection.BuildServiceProvider(); var logger = new TestLogger(); diff --git a/test/EFCore.Sqlite.Design.FunctionalTests/SqliteScaffoldingModelFactoryTest.cs b/test/EFCore.Sqlite.Design.FunctionalTests/SqliteScaffoldingModelFactoryTest.cs index e1309a130a5..d4fb874e758 100644 --- a/test/EFCore.Sqlite.Design.FunctionalTests/SqliteScaffoldingModelFactoryTest.cs +++ b/test/EFCore.Sqlite.Design.FunctionalTests/SqliteScaffoldingModelFactoryTest.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -30,10 +29,6 @@ public SqliteScaffoldingModelFactoryTest() var serviceCollection = new ServiceCollection().AddScaffolding().AddLogging(); new SqliteDesignTimeServices().ConfigureDesignTimeServices(serviceCollection); - serviceCollection - .AddSingleton() - .AddSingleton(typeof(IInterceptingLogger<>), typeof(InterceptingLogger<>)); - var serviceProvider = serviceCollection .AddSingleton() .BuildServiceProvider(); @@ -204,8 +199,8 @@ FOREIGN KEY (ParentId) REFERENCES Parent (Id) GetModel(sql); - Assert.Contains("Debug: " + - SqliteDesignStrings.PrincipalTableNotFound(0, "Children", "Parent"), + Assert.Contains("Warning: " + + RelationalDesignStrings.ForeignKeyScaffoldErrorPrincipalTableNotFound("0"), _logger.FullLog); } @@ -222,8 +217,8 @@ FOREIGN KEY (ParentId) REFERENCES Parent (Id) GetModel(sql); - Assert.Contains("Debug: " + - SqliteDesignStrings.PrincipalColumnNotFound(0, "Children", "Id", "Parent"), + Assert.Contains("Warning: " + + RelationalDesignStrings.PrincipalColumnNotFound(0, "Children", "Id", "Parent"), _logger.FullLog); } diff --git a/test/EFCore.Sqlite.Design.Tests/SqliteDesignEventIdTest.cs b/test/EFCore.Sqlite.Design.Tests/SqliteDesignEventIdTest.cs new file mode 100644 index 00000000000..731936d7061 --- /dev/null +++ b/test/EFCore.Sqlite.Design.Tests/SqliteDesignEventIdTest.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.FunctionalTests; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Design +{ + public class SqliteDesignEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var fakeFactories = new Dictionary> + { + { typeof(string), () => "Fake" } + }; + + SqliteTestHelpers.Instance.TestEventLogging( + typeof(SqliteDesignEventId), + typeof(SqliteDesignLoggerExtensions), + fakeFactories); + } + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/BadDataSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BadDataSqliteTest.cs index 624a66e6f71..9e122fd149d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BadDataSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BadDataSqliteTest.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; -using System.Diagnostics; using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; @@ -134,54 +133,54 @@ public void Bad_data_error_handling_null_no_tracking() private class BadDataCommandBuilderFactory : RelationalCommandBuilderFactory { public BadDataCommandBuilderFactory( - IInterceptingLogger logger, - DiagnosticSource diagnosticSource, + IDiagnosticsLogger sqlLogger, + IDiagnosticsLogger readerLogger, IRelationalTypeMapper typeMapper) - : base(logger, diagnosticSource, typeMapper) + : base(sqlLogger, readerLogger, typeMapper) { } public object[] Values { private get; set; } protected override IRelationalCommandBuilder CreateCore( - IInterceptingLogger logger, - DiagnosticSource diagnosticSource, + IDiagnosticsLogger sqlLogger, + IDiagnosticsLogger readerLogger, IRelationalTypeMapper relationalTypeMapper) => new BadDataRelationalCommandBuilder( - logger, diagnosticSource, relationalTypeMapper, Values); + sqlLogger, readerLogger, relationalTypeMapper, Values); private class BadDataRelationalCommandBuilder : RelationalCommandBuilder { private readonly object[] _values; public BadDataRelationalCommandBuilder( - IInterceptingLogger logger, - DiagnosticSource diagnosticSource, + IDiagnosticsLogger sqlLogger, + IDiagnosticsLogger readerLogger, IRelationalTypeMapper typeMapper, object[] values) - : base(logger, diagnosticSource, typeMapper) + : base(sqlLogger, readerLogger, typeMapper) { _values = values; } protected override IRelationalCommand BuildCore( - IInterceptingLogger logger, - DiagnosticSource diagnosticSource, + IDiagnosticsLogger sqlLogger, + IDiagnosticsLogger readerLogger, string commandText, IReadOnlyList parameters) - => new BadDataRelationalCommand(logger, diagnosticSource, commandText, parameters, _values); + => new BadDataRelationalCommand(sqlLogger, readerLogger, commandText, parameters, _values); private class BadDataRelationalCommand : RelationalCommand { private readonly object[] _values; public BadDataRelationalCommand( - IInterceptingLogger logger, - DiagnosticSource diagnosticSource, + IDiagnosticsLogger sqlLogger, + IDiagnosticsLogger readerLogger, string commandText, IReadOnlyList parameters, object[] values) - : base(logger, diagnosticSource, commandText, parameters) + : base(sqlLogger, readerLogger, commandText, parameters) { _values = values; } diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteDatabaseCleaner.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteDatabaseCleaner.cs index d346be94eec..f50133fead0 100644 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteDatabaseCleaner.cs +++ b/test/EFCore.Sqlite.FunctionalTests/SqliteDatabaseCleaner.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; @@ -13,9 +14,11 @@ public class SqliteDatabaseCleaner : RelationalDatabaseCleaner { protected override IInternalDatabaseModelFactory CreateDatabaseModelFactory(ILoggerFactory loggerFactory) => new SqliteDatabaseModelFactory( - new InterceptingLogger( - loggerFactory, - new LoggingOptions())); + new DiagnosticsLogger( + new InterceptingLogger( + loggerFactory, + new LoggingOptions()), + new DiagnosticListener("Fake"))); protected override bool AcceptForeignKey(ForeignKeyModel foreignKey) => false; diff --git a/test/EFCore.Sqlite.Tests/Migrations/SqliteHistoryRepositoryTest.cs b/test/EFCore.Sqlite.Tests/Migrations/SqliteHistoryRepositoryTest.cs index 2867befa3f1..e1371948028 100644 --- a/test/EFCore.Sqlite.Tests/Migrations/SqliteHistoryRepositoryTest.cs +++ b/test/EFCore.Sqlite.Tests/Migrations/SqliteHistoryRepositoryTest.cs @@ -121,8 +121,12 @@ private static IHistoryRepository CreateHistoryRepository() new SqliteMigrationsSqlGenerator( new MigrationsSqlGeneratorDependencies( new RelationalCommandBuilderFactory( - new FakeInterceptingLogger(), - new DiagnosticListener("Fake"), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), + new DiagnosticsLogger( + new FakeInterceptingLogger(), + new DiagnosticListener("Fake")), typeMapper), new SqliteSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), typeMapper, diff --git a/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs b/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs new file mode 100644 index 00000000000..cb48bff4022 --- /dev/null +++ b/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.FunctionalTests; +using Xunit; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Tests +{ + public class SqliteEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var entityType = new EntityType(typeof(object), new Model(new ConventionSet()), ConfigurationSource.Convention); + + var fakeFactories = new Dictionary> + { + { typeof(string), () => "Fake" }, + { typeof(IEntityType), () => entityType }, + { typeof(ISequence), () => new FakeSequence() } + }; + + SqliteTestHelpers.Instance.TestEventLogging( + typeof(SqliteEventId), + typeof(SqliteLoggerExtensions), + fakeFactories); + } + + private class FakeSequence : ISequence + { + public string Name => "SequenceName"; + public string Schema => throw new NotImplementedException(); + public long StartValue => throw new NotImplementedException(); + public int IncrementBy => throw new NotImplementedException(); + public long? MinValue => throw new NotImplementedException(); + public long? MaxValue => throw new NotImplementedException(); + public Type ClrType => throw new NotImplementedException(); + public IModel Model => throw new NotImplementedException(); + public bool IsCyclic => throw new NotImplementedException(); + } + } +} diff --git a/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs index dbcc4b3f0d7..27b5321139a 100644 --- a/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs +++ b/test/EFCore.Sqlite.Tests/SqliteModelValidatorTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; @@ -63,9 +64,11 @@ public void Detects_sequences() protected override ModelValidator CreateModelValidator() => new SqliteModelValidator( new ModelValidatorDependencies( - new InterceptingLogger( - new ListLoggerFactory(Log, l => l == LoggerCategory.Model.Validation.Name), - new LoggingOptions())), + new DiagnosticsLogger( + new InterceptingLogger( + new ListLoggerFactory(Log, l => l == LoggerCategory.Model.Validation.Name), + new LoggingOptions()), + new DiagnosticListener("Fake"))), new RelationalModelValidatorDependencies( new TestSqliteAnnotationProvider(), new SqliteTypeMapper(new RelationalTypeMapperDependencies()))); diff --git a/test/EFCore.Tests/DbContextTest.cs b/test/EFCore.Tests/DbContextTest.cs index 6d37131e4d6..5a2a49ec4a6 100644 --- a/test/EFCore.Tests/DbContextTest.cs +++ b/test/EFCore.Tests/DbContextTest.cs @@ -4580,12 +4580,12 @@ public void Throws_changing_warnings_in_OnConfiguring_when_UseInternalServicePro .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); - using (var context = new ChangeWarningsCacheContext(serviceProvider, b => b.Throw(CoreEventId.QueryPlan))) + using (var context = new ChangeWarningsCacheContext(serviceProvider, b => b.Throw(CoreEventId.QueryExecutionPlanned))) { var _ = context.Model; } - using (var context = new ChangeWarningsCacheContext(serviceProvider, b => b.Log(CoreEventId.QueryPlan))) + using (var context = new ChangeWarningsCacheContext(serviceProvider, b => b.Log(CoreEventId.QueryExecutionPlanned))) { Assert.Equal( CoreStrings.SingletonOptionChanged( diff --git a/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs b/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs new file mode 100644 index 00000000000..910d6207bd4 --- /dev/null +++ b/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.InMemory.FunctionalTests; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Remotion.Linq; +using Remotion.Linq.Clauses; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Infrastructure.Tests +{ + public class CoreEventIdTest + { + [Fact] + public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() + { + var entityType = new EntityType(typeof(object), new Model(new ConventionSet()), ConfigurationSource.Convention); + var property = new Property("A", typeof(int), null, null, entityType, ConfigurationSource.Convention, ConfigurationSource.Convention); + var queryModel = new QueryModel(new MainFromClause("A", typeof(object), Expression.Constant("A")), new SelectClause(Expression.Constant("A"))); + + var fakeFactories = new Dictionary> + { + { typeof(Type), () => typeof(object) }, + { typeof(QueryModel), () => queryModel }, + { typeof(string), () => "Fake" }, + { typeof(IExpressionPrinter), () => new ExpressionPrinter() }, + { typeof(Expression), () => Expression.Constant("A") }, + { typeof(IEntityType), () => entityType }, + { typeof(IKey), () => new Key(new[] { property }, ConfigurationSource.Convention) }, + { typeof(IServiceProvider), () => new FakeServiceProvider() }, + { typeof(ICollection), () => new List() } + }; + + InMemoryTestHelpers.Instance.TestEventLogging(typeof(CoreEventId), typeof(CoreLoggerExtensions), fakeFactories); + } + + private class FakeServiceProvider : IServiceProvider + { + public object GetService(Type serviceType) => null; + } + } +} diff --git a/test/EFCore.Tests/Infrastructure/InterceptingLoggerTest.cs b/test/EFCore.Tests/Infrastructure/InterceptingLoggerTest.cs index 912eb558c07..e31470ed0e5 100644 --- a/test/EFCore.Tests/Infrastructure/InterceptingLoggerTest.cs +++ b/test/EFCore.Tests/Infrastructure/InterceptingLoggerTest.cs @@ -47,15 +47,15 @@ private void FilterTest(Func filter, params string[] expected) var queryLogger = new InterceptingLogger(loggerFactory, new LoggingOptions()); var randomLogger = loggerFactory.CreateLogger("Random"); - dbLogger.LogInformation("DB1"); - sqlLogger.LogInformation("SQL1"); - queryLogger.LogInformation("Query1"); - randomLogger.LogInformation("Random1"); - - dbLogger.LogInformation("DB2"); - sqlLogger.LogInformation("SQL2"); - queryLogger.LogInformation("Query2"); - randomLogger.LogInformation("Random2"); + dbLogger.LogInformation(1, "DB1"); + sqlLogger.LogInformation(2, "SQL1"); + queryLogger.LogInformation(3, "Query1"); + randomLogger.LogInformation(4, "Random1"); + + dbLogger.LogInformation(1, "DB2"); + sqlLogger.LogInformation(2, "SQL2"); + queryLogger.LogInformation(3, "Query2"); + randomLogger.LogInformation(4, "Random2"); Assert.Equal(loggerProvider.Messages, expected); } diff --git a/test/EFCore.Tests/Infrastructure/LoggerCategoryTest.cs b/test/EFCore.Tests/Infrastructure/LoggerCategoryTest.cs index 0c04c69c8ed..489d4f9482b 100644 --- a/test/EFCore.Tests/Infrastructure/LoggerCategoryTest.cs +++ b/test/EFCore.Tests/Infrastructure/LoggerCategoryTest.cs @@ -13,6 +13,7 @@ public void Logger_categories_have_the_correct_names() Assert.Equal("Microsoft.EntityFrameworkCore.Database", LoggerCategory.Database.Name); Assert.Equal("Microsoft.EntityFrameworkCore.Database.Sql", LoggerCategory.Database.Sql.Name); Assert.Equal("Microsoft.EntityFrameworkCore.Database.Connection", LoggerCategory.Database.Connection.Name); + Assert.Equal("Microsoft.EntityFrameworkCore.Database.DataReader", LoggerCategory.Database.DataReader.Name); Assert.Equal("Microsoft.EntityFrameworkCore.Database.Transaction", LoggerCategory.Database.Transaction.Name); Assert.Equal("Microsoft.EntityFrameworkCore.Infrastructure", LoggerCategory.Infrastructure.Name); Assert.Equal("Microsoft.EntityFrameworkCore.Migrations", LoggerCategory.Migrations.Name); @@ -29,6 +30,7 @@ public void LoggerCategory_instances_generate_the_correct_names() Assert.Equal(LoggerCategory.Database.Name, new LoggerCategory.Database().ToString()); Assert.Equal(LoggerCategory.Database.Sql.Name, new LoggerCategory.Database.Sql().ToString()); Assert.Equal(LoggerCategory.Database.Connection.Name, new LoggerCategory.Database.Connection().ToString()); + Assert.Equal(LoggerCategory.Database.DataReader.Name, new LoggerCategory.Database.DataReader().ToString()); Assert.Equal(LoggerCategory.Database.Transaction.Name, new LoggerCategory.Database.Transaction().ToString()); Assert.Equal(LoggerCategory.Infrastructure.Name, new LoggerCategory.Infrastructure().ToString()); Assert.Equal(LoggerCategory.Migrations.Name, new LoggerCategory.Migrations().ToString()); diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 41a2696349c..ee5c531e436 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -98,14 +99,16 @@ public class ReferencedEntity protected ModelValidatorTest() { Log = new List>(); - Logger = new InterceptingLogger( - new ListLoggerFactory(Log, l => l == LoggerCategory.Model.Validation.Name), - new LoggingOptions()); + Logger = new DiagnosticsLogger( + new InterceptingLogger( + new ListLoggerFactory(Log, l => l == LoggerCategory.Model.Validation.Name), + new LoggingOptions()), + new DiagnosticListener("Fake")); } protected List> Log { get; } - protected IInterceptingLogger Logger { get; } + protected IDiagnosticsLogger Logger { get; } protected virtual void VerifyWarning(string expectedMessage, IModel model) { diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index 5e38cd40bc5..70ea6b4188d 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -1585,9 +1586,11 @@ private void VerifyIgnoreMember( var modelValidator = new CoreModelValidator( new ModelValidatorDependencies( - new InterceptingLogger( - new LoggerFactory(), - new LoggingOptions()))); + new DiagnosticsLogger( + new InterceptingLogger( + new LoggerFactory(), + new LoggingOptions()), + new DiagnosticListener("Fake")))); modelValidator.Validate(modelBuilder.Metadata); diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs index d629c863763..6d40f8cffae 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -614,7 +615,11 @@ public void MemberInfoTestCommon( try { - new CoreModelValidator(new ModelValidatorDependencies(new FakeLogger())).Validate(propertyBase.DeclaringType.Model); + new CoreModelValidator(new ModelValidatorDependencies( + new DiagnosticsLogger( + new FakeLogger(), + new DiagnosticListener("Fake")))) + .Validate(propertyBase.DeclaringType.Model); Assert.Null(failMessage); } catch (InvalidOperationException ex) @@ -631,13 +636,13 @@ public void Log( throw new InvalidOperationException(formatter(state, exception)); } - public bool IsEnabled(LogLevel logLevel) => true; + public bool IsEnabled(EventId eventId, LogLevel logLevel) => true; public IDisposable BeginScope(TState state) => null; public ILoggingOptions Options { get; } - public bool LogSensitiveData { get; } + public bool ShouldLogSensitiveData(IDiagnosticsLogger diagnostics) => false; } [Fact] diff --git a/test/EFCore.Tests/ModelBuilderTest/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilderTest/ModelBuilderTestBase.cs index 80172bb83da..4bc71905e53 100644 --- a/test/EFCore.Tests/ModelBuilderTest/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilderTest/ModelBuilderTestBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.InMemory.FunctionalTests; @@ -134,10 +135,13 @@ public virtual TestModelBuilder Validate() var modelBuilder = ((IInfrastructure)ModelBuilder).Instance.Validate(); new CoreModelValidator( new ModelValidatorDependencies( - new InterceptingLogger( + new DiagnosticsLogger( + new InterceptingLogger( new LoggerFactory(), - new LoggingOptions()))) + new LoggingOptions()), + new DiagnosticListener("Fake")))) .Validate(modelBuilder.Metadata); + return this; } diff --git a/test/EFCore.Tests/ModelSourceTest.cs b/test/EFCore.Tests/ModelSourceTest.cs index e31a0a5f892..fe75c877d25 100644 --- a/test/EFCore.Tests/ModelSourceTest.cs +++ b/test/EFCore.Tests/ModelSourceTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; @@ -18,9 +19,11 @@ public class ModelSourceTest private readonly CoreModelValidator _coreModelValidator = new CoreModelValidator( new ModelValidatorDependencies( - new InterceptingLogger( - new LoggerFactory(), - new LoggingOptions()))); + new DiagnosticsLogger( + new InterceptingLogger( + new LoggerFactory(), + new LoggingOptions()), + new DiagnosticListener("Fake")))); private readonly NullConventionSetBuilder _nullConventionSetBuilder = new NullConventionSetBuilder(); diff --git a/test/EFCore.Tests/TestUtilities/TestInMemoryTransactionManager.cs b/test/EFCore.Tests/TestUtilities/TestInMemoryTransactionManager.cs index d779549be23..ddef53cf9f9 100644 --- a/test/EFCore.Tests/TestUtilities/TestInMemoryTransactionManager.cs +++ b/test/EFCore.Tests/TestUtilities/TestInMemoryTransactionManager.cs @@ -16,7 +16,7 @@ public class TestInMemoryTransactionManager : InMemoryTransactionManager private IDbContextTransaction _currentTransaction; public TestInMemoryTransactionManager( - IInterceptingLogger logger) + IDiagnosticsLogger logger) : base(logger) { }