I am attempting to make a mock implementation of IDbSet<T>
, and I happen to be doing it in F#.
type MockDbSet<'T when 'T : not struct>(items:seq<'T>) =
let collection = ResizeArray(items)
new () = MockDbSet(Seq.empty)
interface IDbSet<'T> with
member x.Add entity = collection.Add entity; entity
member x.Attach entity = collection.Add entity; entity
member x.Remove entity = collection.Remove entity |> ignore; entity
member x.Create() = Unchecked.defaultof<'T>
member x.Create<'TD when 'TD : not struct and 'TD :> 'T>() = Unchecked.defaultof<'TD>
member x.Find([<ParamArray>] values) = raise <| NotImplementedException()
member x.Local = Collections.ObjectModel.ObservableCollection(collection)
interface System.Collections.Generic.IEnumerable<'T> with
member x.GetEnumerator() =
collection.GetEnumerator() :> System.Collections.Generic.IEnumerator<_>
interface System.Collections.IEnumerable with
member x.GetEnumerator() =
collection.GetEnumerator() :> System.Collections.IEnumerator
interface IQueryable<'T> with
member x.ElementType = typeof<'T>
member x.Expression =
collection.AsQueryable().Expression
member x.Provider =
collection.AsQueryable().Provider
Everything is fine, except for this line:
member x.Create<'TD when 'TD : not struct and 'TD :> 'T>() = Unchecked.defaultof<'TD>
...which gives me these compiler errors:
error FS0698: Invalid constraint: the type used for the constraint is sealed, which means the constraint could only be satisfied by at most one solution
warning FS0064: This construct causes code to be less generic than indicated by the type annotations. The type variable 'TD has been constrained to be type ''T'.
error FS0663: This type parameter has been used in a way that constrains it to always be ''T when 'T : not struct'
error FS0661: One or more of the explicit class or function type variables for this binding could not be generalized, because they were constrained to other types
This line is attempting to implement this method, which according to that page has the following signature in C#:
TDerivedEntity Create<TDerivedEntity>()
where TDerivedEntity : class, TEntity
And this signature in F#:
abstract Create : unit -> 'TDerivedEntity when 'TDerivedEntity : not struct and 'TEntity
When I try to use the example F# signature, I get a variety of syntax errors, which doesn't surprise me because that signature doesn't even look like valid F#.
I'm not really sure what to make of these error messages, or how to write my constraints to satisfy both the interface and the F# compiler. I'm starting to wonder if it's even possible to implement this particular Microsoft interface in this particular Microsoft programming language. Any suggestions would be welcomed.
The method Create
needs a subtype constraint between 2 generic type parameters. I'm afraid there is no way to add a subtype constraint to a generic type parameter based on another one in F#. They're always assumed to be equal, see the spec New constraints of the form type :> 'b are solved again as type = 'b.
See this related answer to a similar problem.
We should request to include this feature in the next F# version.
I was very disappointed by this at first. I still am in some ways, but there is a workaround in EF6. You can inherit DbSet<'TEntity>
directly, and use overrides to implement the collection in memory. This will suffice for most cases; you can inherit from this type if you want a concrete implementation of Find
.
type FakeDbSet<'TEntity when 'TEntity : not struct>(items: seq<'TEntity>) =
inherit DbSet<'TEntity>()
let data = ObservableCollection<'TEntity>(items)
let query = data.AsQueryable()
new() = FakeDbSet(Seq.empty)
override __.Add(item: 'TEntity) = data.Add(item); item
override __.Remove(item: 'TEntity) = data.Remove(item) |> ignore; item
override __.Attach(item: 'TEntity) = data.Add(item); item
override __.Create() = Activator.CreateInstance<'TEntity>()
override __.Local with get() = data
interface System.Collections.Generic.IEnumerable<'TEntity> with
member __.GetEnumerator() = data.GetEnumerator() :> System.Collections.Generic.IEnumerator<_>
interface System.Collections.IEnumerable with
member __.GetEnumerator() = data.GetEnumerator() :> System.Collections.IEnumerator
interface IQueryable<'TEntity> with
member __.ElementType = typeof<'TEntity>
member __.Expression = query.Expression
member __.Provider = query.Provider
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With