Querying option types

Configuring

EF Core by default does not know anything about the way of F# deals with nullable values which is the Option type.

This project has a way to enable query for columns defined as an option, first you need to call .UseFSharpTypes() on the context configuration builder

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
open EntityFrameworkCore.FSharp.Extensions

[<CLIMutable>]
type Blog = {
    [<Key>]
    Id : Guid
    Title : string
    Content : string option
}

type MyContext () =
    inherit DbContext()

    [<DefaultValue>]
    val mutable private _blogs : DbSet<Blog>
    member this.Blogs with get() = this._blogs and set v = this._blogs <- v

    override _.OnModelCreating builder =
        builder.RegisterOptionTypes() // enables option values for all entities

    override _.OnConfiguring(options: DbContextOptionsBuilder) : unit =
           options.UseSqlite("Data Source=dbName.db")
                  .UseFSharpTypes()
           |> ignore

Note that the .UseFSharpTypes() will exist independent of the database, the UseSqlite is just for the example. If you are using an SQL server with UseSqlServer or UsePostgres for Postgres, or any other database it will exist.

Querying

With that, you will be able to query your entity by an optional column.

Querying entities with Some content

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let queryWhereSome (ctx: MyContext) =

    // Querying entities with `Some` content
    let blogs =
        query {
            for blog in ctx.Blogs do
            where blog.Content.IsSome
            select blog
        }

    // or using Linq extensions
    let blog =
        ctx.Blogs.Where(fun x -> x.Content.IsSome)

    ()

Querying entities with None content

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let queryWhereNone (ctx: MyContext) =

    let blogs =
        query {
            for blog in ctx.Blogs do
            where blog.Content.IsNone
            select blog
        }

    // or using Linq extensions
    let blog =
        ctx.Blogs.Where(fun x -> x.Content.IsNone)

    ()

Querying optional values by value

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let queryWhereValue (ctx: MyContext) =

    let blogs =
        query {
            for blog in ctx.Blogs do
            where (blog.Content.Value = "Some text")
            select blog
        }

    // or using Linq extensions
    let blog =
        ctx.Blogs.Where(fun x -> x.Content.Value = "Some text")

    ()
namespace EntityFrameworkCore
namespace EntityFrameworkCore.FSharp
module Extensions

from EntityFrameworkCore.FSharp
Multiple items
type CLIMutableAttribute =
  inherit Attribute
  new : unit -> CLIMutableAttribute

--------------------
new : unit -> CLIMutableAttribute
type Blog =
  { Id: Guid
    Title: string
    Content: string option }
Multiple items
type KeyAttribute =
  inherit Attribute
  new : unit -> KeyAttribute

--------------------
KeyAttribute() : KeyAttribute
Blog.Id: Guid
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 5 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    member TryFormat : destination:Span<char> * charsWritten:int * ?format:ReadOnlySpan<char> -> bool
    member TryWriteBytes : destination:Span<byte> -> bool
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    ...
  end

--------------------
Guid ()
Guid(b: byte []) : Guid
Guid(b: ReadOnlySpan<byte>) : Guid
Guid(g: string) : Guid
Guid(a: int, b: int16, c: int16, d: byte []) : Guid
Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Blog.Title: string
Multiple items
val string : value:'T -> string

--------------------
type string = String
Blog.Content: string option
type 'T option = Option<'T>
Multiple items
type MyContext =
  inherit DbContext
  new : unit -> MyContext
  val mutable private _blogs: DbSet<Blog>
  override OnConfiguring : options:DbContextOptionsBuilder -> unit
  override OnModelCreating : builder:ModelBuilder -> unit
  member Blogs : DbSet<Blog>
  member Blogs : DbSet<Blog> with set

--------------------
new : unit -> MyContext
Multiple items
type DbContext =
  new : options:DbContextOptions -> DbContext
  member Add<'TEntity> : entity:'TEntity -> EntityEntry<'TEntity> + 1 overload
  member AddAsync<'TEntity> : entity:'TEntity * ?cancellationToken:CancellationToken -> ValueTask<EntityEntry<'TEntity>> + 1 overload
  member AddRange : [<ParamArray>] entities:obj[] -> unit + 1 overload
  member AddRangeAsync : [<ParamArray>] entities:obj[] -> Task + 1 overload
  member Attach<'TEntity> : entity:'TEntity -> EntityEntry<'TEntity> + 1 overload
  member AttachRange : [<ParamArray>] entities:obj[] -> unit + 1 overload
  member ChangeTracker : ChangeTracker
  member ContextId : DbContextId
  member Database : DatabaseFacade
  ...

--------------------
DbContext() : DbContext
DbContext(options: DbContextOptions) : DbContext
Multiple items
type DefaultValueAttribute =
  inherit Attribute
  new : unit -> DefaultValueAttribute
  new : check:bool -> DefaultValueAttribute
  member Check : bool

--------------------
new : unit -> DefaultValueAttribute
new : check:bool -> DefaultValueAttribute
MyContext._blogs: DbSet<Blog>
type DbSet<'TEntity (requires reference type)> =
  member Add : entity:'TEntity -> EntityEntry<'TEntity>
  member AddAsync : entity:'TEntity * ?cancellationToken:CancellationToken -> ValueTask<EntityEntry<'TEntity>>
  member AddRange : [<ParamArray>] entities:'TEntity[] -> unit + 1 overload
  member AddRangeAsync : [<ParamArray>] entities:'TEntity[] -> Task + 1 overload
  member AsAsyncEnumerable : unit -> IAsyncEnumerable<'TEntity>
  member AsQueryable : unit -> IQueryable<'TEntity>
  member Attach : entity:'TEntity -> EntityEntry<'TEntity>
  member AttachRange : [<ParamArray>] entities:'TEntity[] -> unit + 1 overload
  member EntityType : IEntityType
  member Equals : obj:obj -> bool
  ...
val this : MyContext
val set : elements:seq<'T> -> Set<'T> (requires comparison)
val v : DbSet<Blog>
val builder : ModelBuilder
member ModelBuilder.RegisterOptionTypes : unit -> unit
val options : DbContextOptionsBuilder
Multiple items
type DbContextOptionsBuilder =
  new : unit -> DbContextOptionsBuilder + 1 overload
  member AddInterceptors : interceptors:IEnumerable<IInterceptor> -> DbContextOptionsBuilder + 1 overload
  member ConfigureLoggingCacheTime : timeSpan:TimeSpan -> DbContextOptionsBuilder
  member ConfigureWarnings : warningsConfigurationBuilderAction:Action<WarningsConfigurationBuilder> -> DbContextOptionsBuilder
  member EnableDetailedErrors : ?detailedErrorsEnabled:bool -> DbContextOptionsBuilder
  member EnableSensitiveDataLogging : ?sensitiveDataLoggingEnabled:bool -> DbContextOptionsBuilder
  member EnableServiceProviderCaching : ?cacheServiceProvider:bool -> DbContextOptionsBuilder
  member EnableThreadSafetyChecks : ?enableChecks:bool -> DbContextOptionsBuilder
  member Equals : obj:obj -> bool
  member GetHashCode : unit -> int
  ...

--------------------
type DbContextOptionsBuilder<'TContext (requires 'TContext :> DbContext)> =
  inherit DbContextOptionsBuilder
  new : unit -> DbContextOptionsBuilder<'TContext> + 1 overload
  member AddInterceptors : interceptors:IEnumerable<IInterceptor> -> DbContextOptionsBuilder<'TContext> + 1 overload
  member ConfigureLoggingCacheTime : timeSpan:TimeSpan -> DbContextOptionsBuilder<'TContext>
  member ConfigureWarnings : warningsConfigurationBuilderAction:Action<WarningsConfigurationBuilder> -> DbContextOptionsBuilder<'TContext>
  member EnableDetailedErrors : ?detailedErrorsEnabled:bool -> DbContextOptionsBuilder<'TContext>
  member EnableSensitiveDataLogging : ?sensitiveDataLoggingEnabled:bool -> DbContextOptionsBuilder<'TContext>
  member EnableServiceProviderCaching : ?cacheServiceProvider:bool -> DbContextOptionsBuilder<'TContext>
  member EnableThreadSafetyChecks : ?checksEnabled:bool -> DbContextOptionsBuilder<'TContext>
  member LogTo : filter:Func<EventId, LogLevel, bool> * logger:Action<EventData> -> DbContextOptionsBuilder<'TContext> + 4 overloads
  member Options : DbContextOptions<'TContext>
  ...

--------------------
DbContextOptionsBuilder() : DbContextOptionsBuilder
DbContextOptionsBuilder(options: DbContextOptions) : DbContextOptionsBuilder

--------------------
DbContextOptionsBuilder() : DbContextOptionsBuilder<'TContext>
DbContextOptionsBuilder(options: DbContextOptions<'TContext>) : DbContextOptionsBuilder<'TContext>
type unit = Unit
(extension) DbContextOptionsBuilder.UseSqlite(?sqliteOptionsAction: Action<Infrastructure.SqliteDbContextOptionsBuilder>) : DbContextOptionsBuilder
(extension) DbContextOptionsBuilder.UseSqlite(connectionString: string,?sqliteOptionsAction: Action<Infrastructure.SqliteDbContextOptionsBuilder>) : DbContextOptionsBuilder
(extension) DbContextOptionsBuilder.UseSqlite(connection: Data.Common.DbConnection,?sqliteOptionsAction: Action<Infrastructure.SqliteDbContextOptionsBuilder>) : DbContextOptionsBuilder
val ignore : value:'T -> unit
val queryWhereSome : ctx:MyContext -> unit
val ctx : MyContext
val blogs : IQueryable<Blog>
val query : Linq.QueryBuilder
val blog : Blog
property MyContext.Blogs: DbSet<Blog> with get, set
custom operation: where (bool)

Calls Linq.QueryBuilder.Where
property Option.IsSome: bool with get
custom operation: select ('Result)

Calls Linq.QueryBuilder.Select
val blog : IQueryable<Blog>
(extension) Collections.Generic.IEnumerable.Where<'TSource>(predicate: Func<'TSource,bool>) : Collections.Generic.IEnumerable<'TSource>
(extension) Collections.Generic.IEnumerable.Where<'TSource>(predicate: Func<'TSource,int,bool>) : Collections.Generic.IEnumerable<'TSource>
(extension) IQueryable.Where<'TSource>(predicate: Expressions.Expression<Func<'TSource,bool>>) : IQueryable<'TSource>
(extension) IQueryable.Where<'TSource>(predicate: Expressions.Expression<Func<'TSource,int,bool>>) : IQueryable<'TSource>
val x : Blog
val queryWhereNone : ctx:MyContext -> unit
property Option.IsNone: bool with get
val queryWhereValue : ctx:MyContext -> unit
property Option.Value: string with get