efcore / EFCore.FSharp

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

Failure to compile database-first Context in 6.0.1 #118

Closed ntwilson closed 2 years ago

ntwilson commented 2 years ago

Describe the bug When running dotnet ef dbcontext scaffold on my database, I'm getting F# code that fails to compile since the v6 upgrade. I'm seeing

            entity.HasOne(fun d -> d.User).WithOne("Memberships")
                .HasForeignKey<Memberships>((fun d -> (d.UserId) :> obj))

where the lambda on the second line infers the wrong type for d, since multiple types have a UserId field (I'm generating the Domain using F# records, not classes). The solution I found is to change .HasForeignKey<Memberships> to just .HasForeignKey, and somehow the inference works better. Or just adding an annotation to d. I'm also seeing

            entity.HasMany(fun d -> d.Role)
                .WithMany(fun p -> p.User)
                .UsingEntity<System.Collections.Generic.Dictionary<string, obj>>(
                    "UsersInRoles",
                    (fun l -> l.HasOne<Roles>().WithMany().HasForeignKey("RoleId").OnDelete(DeleteBehavior.ClientSetNull)).HasConstraintName("UsersInRole_Role"),
                    (fun r -> r.HasOne<Users>().WithMany().HasForeignKey("UserId").OnDelete(DeleteBehavior.ClientSetNull)).HasConstraintName("UsersInRole_User"),
                    fun j ->
                        j.HasKey("UserId", "RoleId").HasName({code.Literal(key.GetName())}) |> ignore
                        j.ToTable("UsersInRoles") |> ignore

                        j.IndexerProperty<Guid>("UserId").HasColumnName("UserId") |> ignore

                        j.IndexerProperty<Guid>("RoleId").HasColumnName("RoleId") |> ignore
                    ) |> ignore

which has 3 issues:

  1. l and r in the lambdas are not having their type inferred. I can manually annotate them as EntityTypeBuilder<_> to get it to compile. There might be some other solution as well.
  2. the lambda itself has the .HasConstraintName member called on it, and it fails to find the HasConstraintName member. I have no idea what the solution is for this, and I can only fix it by deleting that member.
  3. In that 3rd lambda, the {code.Literal(key.GetName())} fails to compile. Looks like maybe that's some leftover code-gen that didn't get executed??

I'm sorry that I don't have a better way to reproduce this! If you're having trouble reproducing it, I can try assembling a SQL script that would create the tables needed to observe these problems.

Thanks for all your effort on this project! I'm really excited to use EF Core 6 and 100% F#!

Desktop (please complete the following information):

simon-reynolds commented 2 years ago

Hi @ntwilson Thank you so much for the feedback. If you can include a SQL sample of the relevant tables I'd really appreciate it I'll take a look at this as time allows over the next couple of days

ntwilson commented 2 years ago

Thanks @simon-reynolds! From the errors, these are the only tables that I can see are involved, and I can share all of them:

CREATE TABLE [dbo].[Applications](
    [ApplicationId] [uniqueidentifier] NOT NULL,
    [ApplicationName] [nvarchar](235) NOT NULL,
    [Description] [nvarchar](256) NULL,
 CONSTRAINT [PK__Applicat__C93A4C990519C6AF] PRIMARY KEY CLUSTERED 
(
    [ApplicationId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Users](
    [UserId] [uniqueidentifier] NOT NULL,
    [ApplicationId] [uniqueidentifier] NOT NULL,
    [UserName] [nvarchar](50) NOT NULL,
    [IsAnonymous] [bit] NOT NULL,
    [LastActivityDate] [datetime] NOT NULL,
 CONSTRAINT [PK__Users__1788CC4C19DFD96B] PRIMARY KEY CLUSTERED 
(
    [UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [UN_UserName] UNIQUE NONCLUSTERED 
(
    [UserName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Users]  WITH CHECK ADD  CONSTRAINT [User_Application] FOREIGN KEY([ApplicationId])
REFERENCES [dbo].[Applications] ([ApplicationId])
GO

CREATE TABLE [dbo].[Roles](
    [RoleId] [uniqueidentifier] NOT NULL,
    [ApplicationId] [uniqueidentifier] NOT NULL,
    [RoleName] [nvarchar](256) NOT NULL,
    [Description] [nvarchar](256) NULL,
 CONSTRAINT [PK__Roles__8AFACE1A73BA3083] PRIMARY KEY CLUSTERED 
(
    [RoleId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [UN_RoleName] UNIQUE NONCLUSTERED 
(
    [RoleName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Roles]  WITH CHECK ADD  CONSTRAINT [RoleEntity_Application] FOREIGN KEY([ApplicationId])
REFERENCES [dbo].[Applications] ([ApplicationId])
GO

CREATE TABLE [dbo].[Memberships](
    [UserId] [uniqueidentifier] NOT NULL,
    [ApplicationId] [uniqueidentifier] NOT NULL,
    [Password] [nvarchar](128) NOT NULL,
    [PasswordFormat] [int] NOT NULL,
    [PasswordSalt] [nvarchar](128) NOT NULL,
    [Email] [nvarchar](256) NULL,
    [PasswordQuestion] [nvarchar](256) NULL,
    [PasswordAnswer] [nvarchar](128) NULL,
    [IsApproved] [bit] NOT NULL,
    [IsLockedOut] [bit] NOT NULL,
    [CreateDate] [datetime] NOT NULL,
    [LastLoginDate] [datetime] NOT NULL,
    [LastPasswordChangedDate] [datetime] NOT NULL,
    [LastLockoutDate] [datetime] NOT NULL,
    [FailedPasswordAttemptCount] [int] NOT NULL,
    [FailedPasswordAttemptWindowStart] [datetime] NOT NULL,
    [FailedPasswordAnswerAttemptCount] [int] NOT NULL,
    [FailedPasswordAnswerAttemptWindowsStart] [datetime] NOT NULL,
    [Comment] [nvarchar](256) NULL,
 CONSTRAINT [PK__Membersh__1788CC4C4222D4EF] PRIMARY KEY CLUSTERED 
(
    [UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Memberships]  WITH CHECK ADD  CONSTRAINT [MembershipEntity_Application] FOREIGN KEY([ApplicationId])
REFERENCES [dbo].[Applications] ([ApplicationId])
GO

ALTER TABLE [dbo].[Memberships]  WITH CHECK ADD  CONSTRAINT [MembershipEntity_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[Users] ([UserId])
GO

CREATE TABLE [dbo].[UsersInRoles](
    [UserId] [uniqueidentifier] NOT NULL,
    [RoleId] [uniqueidentifier] NOT NULL,
 CONSTRAINT [PK__UsersInR__AF2760AD1DB06A4F] PRIMARY KEY CLUSTERED 
(
    [UserId] ASC,
    [RoleId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[UsersInRoles]  WITH CHECK ADD  CONSTRAINT [UsersInRole_Role] FOREIGN KEY([RoleId])
REFERENCES [dbo].[Roles] ([RoleId])
GO

ALTER TABLE [dbo].[UsersInRoles]  WITH CHECK ADD  CONSTRAINT [UsersInRole_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[Users] ([UserId])
GO

However I can't say for sure that with only these 5 tables it reproduces all of the issues or if other tables (that I probably can't share) are involved in making the errors show up. I can probably get that answer to you tomorrow.

ntwilson commented 2 years ago

Okay, I just verified that I can observe all the errors in the description when I create a new database with that script, and start a new EFCore.FSharp project using that database. The command I used to do the code-gen was

dotnet ef dbcontext scaffold $connString Microsoft.EntityFrameworkCore.SqlServer -c EF6TestDbContext --use-database-names --no-onconfiguring
simon-reynolds commented 2 years ago

This has been fixed, releasing 6.0.2 now to NuGet

ntwilson commented 2 years ago

Wow, thanks for the fast work!