F#TypeProviders,如何更改数据库?

zul*_*ain 2 f# type-providers

我使用F#TypeProviders处理来自两个不同服务器但很多数据库的SQL数据.它现在很好,除了现在的速度变得非常慢,因为我添加了更多的文件.截至目前,每个文件都有自己的连接字符串.我正在考虑将连接字符串和类型提供程序提取到外部项目,并能够将数据库作为参数传递.为了实现这一点,我尝试以下内容,在以下链接F#Type Provider的第3个答案之后,使用字符串变量创建:

open System.Configuration
open System.Data.SqlClient
open Microsoft.FSharp.Data.TypeProviders
open System.Data.SqlClient

let con = ConfigurationManager.AppSettings.Item("conDebug")
let setDB dbName =
    let cn = new SqlConnectionStringBuilder(con)
    cn.InitialCatalog <- dbName
    cn.ConnectionString

[<Literal>]
let conStr = setDB "AnotherDB"

type selectedDb = SqlDataConnection<conStr>
Run Code Online (Sandbox Code Playgroud)

但继续得到错误: 这不是一个有效的常量表达式 是否也可以使用该ChangeDatabase成员SqlDataConnection?

更新: 在这种情况下,建议的答案不会解决我的问题.以下是与两个不同服务器的不同表格相似的内容:

[<Literal>]
let sourceConStr = @"sourceCon"

type sourceDB = SqlDataConnection<sourceConStr>

type person =
    { Id : string
      birthDate : Nullable<DateTime>
      firstName : string
      lastName : string
      dateCreated : DateTime }

type processPeople()
    member this.makePerson(oldPerson: source.ServiceTypes.Person) =
        { Id = oldPerson.oldId
        ......some other processing of old data
        }

     member this.transferPeople conStr =
        use source = sourceDB.GetDataContext()
        use sw = File.AppendText("./ImportLog.txt")
        use targetCon = new SqlConnection(conStr)
        targetCon.Open()
        let query = "insert query"            

        let insert person =
            try
                use cmd = new SqlCommand(query, targetCon)
                ....setting parameters stuff......                
                cmd.ExecuteNonQuery() |> ignore
            with sx -> sw.WriteLine(sx.Message)
        source.source.ServiceTypes.Person
        |> Seq.map this.makePerson
        |> Seq.filter (fun p -> p.Id <> null && p.birthDate.HasValue )
        |> Seq.iter insert
        printfn "Done"
Run Code Online (Sandbox Code Playgroud)

现在我需要为每个表执行此操作,即每个表的类和文件增长编译以及intellisense启动需要这么长时间.

Gen*_*ski 6

您尝试采取的方法存在根本缺陷.您希望在运行时从应用程序配置中获取连接字符串,并提供SqlDataConnection类型提供程序以使其与底层数据库相结合.

但是这种类型的提供者在工作流的运行时阶段根本无法做任何事情,因为它的作业必须在编译时已经在编译时已知的一些编译时完成.

然后,您可能会问,如果我们想要编译一次代码后,使用类型提供程序有什么意义,能够使用在运行时配置的数据库?

是的,但是我们确实期望类型提供者工作的结果适用于结构相同的数据库,对吗?

那么,出路就是提供类提供商在一个数据库来完成它的工作compileTimeDB与字面在编译时连接字符串知compileTimeCC,并考虑我们让所有的好东西(类型检查,智能感知,...)参数在连接字符串.此连接字符串参数值runTimeCC可以在运行时以任何所需方式设置,只要它指向runTimeDB具有相同模式的数据库即可compileTimeDB.

用下面的一些代码说明这个基本原理:

[<Literal>]
let compileTimeCC = @"Data Source=(localdb)\ProjectsV12;Initial Catalog=compileTimeDB;Integrated Security=True;"
.....
type MySqlConnection = SqlDataConnection<ConnectionString = compileTimeCC>
// Type provider is happy, let it help us writing our DB-related code
.....
let db = MySqlConnection.GetDataContext(runTimeCC)
// at run time db will be a runTimeDB set by connection string runTimeCC that can be any
// as long as runTimeDB and compileTimeDB has same schema
.....
Run Code Online (Sandbox Code Playgroud)

更新作为问题作者使他的问题背景更清楚我可以建议在给定TP时接近这个更具体的建议.由于SO答案应该相当简洁,因此我们将两种遗留Person类型OldPersonT1和OldPersonT2数据源以及一种当代ModernPerson类型作为目的地进行限制.我在这里谈论类型,它可以是你想要的数据库农场周围的实例.

现在,让我们为您创建一个单独的数据库localdb命名CompileTypeDB和创建对应于表运行SQL脚本OldPersonT1,OldPersonT2以及ModernPerson(这是一个时间的锻炼,也没有真正的数据移动将参与).这将是SqlDataConnectionTP 的单一信息来源.

准备好之后,让我们回到代码:

type CTSqlConn = SqlDataConnection<ConnectionString = @"Data Source=(LocalDB)\Projectsv12;Initial Catalog=myCompileTimeDB;Integrated Security=True">

type OldPersonT1 = CTSqlConn.ServiceTypes.OldPersonT1 // just for brevity
type OldPersonT2 = CTSqlConn.ServiceTypes.OldPersonT2
type ModernPerson = CTSqlConn.ServiceTypes.ModernPerson
Run Code Online (Sandbox Code Playgroud)

然后,使用以下静态成员扩充每个遗留类型(以下OldPersonT1仅为简洁起见):

type CTSqlConn.ServiceTypes.OldPersonT1 with
    static member MakeModernPersons(rtConn: string) =
        let projection (old: OldPersonT1) =
            // Just a direct copy, but you may be very flexible in spreading verification
            // logic between query, projection, and even makeModernPersons function
            // that will be processing IQueryable<ModernPerson>
            let mp = ModernPerson()
                mp.Id <- old.Id
                mp.birthDate <- old.birthDate
                mp.firstName <- old.firstName 
                mp.lastName <- old.lastName 
                mp.dateCreated <- old.dateCreated
                mp 
        query {
            for oldPerson in (CTSqlConn.GetDataContext(rtConn)).OldPersonT1 do
            select (projection oldPerson)
        }
Run Code Online (Sandbox Code Playgroud)

现在,您只需通过评估即可IQueryable<ModernPerson>从任何类型OldPersonT1的数据源中获取

OldPersonT1.MakeModernPersons("real time connection string to any DB having OldPersonT1 table")
Run Code Online (Sandbox Code Playgroud)

为了实现这一点,实时DB可能与编译时DB不完全相同,它只应包含OldPersonT1具有和依赖的所有内容.对于OldPersonT2或任何其他变体类型也是如此:通过MakeModernPersons为每个变体类型实现一次,您将获得所有数据源实例.

使用数据目标需要一个带签名的功能

let makeModernPersons destinationConnStr (source: IQueryable<ModernPerson>) =
...
Run Code Online (Sandbox Code Playgroud)

现在Person,只需操作两个实时连接字符串的值,即可涵盖数据源和目标的所有可能组合.

非常粗略,但这个想法似乎很清楚.