F# SqlProvider 无法使用 ODBC 连接更新 dBase DBF 文件中的更改

F# SqlProvider fails to update changes in a dBase DBF file with ODBC connection

我有以下 F# 代码

open FSharp.Data.Sql
open FSharp.Data.Sql.Runtime
open System.IO

[<Literal>]
let private schemaConn = @"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=C:\Citect\User\NPM;" 
type private schema = SqlDataProvider<Common.DatabaseProviderTypes.ODBC, schemaConn>
let private connStringFormat = Printf.StringFormat<string->string>(@"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=%s;")

type internal Project = {
    name : string
    path : string
    dcx : schema.dataContext
}

[<Literal>]
let private cUserPath = @"C:\Citect\User"

let private findPath projectName = 
    Directory.GetDirectories(cUserPath, projectName, SearchOption.AllDirectories)
    |> Array.find (fun d -> d.Contains("web") |> not)

let internal connect projectName =
    let path' = findPath projectName
    let connString = sprintf connStringFormat path'
    let dcx' = schema.GetDataContext(connString)
    { name = projectName; path = path'; dcx = dcx' }

let internal updVariable (project : Project) variable = 
    let dcx = project.dcx
    let q = query {
        for v in dcx.Dbo.Variable do
            where (v.Addr = "%MW217.0")
            select v
            exactlyOne
    }
    q.Addr <- "QQQ"
    dcx.SubmitUpdates() //error

let internal prj = connect "NPMUG_SCC35"
updVariable prj ()

连接和查询按预期工作,但是当我尝试更新数据源时,我收到来自 odbc 驱动程序的以下错误:

Message -> ERROR [HY092] [Microsoft][ODBC dBase Driver]Invalid attribute/option identifier Source -> odbcjt32.dll

有没有办法让它工作,或者我是否需要放弃类型提供程序并求助于 OleDb?

更新

禁用事务会使事情好转一些,现在错误是由于我必须使用的 dbf 文件中缺少主键。

唯一更改的代码是获取数据上下文

let dcx = schema.GetDataContext( { Timeout = TimeSpan.MaxValue; IsolationLevel = Transactions.IsolationLevel.DontCreateTransaction } : FSharp.Data.Sql.Transactions.TransactionOptions)

新的错误是:

System.Exception: Error - you cannot update an entity that does not have a primary key. (dbo.variable) at FSharp.Data.Sql.Providers.OdbcProvider.createUpdateCommand(IDbConnection con, StringBuilder sb, SqlEntity entity, FSharpList`1 changedColumns)

at .$Providers.Odbc.FSharp-Data-Sql-Common-ISqlProvider-ProcessUpdates@648-4.Invoke(SqlEntity e) at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc2 action, IEnumerable1 source) at FSharp.Data.Sql.Providers.OdbcProvider.FSharp-Data-Sql-Common-ISqlProvider-ProcessUpdates(IDbConnection con, ConcurrentDictionary2 entities, TransactionOptions transactionOptions, FSharpOption1 timeout) at .$SqlRuntime.DataContext.f@1-69(SqlDataContext __, IDbConnection con, Unit unitVar0) at FSharp.Data.Sql.Runtime.SqlDataContext.FSharp-Data-Sql-Common-ISqlDataContext-SubmitPendingChanges()

知道如何处理这个问题吗?

我找到了一种 tricky/dirty 方法,我将其归类为变通方法而不是真正的解决方案,但它适用于我的情况;所以我要用它 unless/until 别人建议一个结论性的。

为了使类型提供程序正常工作,我需要做 2 件常规工作流程中没有的事情:

  1. 需要在禁用事务的情况下检索数据上下文
  2. 在对 DBF 执行更改操作之前,我创建了一个主数据库 使用较低级别 SQL 语句
  3. 在该 DBF 上键入

这里是工作代码

[<Literal>]
let private schemaConn = @"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=C:\Citect\User\NPM;READONLY=FALSE" 
type private schema = SqlDataProvider<Common.DatabaseProviderTypes.ODBC, schemaConn>
let private connStringFormat = Printf.StringFormat<string->string>(@"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=%s;READONLY=FALSE")

type internal Project = {
    name : string
    path : string
    dcx : schema.dataContext
}

[<Literal>]
let private cUserPath = @"C:\Citect\User"

let private findPath projectName = 
    Directory.GetDirectories(cUserPath, projectName, SearchOption.AllDirectories)
    |> Array.find (fun d -> d.Contains("web") |> not)

let private createPK (cn : IDbConnection) = 
    let cm = cn.CreateCommand()
    cm.CommandText <- "ALTER TABLE Variable ADD PRIMARY KEY (Name)"
    try
        cn.Open()
        cm.ExecuteNonQuery() |> ignore
    finally cn.Close()

let internal connect projectName =
    let path' = findPath projectName
    let connString = sprintf connStringFormat path'
    let transOptions = { Timeout = TimeSpan.FromSeconds(3.0); IsolationLevel = Transactions.IsolationLevel.DontCreateTransaction }
    let dcx' = schema.GetDataContext(connectionString = connString, transactionOptions = transOptions)
    dcx'.CreateConnection() |> createPK
    { name = projectName; path = path'; dcx = dcx' }

let internal updVariable (project : Project) variable = 
    let dcx = project.dcx
    let q = query {
        for v in dcx.Dbo.Variable do
            where (v.Addr = "%MW217.0")
            select v
            exactlyOne
    }
    q.Addr <- "QQQ"
    dcx.SubmitUpdates()

let internal prj = connect "NPMUG_SCC35"
updVariable prj ()