Search code examples
f#fsharp.data.sqlclient

F# SqlCommandProvider defer eval until runtime


I am trying to use SqlCommandProvider (part of FSharp.Data.SqlClient) to create a column if it does not exist and then set a constraint (self referencing). The problems is that it will not compile because it detects that the column as part of the constraint does not yet exist. I tried wrapping it in another IF block to no avail.

[<Literal>]
let CreateParentColumn = "
    IF COL_LENGTH('Company', 'ParentId') IS NULL
    BEGIN
        ALTER TABLE Company
        ADD ParentId INT

        ALTER TABLE Company
        ADD CONSTRAINT FK_ParentIdCompanyId FOREIGN KEY (ParentId)
        REFERENCES Company(CompanyId);
    END
"

type CreateParentIdColumn  = SqlCommandProvider<CreateParentColumn, connectionString>

I would greatly prefer not to use dynamic sql. I was wondering if there was a way to defer evaluation so that it would run properly (note, the query itself runs fine in SQL Management Studio)

Error 1 The type provider 'FSharp.Data.SqlCommandProvider' reported an error: Foreign key 'FK_ParentIdCustomerId' references invalid column 'ParentId' in referencing table 'Company'. Could not create constraint. See previous errors.


Solution

  • The brief answer is "No, this cannot be done".

    The more elaborate answer is "This cannot be done for the fundamental reason: run time database schema features should be already present at compile time in order to be anyhow utilized by FSharp.Data.SqlClient type provider".

    Just from the academic interest standpoint, it is not a problem (although a quite unusual practice) creating an extra column and a constraint in a database at run time and there is no need to involve dynamic SQL for this purpose. Can be easily achieved by using plain ADO side by side with type provider:

    use cmd = new System.Data.SqlClient.SqlCommand(CreateParentColumn, connectionString)
    cmd.ExecuteNonQuery() |> ignore
    

    The actual problem though is that this freshly created column cannot be of any use in FSharp.Data.SqlClient type provider context as it does not exist yet in database schema at compile time to be anyhow utilized by the type provider.

    Rephrasing, FSharp.Data.SqlClient type provider does not perform any action at run time, its job has been already completed at compile time.