EFCore.FSharp 10.0.0

Edit this page

Single case union types (aka Simple types)

It's common to use single case discriminated unions to meaningfully represent data values. But EF does not know anything about this kind of type. Luckily this repository has some ways to help you to deal with they.

Configuring

We have two approaches to deal with single case union types which are a converter or an extension that searches for all Single Case Unions in your entities.

open EntityFrameworkCore.FSharp.Extensions

type PositiveInteger = PositiveInteger of int

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

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 =
       
        // setting manually each property
        builder.Entity<Blog>()
            .Property(fun x -> x.Votes)
            .HasConversion(SingleCaseUnionConverter<int, PositiveInteger>())
        |> ignore
        
        // OR
        
        // enables single clase unions for all entities
        builder.RegisterSingleUnionCases() 

    override _.OnConfiguring(options: DbContextOptionsBuilder) : unit =
           options
             .UseSqlite( "Data Source=dbName.db")
             .UseFSharpTypes() // enable queries for F# types
           |> ignore

Querying

You can query for equality without any problem

let blog =
   query {
       for blog in ctx.Blogs do
       where (blog.Votes = PositiveInteger 10)
       select blog
       headOrDefault
   }
   
 // or  
let blog' = ctx.Blogs.Where(fun b -> b.Votes = PositiveInteger 10).FirstOrDefault()

For querying with other types of operation you will need to unwrap the value inside the query

let blogQuery =
   query {
       for blog in ctx.Blogs do
       let (PositiveInteger votes) = blog.Votes
       where (votes > 0)
       select blog
       headOrDefault
   }

Private Constructor

This extension doesn't support private union case constructors.

Multiple items
union case PositiveInteger.PositiveInteger: int -> PositiveInteger

--------------------
type PositiveInteger = | PositiveInteger of int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
Multiple items
type CLIMutableAttribute = inherit Attribute new: unit -> CLIMutableAttribute

--------------------
new: unit -> CLIMutableAttribute
type Blog = { Id: Guid Title: string Votes: PositiveInteger }
Multiple items
type KeyAttribute = inherit Attribute new: unit -> unit
<summary>Denotes one or more properties that uniquely identify an entity.</summary>

--------------------
KeyAttribute() : KeyAttribute
Blog.Id: Guid
Multiple items
[<Struct>] type Guid = new: b: byte array -> unit + 6 overloads member CompareTo: value: Guid -> int + 1 overload member Equals: g: Guid -> bool + 1 overload member GetHashCode: unit -> int member ToByteArray: unit -> byte array + 1 overload member ToString: unit -> string + 2 overloads member TryFormat: utf8Destination: Span<byte> * bytesWritten: byref<int> * ?format: ReadOnlySpan<char> -> bool + 1 overload member TryWriteBytes: destination: Span<byte> -> bool + 1 overload static member (<) : left: Guid * right: Guid -> bool static member (<=) : left: Guid * right: Guid -> bool ...
<summary>Represents a globally unique identifier (GUID).</summary>

--------------------
Guid ()
Guid(b: byte array) : Guid
Guid(b: ReadOnlySpan<byte>) : Guid
Guid(g: string) : Guid
Guid(b: ReadOnlySpan<byte>, bigEndian: bool) : Guid
Guid(a: int, b: int16, c: int16, d: byte array) : 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
Guid(a: uint32, b: uint16, c: uint16, 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.Votes: PositiveInteger
Multiple items
type MyContext = new: unit -> MyContext override OnConfiguring: options: 'a -> unit override OnModelCreating: builder: 'a -> 'b member Blogs: IQueryable<Blog>

--------------------
new: unit -> MyContext
Multiple items
type DefaultValueAttribute = inherit Attribute new: unit -> DefaultValueAttribute + 1 overload member Check: bool

--------------------
new: unit -> DefaultValueAttribute
new: check: bool -> DefaultValueAttribute
MyContext._blogs: obj
val set: elements: seq<'T> -> Set<'T> (requires comparison)
val ignore: value: 'T -> unit
type unit = Unit
val blog: Blog
val query: Linq.QueryBuilder
val ctx: MyContext
property MyContext.Blogs: IQueryable<Blog> with get, set
custom operation: where (bool) Calls Linq.QueryBuilder.Where
custom operation: select ('Result) Calls Linq.QueryBuilder.Select
custom operation: headOrDefault Calls Linq.QueryBuilder.HeadOrDefault
val blog': 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 b: Blog
val blogQuery: Blog
val votes: int