Natalie Perret
Natalie Perret

Reputation: 8997

How to use Entity Framework in-memory database in an F#?

I am trying to use Entity Framework Core with F# with the in memory database for a very simple use case:

open System
open Microsoft.EntityFrameworkCore

type Position = {
    X: double
    Y: double
}

type Airport = {
    Id: Guid
    Name: string
    Position: Position
}

type MyContext =
    inherit DbContext

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

    override __.OnConfiguring optionsBuilder =
        if optionsBuilder.IsConfigured <> false then
            optionsBuilder.UseInMemoryDatabase("database_name") |> ignore

    [<DefaultValue>]
    val mutable airports: DbSet<Airport>
    member x.Airports
        with get() = x.airports
        and set value = x.airports <- value

module AirportRepository =
    let getAirport id =
        use context = new MyContext()
        query {
            for airport in context.Airports do
                where (airport.Id = id)
                select airport
                exactlyOne
        } |> (fun x -> if box x = null then None else Some x)

    let addAirport (entity: Airport) =
        use context = new MyContext()
        context.Airports.Add(entity) |> ignore
        context.SaveChanges true |> ignore

[<EntryPoint>]
let main argv =
    let airport = {
        Id = Guid.NewGuid()
        Name = "Michelle"
        Position = {
            X = 42.0
            Y = 42.0
        }
    }
    AirportRepository.addAirport airport
    0

but it does not work and throw the following exception:

Unhandled Exception: System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application se
rvice provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.EntryWithoutDetectChanges[TEntity](TEntity entity)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
   at Program.AirportRepository.addAirport(Airport entity) in C:\Users\eperret\RiderProjects\FSharpCore\FSharpCore\Program.fs:line 43
   at Program.main(String[] argv) in C:\Users\eperret\RiderProjects\FSharpCore\FSharpCore\Program.fs:line 56

How can I make it work, the OnConfiguring override is present so I am not really what I am missing here.

Upvotes: 1

Views: 901

Answers (2)

Natalie Perret
Natalie Perret

Reputation: 8997

There was a few issues:

  • As pointed out by Matthew Abbott, the OnConfiguring method was not properly implemented and needed to check if was not already configured (and not the opposite like what I initially did)
  • It seems that I needed to have only simple types in my entity definition and no complex types (cause there would be considered as another entity

The solution:

open System
open Microsoft.EntityFrameworkCore


[<CLIMutable>]
type Airport = {
    Id: Guid
    Name: string
    X: double
    Y: double
}

type MyContext =
    inherit DbContext

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

    override __.OnConfiguring optionsBuilder =
        if optionsBuilder.IsConfigured <> true then
            optionsBuilder.UseInMemoryDatabase("database_name") |> ignore

    [<DefaultValue>]
    val mutable airports: DbSet<Airport>
    member x.Airports
        with get() = x.airports
        and set value = x.airports <- value

module AirportRepository =
    let getAirport id =
        use context = new MyContext()
        query {
            for airport in context.Airports do
                where (airport.Id = id)
                select airport
                exactlyOne
        } |> (fun x -> if box x = null then None else Some x)

    let addAirport (entity: Airport) =
        use context = new MyContext()
        context.Airports.Add(entity) |> ignore
        context.SaveChanges true |> ignore

[<EntryPoint>]
let main argv =
    let myGuid = Guid.NewGuid()
    let airport = {
        Id = myGuid
        Name = "Michelle"
        X = 42.0
        Y = 42.0
    }
    AirportRepository.addAirport airport
    let thisAirport = AirportRepository.getAirport myGuid
    assert (thisAirport = Some airport)
    0

Upvotes: 0

Matthew Abbott
Matthew Abbott

Reputation: 61589

At a guess, I'm wondering if this line is the culprit:

if optionsBuilder.IsConfigured <> false then
            optionsBuilder.UseInMemoryDatabase("database_name") |> ignore

The OnConfiguring should only be called by EFCore once per DbContext instance, so you may not need the check for IsConfigured here. Try removing that if branch and try again?

Upvotes: 3

Related Questions