From 0e2d13018e1308bcd9be4a6141589f95825eed0b Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 25 Oct 2021 11:17:16 -0700 Subject: [PATCH] Register design-time LoggerFactory as Scoped Fixes #26384 --- .../DesignTimeServiceCollectionExtensions.cs | 6 +- .../EntityFrameworkDesignServicesBuilder.cs | 5 -- .../Query/FromSqlQueryCosmosTest.cs | 70 +++++++++--------- .../Design/Internal/OperationLoggerTest.cs | 6 +- .../Design/SnapshotModelProcessorTest.cs | 9 ++- .../CSharpRuntimeModelCodeGeneratorTest.cs | 40 ++--------- .../RelationalScaffoldingModelFactoryTest.cs | 31 ++++---- .../TestUtilities/DesignTestHelpers.cs | 26 +++++++ ...Core.Relational.Specification.Tests.csproj | 1 - .../TestUtilities/RelationalTestHelpers.cs | 6 +- .../EFCore.Specification.Tests.csproj | 1 + .../TestUtilities/TestHelpers.cs | 72 ++++++++++++++++++- .../TestUtilities/TestOperationReporter.cs | 13 ++-- .../SqlServerDatabaseModelFactoryTest.cs | 54 ++++++-------- 14 files changed, 203 insertions(+), 137 deletions(-) create mode 100644 test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs rename test/{EFCore.Design.Tests => EFCore.Specification.Tests}/TestUtilities/TestOperationReporter.cs (61%) diff --git a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs index fede9cb72c3..2ccded037a1 100644 --- a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs @@ -79,8 +79,10 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices( .TryAddScoped() .TryAddScoped()); - return services - .AddLogging(b => b.SetMinimumLevel(LogLevel.Debug).AddProvider(new OperationLoggerProvider(reporter))); + var loggerFactory = new LoggerFactory(new[] { new OperationLoggerProvider(reporter) }, new LoggerFilterOptions { MinLevel = LogLevel.Debug }); + services.AddScoped(_ => loggerFactory); + + return services; } /// diff --git a/src/EFCore/Design/EntityFrameworkDesignServicesBuilder.cs b/src/EFCore/Design/EntityFrameworkDesignServicesBuilder.cs index 609c78d46b1..5dd0e09f72f 100644 --- a/src/EFCore/Design/EntityFrameworkDesignServicesBuilder.cs +++ b/src/EFCore/Design/EntityFrameworkDesignServicesBuilder.cs @@ -48,8 +48,6 @@ public class EntityFrameworkDesignServicesBuilder : EntityFrameworkServicesBuild public static readonly IDictionary Services = new Dictionary { - { typeof(IDbContextLogger), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IDiagnosticsLogger<>), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(ICSharpRuntimeAnnotationCodeGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) } }; @@ -81,9 +79,6 @@ public EntityFrameworkDesignServicesBuilder(IServiceCollection serviceCollection /// This builder, such that further calls can be chained. public override EntityFrameworkServicesBuilder TryAddCoreServices() { - TryAdd(); - TryAdd(typeof(IDiagnosticsLogger<>), typeof(DiagnosticsLogger<>)); - TryAdd(); TryAdd(); ServiceCollectionMap.GetInfrastructure() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs index 03a2aeb70bd..364046a603c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs @@ -34,8 +34,8 @@ protected NorthwindContext CreateContext() public async Task FromSqlRaw_queryable_simple(bool async) { using var context = CreateContext(); - var query = context.Set() - .FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""ContactName""] LIKE '%z%'"); + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""ContactName""] LIKE '%z%'"); var actual = async ? await query.ToArrayAsync() @@ -56,8 +56,8 @@ public async Task FromSqlRaw_queryable_simple(bool async) public async Task FromSqlRaw_queryable_incorrect_discriminator_throws(bool async) { using var context = CreateContext(); - var query = context.Set() - .FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Order"""); + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Order"""); var exception = async ? await Assert.ThrowsAsync(() => query.ToArrayAsync()) @@ -73,7 +73,7 @@ public async Task FromSqlRaw_queryable_incorrect_discriminator_throws(bool async public async Task FromSqlRaw_queryable_simple_columns_out_of_order(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT c[""id""], c[""Discriminator""], c[""Region""], c[""PostalCode""], c[""Phone""], c[""Fax""], c[""CustomerID""], c[""Country""], c[""ContactTitle""], c[""ContactName""], c[""CompanyName""], c[""City""], c[""Address""] FROM root c WHERE c[""Discriminator""] = ""Customer"""); var actual = async @@ -95,7 +95,7 @@ public async Task FromSqlRaw_queryable_simple_columns_out_of_order(bool async) public async Task FromSqlRaw_queryable_simple_columns_out_of_order_and_extra_columns(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT c[""id""], c[""Discriminator""], c[""Region""], c[""PostalCode""], c[""PostalCode""] AS Foo, c[""Phone""], c[""Fax""], c[""CustomerID""], c[""Country""], c[""ContactTitle""], c[""ContactName""], c[""CompanyName""], c[""City""], c[""Address""] FROM root c WHERE c[""Discriminator""] = ""Customer"""); var actual = async @@ -117,7 +117,7 @@ public async Task FromSqlRaw_queryable_simple_columns_out_of_order_and_extra_col public async Task FromSqlRaw_queryable_composed(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""").Where(c => c.ContactName.Contains("z")); var sql = query.ToQueryString(); @@ -142,7 +142,7 @@ public async Task FromSqlRaw_queryable_composed(bool async) public virtual async Task FromSqlRaw_queryable_composed_after_removing_whitespaces(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), _eol + " " + _eol + _eol + _eol + "SELECT" + _eol + @"* FROM root c WHERE c[""Discriminator""] = ""Customer""") .Where(c => c.ContactName.Contains("z")); @@ -172,8 +172,8 @@ public async Task FromSqlRaw_queryable_composed_compiled(bool async) if (async) { var query = EF.CompileAsyncQuery( - (NorthwindContext context) => context.Set() - .FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") + (NorthwindContext context) => CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") .Where(c => c.ContactName.Contains("z"))); using (var context = CreateContext()) @@ -186,8 +186,8 @@ public async Task FromSqlRaw_queryable_composed_compiled(bool async) else { var query = EF.CompileQuery( - (NorthwindContext context) => context.Set() - .FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") + (NorthwindContext context) => CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") .Where(c => c.ContactName.Contains("z"))); using (var context = CreateContext()) @@ -213,8 +213,8 @@ public virtual async Task FromSqlRaw_queryable_composed_compiled_with_parameter( if (async) { var query = EF.CompileAsyncQuery( - (NorthwindContext context) => context.Set() - .FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""CustomerID""] = {0}", "CONSH") + (NorthwindContext context) => CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""CustomerID""] = {0}", "CONSH") .Where(c => c.ContactName.Contains("z"))); using (var context = CreateContext()) @@ -227,8 +227,8 @@ public virtual async Task FromSqlRaw_queryable_composed_compiled_with_parameter( else { var query = EF.CompileQuery( - (NorthwindContext context) => context.Set() - .FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""CustomerID""] = {0}", "CONSH") + (NorthwindContext context) => CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""CustomerID""] = {0}", "CONSH") .Where(c => c.ContactName.Contains("z"))); using (var context = CreateContext()) @@ -252,7 +252,7 @@ public virtual async Task FromSqlRaw_queryable_composed_compiled_with_parameter( public virtual async Task FromSqlRaw_queryable_multiple_line_query(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = 'London'"); @@ -278,7 +278,7 @@ FROM root c public virtual async Task FromSqlRaw_queryable_composed_multiple_line_query(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") @@ -309,7 +309,7 @@ public async Task FromSqlRaw_queryable_with_parameters(bool async) var contactTitle = "Sales Representative"; using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = {0} AND c[""ContactTitle""] = {1}", city, contactTitle); @@ -336,7 +336,7 @@ SELECT c public async Task FromSqlRaw_queryable_with_parameters_inline(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = {0} AND c[""ContactTitle""] = {1}", "London", "Sales Representative"); @@ -365,7 +365,7 @@ public async Task FromSqlRaw_queryable_with_null_parameter(bool async) uint? reportsTo = null; using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Employee"" AND c[""ReportsTo""] = {0} OR (IS_NULL(c[""ReportsTo""]) AND IS_NULL({0}))", reportsTo); var actual = async @@ -391,7 +391,7 @@ public async Task FromSqlRaw_queryable_with_parameters_and_closure(bool async) var contactTitle = "Sales Representative"; using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = {0}", city) .Where(c => c.ContactTitle == contactTitle); var queryString = query.ToQueryString(); @@ -420,8 +420,8 @@ SELECT c public virtual async Task FromSqlRaw_queryable_simple_cache_key_includes_query_string(bool async) { using var context = CreateContext(); - var query = context.Set() - .FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = 'London'"); + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = 'London'"); var actual = async ? await query.ToArrayAsync() @@ -430,8 +430,8 @@ public virtual async Task FromSqlRaw_queryable_simple_cache_key_includes_query_s Assert.Equal(6, actual.Length); Assert.True(actual.All(c => c.City == "London")); - query = context.Set() - .FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = 'Seattle'"); + query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = 'Seattle'"); actual = async ? await query.ToArrayAsync() @@ -461,7 +461,7 @@ public virtual async Task FromSqlRaw_queryable_with_parameters_cache_key_include var sql = @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer"" AND c[""City""] = {0} AND c[""ContactTitle""] = {1}"; using var context = CreateContext(); - var query = context.Set().FromSqlRaw(sql, city, contactTitle); + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), sql, city, contactTitle); var actual = async ? await query.ToArrayAsync() @@ -474,7 +474,7 @@ public virtual async Task FromSqlRaw_queryable_with_parameters_cache_key_include city = "Madrid"; contactTitle = "Accounting Manager"; - query = context.Set().FromSqlRaw(sql, city, contactTitle); + query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), sql, city, contactTitle); actual = async ? await query.ToArrayAsync() @@ -507,7 +507,8 @@ SELECT c public virtual async Task FromSqlRaw_queryable_simple_as_no_tracking_not_composed(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") .AsNoTracking(); var actual = async @@ -529,7 +530,7 @@ public virtual async Task FromSqlRaw_queryable_simple_as_no_tracking_not_compose public virtual async Task FromSqlRaw_queryable_simple_projection_composed(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw( + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Product"" AND NOT c[""Discontinued""] AND ((c[""UnitsInStock""] + c[""UnitsOnOrder""]) < c[""ReorderLevel""])") @@ -555,7 +556,8 @@ FROM root c public virtual async Task FromSqlRaw_composed_with_nullable_predicate(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") .Where(c => c.ContactName == c.CompanyName); var actual = async @@ -579,7 +581,8 @@ public virtual async Task FromSqlRaw_does_not_parameterize_interpolated_string(b using var context = CreateContext(); var propertyName = "OrderID"; var max = 10250; - var query = context.Orders.FromSqlRaw($@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Order"" AND c[""{propertyName}""] < {{0}}", max); + var query = CosmosQueryableExtensions.FromSqlRaw(context.Orders, + $@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Order"" AND c[""{propertyName}""] < {{0}}", max); var actual = async ? await query.ToListAsync() @@ -593,7 +596,8 @@ public virtual async Task FromSqlRaw_does_not_parameterize_interpolated_string(b public virtual async Task FromSqlRaw_queryable_simple_projection_not_composed(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") + var query = CosmosQueryableExtensions.FromSqlRaw(context.Set(), + @"SELECT * FROM root c WHERE c[""Discriminator""] = ""Customer""") .Select( c => new { c.CustomerID, c.City }) .AsNoTracking(); diff --git a/test/EFCore.Design.Tests/Design/Internal/OperationLoggerTest.cs b/test/EFCore.Design.Tests/Design/Internal/OperationLoggerTest.cs index b3bf01db0f5..7801035d1a7 100644 --- a/test/EFCore.Design.Tests/Design/Internal/OperationLoggerTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/OperationLoggerTest.cs @@ -27,7 +27,11 @@ public void Log_dampens_logLevel_when_CommandExecuted() Assert.Collection( reporter.Messages, - x => Assert.Equal("verbose: -- Can't stop the SQL", x)); + x => + { + Assert.Equal("-- Can't stop the SQL", x.Message); + Assert.Equal(LogLevel.Debug, x.Level); + }); } } } diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index f8b5438e6fb..628ad2e302a 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -14,6 +14,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Ownership; using Xunit; @@ -95,7 +96,9 @@ public void Warns_for_conflicting_annotations() new SnapshotModelProcessor(reporter, DummyModelRuntimeInitializer.Instance).Process(model); - Assert.Equal("warn: " + DesignStrings.MultipleAnnotationConflict("DefaultSchema"), reporter.Messages.Single()); + var (level, message) = reporter.Messages.Single(); + Assert.Equal(LogLevel.Warning, level); + Assert.Equal(DesignStrings.MultipleAnnotationConflict("DefaultSchema"), message); Assert.Equal(2, model.GetAnnotations().Count()); var actual = (string)model["Relational:DefaultSchema"]; @@ -116,7 +119,9 @@ public void Warns_for_conflicting_annotations_one_relational() new SnapshotModelProcessor(reporter, DummyModelRuntimeInitializer.Instance).Process(model); - Assert.Equal("warn: " + DesignStrings.MultipleAnnotationConflict("DefaultSchema"), reporter.Messages.Single()); + var (level, message) = reporter.Messages.Single(); + Assert.Equal(LogLevel.Warning, level); + Assert.Equal(DesignStrings.MultipleAnnotationConflict("DefaultSchema"), message); Assert.Equal(2, model.GetAnnotations().Count()); var actual = (string)model["Relational:DefaultSchema"]; diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index c93b47dedc1..0994a8bb75b 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -3563,7 +3563,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder options) } protected void Test( - EntityFrameworkCore.DbContext context, + DbContext context, CompiledModelCodeGenerationOptions options, Action> assertScaffold = null, Action assertModel = null, @@ -3572,19 +3572,12 @@ protected void Test( { var model = context.GetService().Model; - var services = new ServiceCollection(); - if (additionalDesignTimeServices != null) - { - ConfigureDesignTimeServices(additionalDesignTimeServices, services); - } - ConfigureProviderServices(context.GetService().Name, services); - services.AddEntityFrameworkDesignTimeServices(); - options.ModelNamespace ??= "TestNamespace"; options.ContextType = context.GetType(); - var generator = services - .BuildServiceProvider(validateScopes: true) + var generator = DesignTestHelpers.Instance.CreateDesignServiceProvider( + context.GetService().Name, + additionalDesignTimeServices: additionalDesignTimeServices) .GetRequiredService() .Select(options); @@ -3641,31 +3634,6 @@ protected void Test( } } - private void ConfigureProviderServices(string provider, IServiceCollection services) - { - var providerAssembly = Assembly.Load(new AssemblyName(provider)); - - var providerServicesAttribute = providerAssembly.GetCustomAttribute(); - if (providerServicesAttribute == null) - { - throw new InvalidOperationException(DesignStrings.CannotFindDesignTimeProviderAssemblyAttribute(provider)); - } - - var designTimeServicesType = providerAssembly.GetType( - providerServicesAttribute.TypeName, - throwOnError: true, - ignoreCase: false)!; - - ConfigureDesignTimeServices(designTimeServicesType, services); - } - - private static void ConfigureDesignTimeServices( - Type designTimeServicesType, - IServiceCollection services) - { - var designTimeServices = (IDesignTimeServices)Activator.CreateInstance(designTimeServicesType)!; - designTimeServices.ConfigureDesignTimeServices(services); - } protected static void AssertFileContents( string expectedPath, diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs index c2a84daa5a8..a50a43e3214 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs @@ -12,6 +12,7 @@ using Microsoft.EntityFrameworkCore.Scaffolding.Metadata.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Xunit; namespace Microsoft.EntityFrameworkCore.Internal @@ -455,7 +456,10 @@ public void Unmappable_column_type(string StoreType) }); Assert.Single(_factory.Create(info, new ModelReverseEngineerOptions()).FindEntityType("E").GetProperties()); - Assert.Single(_reporter.Messages, t => t.Contains(DesignStrings.CannotFindTypeMappingForColumn("E.Coli", StoreType))); + + var (level, message) = _reporter.Messages.Single(); + Assert.Equal(LogLevel.Warning, level); + Assert.Equal(DesignStrings.CannotFindTypeMappingForColumn("E.Coli", StoreType), message); } [ConditionalTheory] @@ -1135,11 +1139,10 @@ public void It_logs_warning_for_bad_foreign_key() new DatabaseModel { Tables = { parentTable, childrenTable } }, new ModelReverseEngineerOptions()); - Assert.Single( - _reporter.Messages, t => t.Contains( - "warn: " - + DesignStrings.ForeignKeyScaffoldErrorPrincipalKeyNotFound( - childrenTable.ForeignKeys.ElementAt(0).DisplayName(), "NotPkId", "Parent"))); + var (level, message) = _reporter.Messages.Single(); + Assert.Equal(LogLevel.Warning, level); + Assert.Equal(DesignStrings.ForeignKeyScaffoldErrorPrincipalKeyNotFound( + childrenTable.ForeignKeys.ElementAt(0).DisplayName(), "NotPkId", "Parent"), message); } [ConditionalFact] @@ -1194,10 +1197,9 @@ public void It_logs_warning_for_duplicate_foreign_key() new DatabaseModel { Tables = { parentTable, childrenTable } }, new ModelReverseEngineerOptions()); - Assert.Single( - _reporter.Messages, t => t.Contains( - "warn: " - + DesignStrings.ForeignKeyWithSameFacetsExists(childrenTable.ForeignKeys.ElementAt(1).DisplayName(), "FK_Foo"))); + var (level, message) = _reporter.Messages.Single(); + Assert.Equal(LogLevel.Warning, level); + Assert.Equal(DesignStrings.ForeignKeyWithSameFacetsExists(childrenTable.ForeignKeys.ElementAt(1).DisplayName(), "FK_Foo"), message); } [ConditionalFact] @@ -1305,11 +1307,10 @@ public void Unique_nullable_index_used_by_foreign_key() var alternateKey = model.GetKeys().Single(k => !k.IsPrimaryKey()); Assert.Equal(alternateKey, fk.PrincipalKey); - Assert.Single( - _reporter.Messages, t => t.Contains( - "warn: " - + DesignStrings.ForeignKeyPrincipalEndContainsNullableColumns( - table.ForeignKeys.ElementAt(0).DisplayName(), "FriendsNameUniqueIndex", "Friends.BuddyId"))); + var (level, message) = _reporter.Messages.Single(); + Assert.Equal(LogLevel.Warning, level); + Assert.Equal(DesignStrings.ForeignKeyPrincipalEndContainsNullableColumns( + table.ForeignKeys.ElementAt(0).DisplayName(), "FriendsNameUniqueIndex", "Friends.BuddyId"), message); } [ConditionalFact] diff --git a/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs b/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs new file mode 100644 index 00000000000..10e393c3319 --- /dev/null +++ b/test/EFCore.Design.Tests/TestUtilities/DesignTestHelpers.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.TestUtilities +{ + public class DesignTestHelpers : TestHelpers + { + protected DesignTestHelpers() + { + } + + public static DesignTestHelpers Instance { get; } = new(); + + public override IServiceCollection AddProviderServices(IServiceCollection services) + => FakeRelationalOptionsExtension.AddEntityFrameworkRelationalDatabase(services); + + public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseFakeRelational(); + + public override LoggingDefinitions LoggingDefinitions { get; } = new TestRelationalLoggingDefinitions(); + } +} diff --git a/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj b/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj index 9abd6f267f6..06f675f442d 100644 --- a/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj +++ b/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj @@ -15,7 +15,6 @@ - diff --git a/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs b/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs index eb11c48f66f..fdd985f4ca5 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; +using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; using Microsoft.Extensions.DependencyInjection; @@ -16,6 +16,10 @@ protected RelationalTestHelpers() public static RelationalTestHelpers Instance { get; } = new(); + protected override EntityFrameworkDesignServicesBuilder CreateEntityFrameworkDesignServicesBuilder( + IServiceCollection services) + => new EntityFrameworkRelationalDesignServicesBuilder(services); + public override IServiceCollection AddProviderServices(IServiceCollection services) => FakeRelationalOptionsExtension.AddEntityFrameworkRelationalDatabase(services); diff --git a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj index 76cbcfbfdf1..093857a01a3 100644 --- a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj +++ b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj @@ -17,6 +17,7 @@ + diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs index a061810c454..c052e92b73d 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs @@ -4,14 +4,16 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; @@ -63,6 +65,72 @@ private static IServiceProvider CreateServiceProvider( return services.BuildServiceProvider(); // No scope validation; test doubles violate scopes, but only resolved once. } + protected virtual EntityFrameworkDesignServicesBuilder CreateEntityFrameworkDesignServicesBuilder(IServiceCollection services) + => new EntityFrameworkDesignServicesBuilder(services); + + public IServiceProvider CreateDesignServiceProvider( + IServiceCollection customServices = null, + Action replaceServices = null, + Type additionalDesignTimeServices = null, + IOperationReporter reporter = null) + => CreateDesignServiceProvider( + CreateContext().GetService().Name, + customServices, + replaceServices, + additionalDesignTimeServices, + reporter); + + public IServiceProvider CreateDesignServiceProvider( + string provider, + IServiceCollection customServices = null, + Action replaceServices = null, + Type additionalDesignTimeServices = null, + IOperationReporter reporter = null) + => CreateServiceProvider(customServices, services => + { + if (replaceServices != null) + { + var builder = CreateEntityFrameworkDesignServicesBuilder(services); + replaceServices(builder); + } + + if (additionalDesignTimeServices != null) + { + ConfigureDesignTimeServices(additionalDesignTimeServices, services); + } + + ConfigureProviderServices(provider, services); + services.AddEntityFrameworkDesignTimeServices(reporter); + + return services; + }); + + private void ConfigureProviderServices(string provider, IServiceCollection services) + { + var providerAssembly = Assembly.Load(new AssemblyName(provider)); + + var providerServicesAttribute = providerAssembly.GetCustomAttribute(); + if (providerServicesAttribute == null) + { + throw new InvalidOperationException(DesignStrings.CannotFindDesignTimeProviderAssemblyAttribute(provider)); + } + + var designTimeServicesType = providerAssembly.GetType( + providerServicesAttribute.TypeName, + throwOnError: true, + ignoreCase: false)!; + + ConfigureDesignTimeServices(designTimeServicesType, services); + } + + private static void ConfigureDesignTimeServices( + Type designTimeServicesType, + IServiceCollection services) + { + var designTimeServices = (IDesignTimeServices)Activator.CreateInstance(designTimeServicesType)!; + designTimeServices.ConfigureDesignTimeServices(services); + } + public abstract IServiceCollection AddProviderServices(IServiceCollection services); public DbContextOptionsBuilder AddProviderOptions(DbContextOptionsBuilder optionsBuilder) diff --git a/test/EFCore.Design.Tests/TestUtilities/TestOperationReporter.cs b/test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs similarity index 61% rename from test/EFCore.Design.Tests/TestUtilities/TestOperationReporter.cs rename to test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs index b14b47d5514..528ec7b8fe9 100644 --- a/test/EFCore.Design.Tests/TestUtilities/TestOperationReporter.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs @@ -3,29 +3,30 @@ using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Design.Internal; +using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.TestUtilities { public class TestOperationReporter : IOperationReporter { - private readonly List _messages = new(); + private readonly List<(LogLevel, string)> _messages = new(); - public IReadOnlyList Messages + public IReadOnlyList<(LogLevel Level, string Message)> Messages => _messages; public void Clear() => _messages.Clear(); public void WriteInformation(string message) - => _messages.Add("info: " + message); + => _messages.Add((LogLevel.Information, message)); public void WriteVerbose(string message) - => _messages.Add("verbose: " + message); + => _messages.Add((LogLevel.Debug, message)); public void WriteWarning(string message) - => _messages.Add("warn: " + message); + => _messages.Add((LogLevel.Warning, message)); public void WriteError(string message) - => _messages.Add("error: " + message); + => _messages.Add((LogLevel.Error, message)); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs index 7a2a348999d..f9924533fc3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs @@ -3,12 +3,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Diagnostics.Internal; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations; @@ -16,8 +12,8 @@ using Microsoft.EntityFrameworkCore.SqlServer.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Xunit; @@ -32,7 +28,7 @@ public class SqlServerDatabaseModelFactoryTest : IClassFixture t.Level == LogLevel.Warning)); + var message = Fixture.OperationReporter.Messages.Single(m => m.Level == LogLevel.Warning).Message; - Assert.Equal(SqlServerResources.LogMissingSchema(new TestLogger()).EventId, Id); Assert.Equal( SqlServerResources.LogMissingSchema(new TestLogger()).GenerateMessage("MySchema"), - Message); + message); }, "DROP TABLE Blank;"); } @@ -2405,12 +2400,11 @@ CREATE TABLE Blank ( { Assert.Empty(dbModel.Tables); - var (_, Id, Message, _, _) = Assert.Single(Fixture.ListLoggerFactory.Log.Where(t => t.Level == LogLevel.Warning)); + var message = Fixture.OperationReporter.Messages.Single(m => m.Level == LogLevel.Warning).Message; - Assert.Equal(SqlServerResources.LogMissingTable(new TestLogger()).EventId, Id); Assert.Equal( SqlServerResources.LogMissingTable(new TestLogger()).GenerateMessage("MyTable"), - Message); + message); }, "DROP TABLE Blank;"); } @@ -2433,14 +2427,12 @@ CONSTRAINT MYFK FOREIGN KEY (ForeignKeyId) REFERENCES PrincipalTable(Id) ON DELE Enumerable.Empty(), dbModel => { - var (_, Id, Message, _, _) = Assert.Single(Fixture.ListLoggerFactory.Log.Where(t => t.Level == LogLevel.Warning)); + var message = Fixture.OperationReporter.Messages.Single(m => m.Level == LogLevel.Warning).Message; - Assert.Equal( - SqlServerResources.LogPrincipalTableNotInSelectionSet(new TestLogger()).EventId, Id); Assert.Equal( SqlServerResources.LogPrincipalTableNotInSelectionSet(new TestLogger()) .GenerateMessage( - "MYFK", "dbo.DependentTable", "dbo.PrincipalTable"), Message); + "MYFK", "dbo.DependentTable", "dbo.PrincipalTable"), message); }, @" DROP TABLE DependentTable; @@ -2460,12 +2452,11 @@ CONSTRAINT MYFK FOREIGN KEY (Id) REFERENCES PrincipalTable(Id) Enumerable.Empty(), dbModel => { - var (level, _, message, _, _) = Assert.Single( - Fixture.ListLoggerFactory.Log, t => t.Id == SqlServerEventId.ReflexiveConstraintIgnored); + var level = Fixture.OperationReporter.Messages + .Single(m => m.Message == SqlServerResources.LogReflexiveConstraintIgnored(new TestLogger()) + .GenerateMessage("MYFK", "dbo.PrincipalTable")).Level; + Assert.Equal(LogLevel.Debug, level); - Assert.Equal( - SqlServerResources.LogReflexiveConstraintIgnored(new TestLogger()) - .GenerateMessage("MYFK", "dbo.PrincipalTable"), message); var table = Assert.Single(dbModel.Tables); Assert.Empty(table.ForeignKeys); @@ -2497,12 +2488,11 @@ CONSTRAINT MYFK3 FOREIGN KEY (ForeignKeyId) REFERENCES OtherPrincipalTable(Id), Enumerable.Empty(), dbModel => { - var (level, _, message, _, _) = Assert.Single( - Fixture.ListLoggerFactory.Log, t => t.Id == SqlServerEventId.DuplicateForeignKeyConstraintIgnored); + var level = Fixture.OperationReporter.Messages + .Single(m => m.Message == SqlServerResources.LogDuplicateForeignKeyConstraintIgnored(new TestLogger()) + .GenerateMessage("MYFK2", "dbo.DependentTable", "MYFK1")).Level; + Assert.Equal(LogLevel.Warning, level); - Assert.Equal( - SqlServerResources.LogDuplicateForeignKeyConstraintIgnored(new TestLogger()) - .GenerateMessage("MYFK2", "dbo.DependentTable", "MYFK1"), message); var table = dbModel.Tables.Single(t => t.Name == "DependentTable"); Assert.Equal(2, table.ForeignKeys.Count); @@ -2529,13 +2519,9 @@ private void Test( try { - var databaseModelFactory = new SqlServerDatabaseModelFactory( - new DiagnosticsLogger( - Fixture.ListLoggerFactory, - new LoggingOptions(), - new DiagnosticListener("Fake"), - new SqlServerLoggingDefinitions(), - new NullDbContextLogger())); + var databaseModelFactory = SqlServerTestHelpers.Instance.CreateDesignServiceProvider( + reporter: Fixture.OperationReporter) + .CreateScope().ServiceProvider.GetRequiredService(); var databaseModel = databaseModelFactory.Create( Fixture.TestStore.ConnectionString, @@ -2562,6 +2548,8 @@ protected override ITestStoreFactory TestStoreFactory public new SqlServerTestStore TestStore => (SqlServerTestStore)base.TestStore; + public TestOperationReporter OperationReporter { get; } = new TestOperationReporter(); + public override async Task InitializeAsync() { await base.InitializeAsync();