efcore / EFCore.FSharp

Adds F# design-time support to EF Core
MIT License
234 stars 27 forks source link

UseFSharpTypes causes ASP.NET Core 6 to throw Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning exception #129

Closed LiteracyFanatic closed 2 years ago

LiteracyFanatic commented 2 years ago

Describe the bug There seems to be something wrong with the interaction between UseFSharpTypes from this library and ASP.NET Core's dependency injection. Things work fine at first, but the code will throw an exception after 20 requests. This was not happening prior to the .NET 6 upgrade.

To Reproduce

The follow code demonstrates the issue

module Test

open System
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.DependencyInjection
open Microsoft.EntityFrameworkCore
open EntityFrameworkCore.FSharp.Extensions

[<CLIMutable>]
type BlogPost = {
    Id: int
    Title: string
    Content: string
}

type TestContext =
    inherit DbContext

    new(options : DbContextOptions<TestContext>) =
        { inherit DbContext(options) }

    [<DefaultValue>] val mutable private _BlogPosts : DbSet<BlogPost>
    member this.BlogPosts with get() = this._BlogPosts and set v = this._BlogPosts <- v

[<EntryPoint>]
let main args =
    let builder = WebApplication.CreateBuilder(args)

    builder.Services.AddDbContext<TestContext>(fun options ->
        options.UseSqlite("Data Source=Test.db") |> ignore
        options.UseFSharpTypes() |> ignore
    ) |> ignore

    let app = builder.Build()

    use serviceScope = app.Services.CreateScope()
    let db = serviceScope.ServiceProvider.GetRequiredService<TestContext>()
    db.Database.EnsureCreated() |> ignore

    app.MapGet("/", Func<_,_>(fun (db: TestContext) -> "Hello World!")) |> ignore

    app.Run()

    0

Make more than 20 requests to trigger the exception

for i in $(seq 21); do curl localhost:5000; done
System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning': More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance. For example, calling 'UseLoggerFactory' passing in a new instance each time--see https://go.microsoft.com/fwlink/?linkid=869049 for more details. This may lead to performance issues, consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. This exception can be suppressed or logged by passing event ID 'CoreEventId.ManyServiceProvidersCreatedWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
   at Microsoft.EntityFrameworkCore.Diagnostics.EventDefinition.Log[TLoggerCategory](IDiagnosticsLogger`1 logger, Exception exception)
   at Microsoft.EntityFrameworkCore.Diagnostics.CoreLoggerExtensions.ManyServiceProvidersCreatedWarning(IDiagnosticsLogger`1 diagnostics, ICollection`1 serviceProviders)
   at Microsoft.EntityFrameworkCore.Internal.ServiceProviderCache.<GetOrAdd>g__BuildServiceProvider|4_0(IDbContextOptions options, ConcurrentDictionary`2 configurations)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArgument)
   at Microsoft.EntityFrameworkCore.Internal.ServiceProviderCache.GetOrAdd(IDbContextOptions options, Boolean providerRequired)
   at Microsoft.EntityFrameworkCore.DbContext..ctor(DbContextOptions options)
   at Test.TestContext..ctor(DbContextOptions`1 options)
   at ResolveService(ILEmitResolverBuilderRuntimeContext , ServiceProviderEngineScope )
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at lambda_method2(Closure , Object , HttpContext )
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass36_0.<Create>b__0(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Additional context The following snippet can be added to the ASP.NET startup code to disable the exception.

options.ConfigureWarnings(fun x -> x.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning) |> ignore) |> ignore
simon-reynolds commented 2 years ago

Hi @LiteracyFanatic That has got to be one of the best bug reports I've ever gotten, thank you so much for that! I'll take a look at this when I can over the next few days

simon-reynolds commented 2 years ago

Fixed in #130

LiteracyFanatic commented 2 years ago

I can confirm that the exception is no longer being thrown. Thanks for all your hard work!