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

时间:2015-08-31 13:31:17

标签: f# type-providers

使用F#TypeProviders处理来自两个不同服务器但数据库的SQL数据。它现在很好,除了现在的速度变得非常慢,因为我添加了更多的文件。截至目前,每个文件都有自己的连接字符串。我正在考虑将连接字符串和类型提供程序提取到外部项目,并能够将数据库作为参数传递。为了实现这一点,我在以下链接F# Type Provider, create with string variable的第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>

但一直收到错误: 这不是有效的常量表达式 是否也可以使用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"

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

1 个答案:

答案 0 :(得分: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
.....

更新,因为问题作者使他的问题背景更加清晰我可能会建议在给定TP时采用更具体的建议。由于SO答案应该相当简洁,因此我们将两个遗留Person类型OldPersonT1类型OldPersonT2ModernPerson作为数据源并将一个当代localdb类型作为目标进行限制。我在这里谈论类型,它可以在您的数据库场周围有多少实例

现在,让我们在名为CompileTypeDB的{​​{1}}创建一个数据库并运行Sql脚本,以创建与OldPersonT1OldPersonT2和{相对应的表格{1}}(这是一次性练习,不涉及真正的数据移动)。这将是ModernPerson TP的单一信息来源。

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

SqlDataConnection

然后,使用以下静态成员扩充每个遗留类型(仅为简洁起见,下面针对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 给出):

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) } 类型的任何数据源抓住IQueryable<ModernPerson>
OldPersonT1

为了使这个工作实时DB可能与编译时DB不同,它应该包含OldPersonT1.MakeModernPersons("real time connection string to any DB having OldPersonT1 table") 具有和依赖的所有内容。 同样适用于OldPersonT1或任何其他变体类型:通过为每种变体类型实施OldPersonT2,您可以获得所有数据源实例。

使用数据目标需要一个带签名的单个函数

MakeModernPersons

现在仅通过操纵两个实时连接字符串的值来涵盖let makeModernPersons destinationConnStr (source: IQueryable<ModernPerson>) = ... 数据源和目标的所有可能组合。

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