逐步解說:使用型別提供者存取 SQL 資料庫 (F#)
底下的說明解釋如何使用F# 3.0裡的SqlDataConnection (LINQ to SQL) 型別提供者來根據直接連接至資料庫的連接字串存取 SQL 資料庫的資料型別。如果你沒有連線到資料庫, 但是有LINQ to SQ結構描述檔案(DBML檔), 請見逐步解說:從 DBML 檔案產生 F# 型別 (F#)。
這個逐步解說將說明下列工作:這些工作必須以這個逐步解說的順序執行才會成功:
建立一個測試資料庫。
建立專案
設定型別提供者
查詢資料。
使用可為 null 的資料行搭配使用
呼叫預存程序
更新資料庫
執行 Transact-SQL 程式碼
使用完整的資料內容。
刪除資料
建立測試資料庫。
建立一個測試資料庫。
在執行SQL Server 的伺服器上建立測試用的資料庫.您可以使用本頁下方的區段的資料庫建立指令碼 建立測試資料庫。 來做。
準備測試資料庫。
- 要執行 建立測試資料庫。, 開啟 View ,接著選擇 SQL Server Object Explorer 或按 Ctrl+\, Ctrl+S 鍵.在 SQL Server Object Explorer 視窗中,打開shortcut menu for the appropriate instance,選擇 New Query,複製本頁下方的指令碼然後貼到編輯器上。若要執行 SQL 指令碼,請選擇使用三角形符號的工具列圖示或 Ctrl+Q的索引鍵。如需詳細資訊,請參閱連接的資料庫開發https://go.microsoft.com/fwlink/?LinkId=233646
建立專案
接著,您會建立 F# 應用程式專案。
若要建立並設定此專案
建立新的 MFC 應用程式專案。
將 .FSharp.Data.TypeProviders的參考,以及 System.Data和 System.Data.Linq。
將下列程式碼行開啟適當的命名空間加入至 F# 程式碼檔案 Program.fs 頂端。
open System open System.Data open System.Data.Linq open Microsoft.FSharp.Data.TypeProviders open Microsoft.FSharp.Linq
如同大多數的 F# 程式,您可以執行逐步解說中的程式碼也可以用互動方式執行它做為指令碼。如果您喜歡使用指令碼,打開shortcut menu for the project node,選擇Add New Item,加入一個F#的指令碼檔案,然後在指令碼中為每個步驟加入程式碼。您必須在檔案的頂端加入下列幾行來載入組件參考。
#r "System.Data.dll" #r "FSharp.Data.TypeProviders.dll" #r "System.Data.Linq.dll"
您可以選取程式碼的任一區塊,您在 F# Interactive 中將它加入並按 Alt+Enter 來執行它。
設定型別提供者
在這個步驟中,您會建立資料庫結構描述的一種提供者。
設定從直接資料庫連接的提供者型別。
當您想要用型別提供者來查詢SQL資料庫時,有兩行重要的程式碼是您會用到的首先,您會具現化型別的提供者。若要這樣做,請建立那些看起來像是型別 SqlDataConnection 的縮寫且具有靜態泛型參數。SqlDataConnection 是 SQL 提供者型別,而且不應該與 ADO.NET 程式撰寫 SqlConnection 型別混淆。如果您有想要連接的資料庫跟連接的字串,請使用下列程式碼會叫用這個型別的提供者。將連接字串替換成範例所給的。例如,如果您的伺服器是 MYSERVER且資料庫執行個體是INSTANCE,資料庫名稱是MyDatabase,而且想要使用 Windows Authentication來存取資料庫,那麼連接字串為下列範例程式碼。
type dbSchema = SqlDataConnection<"Data Source=MYSERVER\INSTANCE;Initial Catalog=MyDatabase;Integrated Security=SSPI;"> let db = dbSchema.GetDataContext() // Enable the logging of database activity to the console. db.DataContext.Log <- System.Console.Out
現在您有一個型別, dbSchema,是包含所有產生表示資料庫資料表的型別的父型別。您也會有一個物件, db,含有與其成員的所有資料庫內的資料表。資料表名稱會是屬性,,且這些屬性的型別是由 F# 編譯器所產生的。在 dbSchema.ServiceTypes下,型別會顯示為巢狀型別。因此,這些資料表資料列擷取的資料是該資料表產生適當型別的執行個體。這個型別的名稱為 ServiceTypes.Table1。
若要讓自己熟悉 F# 語言如何解譯查詢至 SQL 查詢,請檢視設定在資料內容的 Log 屬性的程式碼行。
進一步探索型別建立型別的提供者,請將下列程式碼。
let table1 = db.Table1
參考 table1 來檢視它的型別。它的型別會是 System.Data.Linq.Table<dbSchema.ServiceTypes.Table1> ,而該泛型引數表示每一個資料列的型別為產生的型別dbSchema.ServiceTypes.Table1。編譯器在資料庫中建立每個資料表的相同型別。
查詢資料。
在這個步驟中,您使用 F# 查詢運算式撰寫查詢。
查詢資料。
現在請建立一個查詢資料庫內的資料表。加入下列程式碼。
let query1 = query { for row in db.Table1 do select row } query1 |> Seq.iter (fun row -> printfn "%s %d" row.Name row.TestData1)
Word query 的存在指出這是查詢運算式,計算運算式的型別產生結果的集合類似一般的資料庫查詢。如果您將滑鼠停留在查詢中,您會看到該執行個體是 Linq.QueryBuilder 類別 (F#),定義查詢計算運算式的型別。如果您將滑鼠停留在 query1,您會看到它是 IQueryable<T>執行個體。身為name suggests, IQueryable<T> 表示可能會查詢的資料,而不是這個查詢結果。查詢會受到延遲評估限制,也就是說,資料庫查詢只能在當該查詢會被評估時。最後一行透過查詢 Seq.iter。查詢是可列舉的型別,而且可以重複與序列。如需詳細資訊,請參閱查詢運算式 (F#)。
現在加入一個查詢運算子來查詢。您可以使用查詢運算子來建立更複雜的查詢。這個範例也示範,您可以排除變數的查詢和使用管線運算子。
query { for row in db.Table1 do where (row.TestData1 > 2) select row } |> Seq.iter (fun row -> printfn "%d %s" row.TestData1 row.Name)
將兩個資料表聯結成更複雜的查詢。
query { for row1 in db.Table1 do join row2 in db.Table2 on (row1.Id = row2.Id) select (row1, row2) } |> Seq.iteri (fun index (row1, row2) -> if (index = 0) then printfn "Table1.Id TestData1 TestData2 Name Table2.Id TestData1 TestData2 Name" printfn "%d %d %f %s %d %d %f %s" row1.Id row1.TestData1 row1.TestData2 row1.Name row2.Id (row2.TestData1.GetValueOrDefault()) (row2.TestData2.GetValueOrDefault()) row2.Name)
在實際的程式碼中,查詢中的參數通常是一個值或變數,而不是編譯時期的常數。加入下列將查詢包進函式的程式碼,然後參數值設為10來呼叫函式。
let findData param = query { for row in db.Table1 do where (row.TestData1 = param) select row } findData 10 |> Seq.iter (fun row -> printfn "Found row: %d %d %f %s" row.Id row.TestData1 row.TestData2 row.Name)
使用可為 null 的資料行搭配使用
在資料庫中,資料行通常允許 NULL 值。在 .NET 型別系統,您不能為允許 Null 的資料是使用一般數值資料型別,因為這些型別不可以是 null 。因此,這些值是由 Nullable<T> 型別的執行個體表現。相較於直接用名子來存取這些欄位,您需要加入一些額外的步驟。您可以使用 Value 屬性存取可為 null 的型別之基礎值。Value 屬性會擲回例外狀況如果這個物件是空的。您可以使用 HasValue 布林方法判斷值是否存在,或者使用 GetValueOrDefault 確定您用有所有情況下的實際值。如果您使用 GetValueOrDefault ,且資料庫中有null值,則它將被一個值取代 (例如字串型別是空字串, 整數型別是0,浮點數型別是0.0)。
當您在查詢中需要執行相等測試或在 where 中比較可為null的值,您可以用 Linq.NullableOperators 模組 (F#)中的nullable operators來做。這些是如同泛型比較運算子 =, >, <=,依此類推,但是有一點例外,就是一個問號出現在可為 Null 之值運算子的左邊或右邊。例如,運算子 >? 是大於具有可為 Null 之值的運算子右邊的。這些運算子運作的方式是,如果運算式的任一邊是空的,則運算式會評估為 false。在 where 子句,這通常表示包含 null 資料行的資料列在查詢結果中沒有選取並且沒有傳回。
使用可為 null 的資料行搭配使用
下列程式碼顯示具有可為 Null 之值一起使用,假設, TestData1 是允許 null 的整數欄位。
query { for row in db.Table2 do where (row.TestData1.HasValue && row.TestData1.Value > 2) select row } |> Seq.iter (fun row -> printfn "%d %s" row.TestData1.Value row.Name) query { for row in db.Table2 do // Use a nullable operator ?> where (row.TestData1 ?> 2) select row } |> Seq.iter (fun row -> printfn "%d %s" (row.TestData1.GetValueOrDefault()) row.Name)
呼叫預存程序
在資料庫中的預存程序可從 F# 呼叫。您必須將這個靜態參數 StoredProcedures 給型別提供者具現化的 true 。這種提供者 SqlDataConnection 包含您可用來設定型別所產生的數個靜態方法。如需這些案例的完整描述,請參閱 SqlDataConnection 型別提供者 (F#)。在資料內容型別的一個方法會針對每個預存程序產生。
預存程序呼叫
如果預存程序會使用可為 Null 的參數,您必須傳入適當的 Nullable<T> 值。傳回純量資料表或預存程序方法的傳回值為 ISingleResult<T>,包含屬性可讓您存取所傳回的資料。ISingleResult<T> 之型別引數的方式取決於特定的程序也是型別提供者所產生的其中一個型別。對於名為Procedure1的程序,型別是 Procedure1Result。這個型別 Procedure1Result 在一個傳回的資料表中的資料行名稱,或者,可傳回純量值的預存程序,表示傳回值。
下列程式碼會假設,在採用兩個可為 Null 的整數做為參數的資料庫的程序 Procedure1 ,會傳回欄位名稱 TestData1的查詢,並傳回整數。
type schema = SqlDataConnection<"Data Source=MYSERVER\INSTANCE;Initial Catalog=MyDatabase;Integrated Security=SSPI;", StoredProcedures = true> let testdb = schema.GetDataContext() let nullable value = new System.Nullable<_>(value) let callProcedure1 a b = let results = testdb.Procedure1(nullable a, nullable b) for result in results do printfn "%d" (result.TestData1.GetValueOrDefault()) results.ReturnValue :?> int printfn "Return Value: %d" (callProcedure1 10 20)
更新資料庫
LINQ DataContext 型別包含在完整輸入的方式可讓您更容易管理交易式資料庫更新與產生之型別的方法。
若要更新資料庫
在下列程式碼中,許多資料列加入至資料庫。如果您只加入資料列,您可以使用 InsertOnSubmit 指定新資料列加入。如果您將多個資料列,您應該將其放在集合和呼叫 InsertAllOnSubmit<TSubEntity>。當您呼叫其中一種方法,資料庫不會立即變更。您必須呼叫 SubmitChanges 實際進行變更。根據預設,您呼叫之前所做的所有項目 SubmitChanges 隱含地視為相同交易的一部分。
let newRecord = new dbSchema.ServiceTypes.Table1(Id = 100, TestData1 = 35, TestData2 = 2.0, Name = "Testing123") let newValues = [ for i in [1 .. 10] -> new dbSchema.ServiceTypes.Table3(Id = 700 + i, Name = "Testing" + i.ToString(), Data = i) ] // Insert the new data into the database. db.Table1.InsertOnSubmit(newRecord) db.Table3.InsertAllOnSubmit(newValues) try db.DataContext.SubmitChanges() printfn "Successfully inserted new rows." with | exn -> printfn "Exception:\n%s" exn.Message
現在請呼叫刪除作業清除資料列。
// Now delete what was added. db.Table1.DeleteOnSubmit(newRecord) db.Table3.DeleteAllOnSubmit(newValues) try db.DataContext.SubmitChanges() printfn "Successfully deleted all pending rows." with | exn -> printfn "Exception:\n%s" exn.Message
執行 Transact-SQL 程式碼
您也可以指定 Transact-SQL 直接使用 ExecuteCommand 方法在 DataContext 類別。
執行自訂的 SQL 命令。
下列程式碼會顯示如何將 SQL 命令將資料列插入資料表以及從資料表刪除資料錄。
try db.DataContext.ExecuteCommand("INSERT INTO Table3 (Id, Name, Data) VALUES (102, 'Testing', 55)") |> ignore with | exn -> printfn "Exception:\n%s" exn.Message try //AND Name = 'Testing' AND Data = 55 db.DataContext.ExecuteCommand("DELETE FROM Table3 WHERE Id = 102 ") |> ignore with | exn -> printfn "Exception:\n%s" exn.Message
使用完整的資料內容。
在上述範例中, GetDataContext 方法用於取得所謂的資料庫結構描述的簡化 的 資料內容。簡化的資料內容較容易使用在您要建構查詢時,因為沒有多個的成員可以取得。因此,當您在 IntelliSense 瀏覽屬性時,您可以專注於資料庫結構 (例如,資料表和預存程序)。不過,在簡化資料內容上有個限制。一個完整的資料內容讓您有能力來做其他動作。如果您提供自己,供這也位於 ServiceTypes 且具有 DataContext 靜態參數的名稱。如果您未提供的話,SqlMetal.exe會根據其他輸入來產生一個資料內容型別的名稱給您。完整的資料內容是 DataContext 繼承並公開其基底類別的成員,包括 ADO.NET 資料型別的參考 (例如在 SQL 中可以使用撰寫查詢的 Connection 物件、方法 (例如 ExecuteCommand 和 ExecuteQuery ,因此方法明確處理交易。
使用完整的資料內容。
下列程式碼示範取得完整的資料內容物件並使用其執行命令直接對資料庫執行。在這個案例中,兩個要執行的命令視為相同交易的一部分。
let dbConnection = testdb.Connection let fullContext = new dbSchema.ServiceTypes.MyDatabase(dbConnection) dbConnection.Open() let transaction = dbConnection.BeginTransaction() fullContext.Transaction <- transaction try let result1 = fullContext.ExecuteCommand("INSERT INTO Table3 (Id, Name, Data) VALUES (102, 'A', 55)") printfn "ExecuteCommand Result: %d" result1 let result2 = fullContext.ExecuteCommand("INSERT INTO Table3 (Id, Name, Data) VALUES (103, 'B', -2)") printfn "ExecuteCommand Result: %d" result2 if (result1 <> 1 || result2 <> 1) then transaction.Rollback() printfn "Rolled back creation of two new rows." else transaction.Commit() printfn "Successfully committed two new rows." with | exn -> transaction.Rollback() printfn "Rolled back creation of two new rows due to exception:\n%s" exn.Message dbConnection.Close()
刪除資料
這個步驟將示範如何從資料表刪除的資料列。
從資料庫刪除資料列
現在,請藉由撰寫刪除指定的資料表資料列的函式來清除所有加入的資料列, Table<TEntity> 類別的執行個體。然後撰寫查詢來尋找您要刪除的資料列,並將查詢的結果輸入 deleteRows 函式。這個程式碼利用提供部分應用程式的函式引數。
let deleteRowsFrom (table:Table<_>) rows = table.DeleteAllOnSubmit(rows) query { for rows in db.Table3 do where (rows.Id > 10) select rows } |> deleteRowsFrom db.Table3 db.DataContext.SubmitChanges() printfn "Successfully deleted rows with Id greater than 10 in Table3."
建立測試資料庫。
本節說明如何設定測試資料庫來應用在這個逐步說明中。
請注意,如果您以某種方式修改資料庫,您必須重新設定這個型別的提供者。重設型別提供者,重建或清除包含這種提供者的專案。
要建立測試資料庫。
在 [伺服器總管] 中的資料連接上按一下滑鼠右鍵,然後在捷徑功能表中選擇[ 新增連接].[加入連接] 對話方塊隨即出現。
在 伺服器名稱 中,為您已經能夠存取的SQL伺服器指定一個名稱或者你還沒存取任何資料庫,將它指定為 (localdb\v11.0).SQL Express LocalDB在你的機器上提供一個輕量化的資料庫伺服器, 供你開發程式 使用。新連接已經建立並顯示在 [伺服器總管] 中的 [資料連接] 底下。如需LocalDB的詳細資訊,請參閱逐步解說:建立 LocalDB 資料庫。
開啟新連線節點的捷徑空能表, 並選擇[新查詢] 。
把底下的SQL腳本檔剪貼到查詢編輯器中, 餅選擇工具列的執行按鈕或是使用Ctrl+Shift+E鍵。
SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO USE [master]; GO IF EXISTS (SELECT * FROM sys.databases WHERE name = 'MyDatabase') DROP DATABASE MyDatabase; GO -- Create the MyDatabase database. CREATE DATABASE MyDatabase; GO -- Specify a simple recovery model -- to keep the log growth to a minimum. ALTER DATABASE MyDatabase SET RECOVERY SIMPLE; GO USE MyDatabase; GO -- Create the Table1 table. CREATE TABLE [dbo].[Table1] ( [Id] INT NOT NULL, [TestData1] INT NOT NULL, [TestData2] FLOAT (53) NOT NULL, [Name] NTEXT NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC) ); --Create Table2. CREATE TABLE [dbo].[Table2] ( [Id] INT NOT NULL, [TestData1] INT NULL, [TestData2] FLOAT (53) NULL, [Name] NTEXT NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC) ); -- Create Table3. CREATE TABLE [dbo].[Table3] ( [Id] INT NOT NULL, [Name] NVARCHAR (50) NOT NULL, [Data] INT NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC) ); GO CREATE PROCEDURE [dbo].[Procedure1] @param1 int = 0, @param2 int AS SELECT TestData1 FROM Table1 RETURN 0 GO -- Insert data into the Table1 table. USE MyDatabase INSERT INTO Table1 (Id, TestData1, TestData2, Name) VALUES(1, 10, 5.5, 'Testing1'); INSERT INTO Table1 (Id, TestData1, TestData2, Name) VALUES(2, 20, -1.2, 'Testing2'); --Insert data into the Table2 table. INSERT INTO Table2 (Id, TestData1, TestData2, Name) VALUES(1, 10, 5.5, 'Testing1'); INSERT INTO Table2 (Id, TestData1, TestData2, Name) VALUES(2, 20, -1.2, 'Testing2'); INSERT INTO Table2 (Id, TestData1, TestData2, Name) VALUES(3, NULL, NULL, 'Testing3'); INSERT INTO Table3 (Id, Name, Data) VALUES (1, 'Testing1', 10); INSERT INTO Table3 (Id, Name, Data) VALUES (2, 'Testing2', 100);