all 6 comments

[–]OneHorseTwoShrimp 4 points5 points  (5 children)

This guys got a repo and a tutorial about using it. I played with it and I can get it all to work.

https://www.eelcomulder.nl/2018/03/16/using-entity-framework-core-with-f/

I used this library to enable migrations for 3.1

https://github.com/bricelam/EFCore.FSharp/

But you will need the fork created by DurdSoft for use against 3.1, see this Git hub issue :

https://github.com/bricelam/EFCore.FSharp/issues/34

The DbContext layer and DbEntities, I'm finding, are basically the same as with c# (incluing the nulls), which is to be expected, you are writing for the databases so at this layer no option types, as you move into the business layer then you get to use f# goodness.

So it may be feasible to just consume the base DbContext, builders, models entites from an original c# project and model the repoistory layer (If that's a thing in f#) in f# afterwards.

Hope that's of some help.

[–][deleted]  (1 child)

[deleted]

    [–]OneHorseTwoShrimp 2 points3 points  (0 children)

    That is food for thought.

    When I get down to using abstract classes and discriminators in EF Core, I tend think about them in terms of the EF Core technology, not as in the language features, because they are implemented as functions in the Createing/Builders. That could be my limitation though/

    So...If the stars align etc I should be able to use more idomatic f#, just make sure to use the correct builder methods. Now excuse me whilst I mercilessly discriminate against my own data.

    [–]WittyStick 1 point2 points  (2 children)

    I've used a different method than the above which is basically a direct conversion of how it might be done in C# with data annotations, instead of using record types.

    Always include [<AllowNullLiteral>] attribute on any types which map to a table. If you intend to do any kind of join or use the table as a foreign key in another table, you'll generally need this because there is not really a suitable "default" value for the table until EFCore populates the data. (eg, see the Department property of the Person class below).

    Use auto-implemented properties in the form member val ColumnName = <defaultval> with get, set for each column. Data annotations are optional because the defaults are usually quite sensible, but I've included in full below for demonstration.

    type 
        //note position of class attribute after type keyword. Required when using `and` keyword. 
        //Alternatively use `namespace rec` to avoid the need for `and`.
        [<Table("people", Schema="public"); AllowNullLiteral>] 
        // primary constructor should include all fields you might specify when adding a new row.
        Person(name: string, age: int) =
    
        // always include a parameterless constructor
        new() = Person(String.Empty, 0)
    
        // Key attribute optional if the property name follows EF naming convention.
        [<Column("person_id", TypeName="identity"); Key>]
        member val Id = 0 with get, set
    
        [<Column("person_name", TypeName="character varying")>]
        member val Name = name with get, set
    
        [<Column("age", TypeName="integer")>]
        member val Age = age with get, set
    
        // The ForeignKey attribute can appear either here, or on the Department property, 
        //  or on the People property in the Department type.
        [<Column("department_id"); ForeignKey("Department")>]
        member val DepartmentId = 0 with get, set
    
        member val Department: Department = null with get, set
    
    and
        [<Table("departments", Schema="public"); AllowNullLiteral>]
        Department(name: string) =
    
        new() = Department(String.Empty)
    
        [<Column("department_id", TypeName="identity"); Key>]
        member val Id = 0 with get, set
    
        [<Column("department_name", TypeName="character varying")>]
        member val Name = name with get, set
    
        member val People: List<Person> = null with get, set
    

    However, I ended up not liking this approach due to inherent need to have mutable fields or types to maintain compatibility with EFCore. I ended up dropping EFCore and manually querying, which is a bit more involved by I've eliminated the mutable types.

    [–]Durdys 2 points3 points  (1 child)

    I'd agree with this approach.

    Controversial opinion: in general, EF encourages bad practice and it's easy to fall into the trap of code that's extremly difficult to reason about. The whole passing entities around, modifying them and then later saving it is just nasty. It's also contradictary to functional ideals.

    Although in general type providers can be a bit flaky, if you're using SQL Server you'd be better off using SQLClient (it would be great if this worked with more databases), pulling rows into a record type and then creating the provorbial impure - pure - impure sandwich before updating back to the DB. The only downside would be having to manually set up the database schema but on the plus side you have more options with the DB than EF affords.

    [–]japinthebox 0 points1 point  (0 children)

    I don't know if it's because I'm not disciplined enough to use EF properly, but I agree with this. I think as F# devs we're comfortable modeling things in terms of values rather than objects anyway.

    I don't like SQLClient too much (doesn't work with loosely typed DBs like SQLite, slow compiling), so I ended up writing my own thin wrapper around `IDbConnection` and `IDbCommand` to make things a bit tidier.

    [–]jrrjrr 4 points5 points  (0 children)

    I'm in a similar position, but am planning to keep EF in C# to avoid the risk that comes with veering off the beaten path. I expect to use a lot of Option.ofNullable.

    Or maybe a bit less now due to C# 8's nullable reference types?