如何在 F# 中使用 Entity Framework 内存数据库?

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

我正在尝试将 Entity Framework Core with F# 与内存数据库一起用于一个非常简单的用例:

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

但它不起作用并抛出以下异常:

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

我怎样才能让它工作,存在 OnConfiguring 覆盖,所以我并不是这里真正缺少的东西。

猜测一下,我想知道这行是不是罪魁祸首:

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

OnConfiguring 只应在每个 DbContext 实例中由 EFCore 调用一次,因此您可能不需要在此处检查 IsConfigured。尝试删除 if 分支并重试?

有几个问题:

  • 正如 Matthew Abbott 指出的那样,OnConfiguring 方法没有正确实施,需要检查是否尚未配置(而不是像我最初所做的那样相反)
  • 我的实体定义中似乎只需要简单类型,不需要复杂类型(因为会被视为另一个实体

解决方法:

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