efcore / EFCore.FSharp

Adds F# design-time support to EF Core
MIT License
228 stars 26 forks source link

How do you create an optional navigation property? #144

Open eastwing27 opened 1 year ago

eastwing27 commented 1 year ago

I'm trying to create Code First DB in F# project and I just can't solve this problem. The idea is to create an optional relation and have a navigation property but if I have option property, EF won't create a migration:

The property 'Product.Model' is of type 'Model' which is not supported by the current database provider. Either change the property CLR type, or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

If I ignore the property with Fluent API migration is created but it generates extra field and I get this warning:

The foreign key property 'Product.ModelId1' was created in shadow state because a conflicting property with the simple name 'ModelId' exists in the entity type, but is either not mapped, is already used for another relationship, or is incompatible with the associated primary key type. See https://aka.ms/efcore-relationships for information on mapping relationships in EF Core.

Here is the examples of models:

    type [<Table("products")>]
    [<CLIMutable>]
    Product = {
        [<Key>]
        [<Column("id")>]
        Id: Guid

        [<Column("name")>]
        [<Required>]
        Name: string

        //relations
        [<Column("model_id")>]
        ModelId: Guid option
        Model: Model option
    }

    and [<Table("models")>]
    [<CLIMutable>]
    Model = {
        [<Key>]
        [<Column("id")>]
        Id: Guid

        [<Column("name")>]
        [<Required>]
        Name: string

        //relations
        Products: Product seq
    }

In C#, adding virtual keyword would solve the issue, but is it possible with F#?

eastwing27 commented 1 year ago

Ok, so the problem seems to be deeper as I can not see any way to create an optional relation in F# way at all! If I remove nav property, the EF keeps generating extra ModelId1 prop. At the moment, the only thing that seems to work for me is removing navigation property at all and using Nullable for FK. If it's the only way, I guess I'll have to rewrite the whole DAL with C#.

eastwing27 commented 1 year ago

Is it possible no one faced that problem before? I thought it is something trivial. Or am I duplicating the question? I couldn't find it. If I do, please, point me to the answered one.

davidtme commented 1 year ago

@eastwing27 I have just start to take a look at efcore/EFCore.FSharp for a new project and this is the first problem I have hit.

I have play around with the ScaffoldOptions and best I could do was setting ScaffoldNullableColumnsAs = ScaffoldNullableColumnsAs.NullableTypes which creates code that complies but doesn't run with adding .IsRequired(false) to the modelBuilder

[<CLIMutable>]
type Lookup = {
    Id: int
    LookupValue: string
    Tests: Test seq
}

[<CLIMutable>]
type Test = {
    Id: int
    LookupId: Nullable<int>
    Name: string
    Lookup: Lookup
}

However this is a massive hack because even though the code runs the record types are sort of hack/nullable and f# doesn't expect them to be

let db = new BlogTestContext()
let result = db.Tests.AsQueryable().ToList() |> Seq.head

if result.Lookup = null || isNull result.Lookup then // Error FS0043 The type 'BlogTestDomain.Lookup' does not have 'null' as a proper value
    ()

if Object.ReferenceEquals(result.Lookup, null) then // Compiles
    ()

this is sort of a bit of a project killer :(