CRUD sample
open System
let ctx = sql.GetDataContext()
let orders = ctx.Main.Orders
let employees = ctx.Main.Employees
let customer = ctx.Main.Customers |> Seq.head
let employee = ctx.Main.Employees |> Seq.head
let now = DateTime.Now
Create() has various overloads to make inserting records simple.
Create a new row
let row = orders.Create()
row.CustomerId <- customer.CustomerId
row.EmployeeId <- employee.EmployeeId
row.Freight <- 10M
row.OrderDate <- now.AddDays(-1.0)
row.RequiredDate <- now.AddDays(1.0)
row.ShipAddress <- "10 Downing St"
row.ShipCity <- "London"
row.ShipName <- "Dragons den"
row.ShipPostalCode <- "SW1A 2AA"
row.ShipRegion <- "UK"
row.ShippedDate <- now
Submit updates to the database
ctx.SubmitUpdates()
After updating, your item (row) will have the Id property.
You can also create with the longer ``Create(...)``(parameters)-method like this:
let emp = ctx.Main.Employees.``Create(FirstName, LastName)``("Don", "Syme")
Delete the row
row.Delete()
Submit updates to the database
ctx.SubmitUpdates()
Insert a list of records:
type Employee = {
FirstName:string
LastName:string
}
let mvps1 = [
{FirstName="Andrew"; LastName="Kennedy"};
{FirstName="Mads"; LastName="Torgersen"};
{FirstName="Martin";LastName="Odersky"};
]
mvps1
|> List.map (fun x ->
let row = employees.Create()
row.FirstName <- x.FirstName
row.LastName <- x.LastName)
ctx.SubmitUpdates()
Or directly specify the fields:
let mvps2 = [
{FirstName="Byron"; LastName="Cook"};
{FirstName="James"; LastName="Huddleston"};
{FirstName="Xavier";LastName="Leroy"};
]
mvps2
|> List.map (fun x ->
employees.Create(x.FirstName, x.LastName)
)
ctx.SubmitUpdates()
update a single row assuming Id is unique
type Employee2 = {
Id:int
FirstName:string
LastName:string
}
let updateEmployee (employee: Employee2) =
let foundEmployeeMaybe = query {
for p in ctx.Public.Employee2 do
where (p.Id = employee.Id)
select (Some p)
exactlyOneOrDefault
}
match foundEmployeeMaybe with
| Some foundEmployee ->
foundEmployee.FirstName <- employee.FirstName
foundEmployee.LastName <- employee.LastName
ctx.SubmitUpdates()
| None -> ()
let updateEmployee' (employee: Employee2) =
query {
for p in ctx.Public.Employee2 do
where (p.Id = employee.Id)
}
|> Seq.iter( fun e ->
e.FirstName <- employee.FirstName
e.LastName <- employee.LastName
)
ctx.SubmitUpdates()
let john = {
Id = 1
FirstName = "John"
LastName = "Doe" }
updateEmployee john
updateEmployee' john
Finally it is also possible to specify a seq of string * obj
, which is precisely the
output of .ColumnValues:
employees
|> Seq.map (fun x ->
employee.Create(x.ColumnValues)) // create twins
|> Seq.toList
let twins = ctx.GetUpdates() // Retrieve the FSharp.Data.Sql.Common.SqlEntity objects
ctx.ClearUpdates() // delete the updates
ctx.GetUpdates() // Get the updates
ctx.SubmitUpdates() // no record is added
Inside SubmitUpdate the transaction is created by default TransactionOption, which is Required: Shares a transaction, if one exists, and creates a new transaction if necessary. So, if you have query-operation before SubmitUpdates, you should create your own transaction to wrap these into the same transaction.
SQLProvider also supports async database operations:
ctx.SubmitUpdatesAsync() // |> Async.AwaitTask
OnConflict
The SQLite, PostgreSQL 9.5+ and MySQL 8.0+ providers support conflict resolution for INSERT statements.
They allow the user to specify if a unique constraint violation should be solved by ignoring the statement (DO NOTHING) or updating existing rows (DO UPDATE).
You can leverage this feature by setting the OnConflict
property on a row object:
* Setting it to DoNothing
will add the DO NOTHING clause (PostgreSQL) or the OR IGNORE clause (SQLite).
* Setting it to Update
will add a DO UPDATE clause on the primary key constraint for all columns (PostgreSQL) or a OR REPLACE clause (SQLite).
Sql Server has a similar feature in the form of the MERGE statement. This is not yet supported.
let ctx = sql.GetDataContext()
let emp = ctx.Main.Employees.Create()
emp.Id <- 1
emp.FirstName <- "Jane"
emp.LastName <- "Doe"
emp.OnConflict <- FSharp.Data.Sql.Common.OnConflict.Update
ctx.SubmitUpdates()
Delete-query for multiple items
To delete many items from a database table, DELETE FROM [dbo].[EMPLOYEES] WHERE (...)
, there is a way, although we don't recommend deleting items from a database. Instead, you should consider a deletion-flag column. You should also back up your database before trying this. Note that changes are immediately saved to the database even if you don't call ctx.SubmitUpdates()
.
query {
for c in ctx.Dbo.Employees do
where (...)
} |> Seq.``delete all items from single table`` |> Async.RunSynchronously
Selecting which Create() to use
There are 3 overrides of create.
The ideal one to use is the long one ``Create(...)``(...):
let emp = ctx.Main.Employees.``Create(FirstName, LastName)``("Don", "Syme")
This is because it will fail if your database structure changes. So, when your table gets new columns, the code will fail at compile time. Then you decide what to do with the new columns, and not let a bug to customers.
But you may want to use the plain .Create() if your setup is not optimal. Try to avoid these conditions:
- If your editor intellisense is not working for backtick-variables.
- You have lot of nullable columns in your database.
- You want to use F# like a dynamic language.
In the last case you'll be maintaining code like this:
let employeeId = 123
// Got some untyped array of data from the client
let createSomeItem (data: seq<string*obj>) =
data
|> Seq.map( // Some parsing and validation:
function
// Skip some fields
| "EmployeeId", x
| "PermissionLevel", x -> "", x
// Convert and validate some fields
| "PostalCode", x ->
"PostalCode", x.ToString().ToUpper().Replace(" ", "") |> box
| "BirthDate", x ->
let bdate = x.ToString() |> DateTime.Parse
if bdate.AddYears(18) > DateTime.UtcNow then
failwith "Too young!"
else
"BirthDate", bdate.ToString("yyyy-MM-dd") |> box
| others -> others)
|> Seq.filter (fun (key,_) -> key <> "")
// Add some fields:
|> Seq.append [|"EmployeeId", employeeId |> box;
"Country", "UK" |> box |]
|> ctx.Main.Employees.Create
What to do if your creation fails systematically every time
Some underlying database connection libraries have problems with serializing underlying data types. So, if this fails:
emp.BirthDate <- DateTime.UtcNow
ctx.SubmitUpdates()
Try using .SetColumn("ColumnName", value |> box)
for example:
emp.SetColumn("BirthDate", DateTime.UtcNow.ToString("yyyy-MM-dd HH\:mm\:ss") |> box)
ctx.SubmitUpdates()
SetColumn takes an object, giving you more control over the type serialization.
type LiteralAttribute = inherit Attribute new: unit -> LiteralAttribute
--------------------
new: unit -> LiteralAttribute
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp.Data
--------------------
namespace Microsoft.FSharp.Data
<summary>Typed representation of a database</summary> <param name='ConnectionString'>The connection string for the SQL database</param> <param name='ConnectionStringName'>The connection string name to select from a configuration file</param> <param name='DatabaseVendor'> The target database vendor</param> <param name='IndividualsAmount'>The amount of sample entities to project into the type system for each SQL entity type. Default 50. Note GDPR/PII regulations if using individuals with ContextSchemaPath.</param> <param name='UseOptionTypes'>If set, F# option types will be used in place of nullable database columns. If not, you will always receive the default value of the column's type even if it is null in the database.</param> <param name='ResolutionPath'>The location to look for dynamically loaded assemblies containing database vendor specific connections and custom types. Types used in desing-time: If no better clue, prefer .NET Standard 2.0 versions. Semicolon to separate multiple.</param> <param name='Owner'>Oracle: The owner of the schema for this provider to resolve. PostgreSQL: A list of schemas to resolve, separated by spaces, newlines, commas, or semicolons.</param> <param name='CaseSensitivityChange'>Should we do ToUpper or ToLower when generating table names?</param> <param name='TableNames'>Comma separated table names list to limit a number of tables in big instances. The names can have '%' sign to handle it as in the 'LIKE' query (Oracle and MSSQL Only)</param> <param name='ContextSchemaPath'>The location of the context schema previously saved with SaveContextSchema. When not empty, will be used to populate the database schema instead of retrieving it from then database.</param> <param name='OdbcQuote'>Odbc quote characters: Quote characters for the table and column names: `alias`, [alias]</param> <param name='SQLiteLibrary'>Use System.Data.SQLite or Mono.Data.SQLite or select automatically (SQLite only)</param> <param name='SsdtPath'>A path to an SSDT .dacpac file.'</param>
<summary>Returns an instance of the SQL Provider using the static parameters</summary>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(selectOperations: SelectOperations) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='selectOperations'>Execute select-clause operations in SQL database rather than .NET-side.</param>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(commandTimeout: int) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='commandTimeout'>SQL command timeout. Maximum time for single SQL-command in seconds.</param>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(transactionOptions: Transactions.TransactionOptions) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='transactionOptions'>TransactionOptions for the transaction created on SubmitChanges.</param>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(connectionString: string) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='connectionString'>The database runtime connection string</param>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(connectionString: string, selectOperations: SelectOperations) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='connectionString'>The database runtime connection string</param><param name='selectOperations'>Execute select-clause operations in SQL database rather than .NET-side.</param>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(transactionOptions: Transactions.TransactionOptions, commandTimeout: int) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='transactionOptions'>TransactionOptions for the transaction created on SubmitChanges.</param><param name='commandTimeout'>SQL command timeout. Maximum time for single SQL-command in seconds.</param>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(connectionString: string, commandTimeout: int) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='connectionString'>The database runtime connection string</param><param name='commandTimeout'>SQL command timeout. Maximum time for single SQL-command in seconds.</param>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(connectionString: string, transactionOptions: Transactions.TransactionOptions) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='connectionString'>The database runtime connection string</param><param name='transactionOptions'>TransactionOptions for the transaction created on SubmitChanges.</param>
(+0 other overloads)
SqlDataProvider<...>.GetDataContext(connectionString: string, resolutionPath: string) : SqlDataProvider<...>.dataContext
<summary>Returns an instance of the SQL Provider using the static parameters</summary><param name='connectionString'>The database runtime connection string</param><param name='resolutionPath'>The location to look for dynamically loaded assemblies containing database vendor specific connections and custom types. Types used in desing-time: If no better clue, prefer .NET Standard 2.0 versions. Semicolon to separate multiple.</param>
(+0 other overloads)
<summary> The table Orders belonging to schema main</summary>
<summary> The table Employees belonging to schema main</summary>
<summary> The table Customers belonging to schema main</summary>
module Seq from FSharp.Data.Sql
--------------------
module Seq from Microsoft.FSharp.Collections
[<Struct>] type DateTime = new: date: DateOnly * time: TimeOnly -> unit + 16 overloads member Add: value: TimeSpan -> DateTime member AddDays: value: float -> DateTime member AddHours: value: float -> DateTime member AddMicroseconds: value: float -> DateTime member AddMilliseconds: value: float -> DateTime member AddMinutes: value: float -> DateTime member AddMonths: months: int -> DateTime member AddSeconds: value: float -> DateTime member AddTicks: value: int64 -> DateTime ...
<summary>Represents an instant in time, typically expressed as a date and time of day.</summary>
--------------------
DateTime ()
(+0 other overloads)
DateTime(ticks: int64) : DateTime
(+0 other overloads)
DateTime(date: DateOnly, time: TimeOnly) : DateTime
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : DateTime
(+0 other overloads)
DateTime(date: DateOnly, time: TimeOnly, kind: DateTimeKind) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : DateTime
(+0 other overloads)
<summary>Gets a <see cref="T:System.DateTime" /> object that is set to the current date and time on this computer, expressed as the local time.</summary>
<returns>An object whose value is the current local date and time.</returns>
SqlDataProvider<...>.dataContext.mainSchema.main.Orders.Create(data: Collections.Generic.IEnumerable<string * obj>) : SqlDataProvider<...>.dataContext.main.OrdersEntity
<summary>Item array of database columns: </summary>
<summary>CustomerID: nchar(5)</summary>
<summary>CustomerID: nchar(5)</summary>
<summary>EmployeeID: integer</summary>
<summary>EmployeeID: integer</summary>
<summary>Freight: money</summary>
<summary>OrderDate: datetime</summary>
<summary>RequiredDate: datetime</summary>
<summary>ShipAddress: nvarchar(60)</summary>
<summary>ShipCity: nvarchar(15)</summary>
<summary>ShipName: nvarchar(40)</summary>
<summary>ShipPostalCode: nvarchar(10)</summary>
<summary>ShipRegion: nvarchar(15)</summary>
<summary>ShippedDate: datetime</summary>
<summary>Save changes to data-source. May throws errors: To deal with non-saved items use GetUpdates() and ClearUpdates().</summary>
val string: value: 'T -> string
--------------------
type string = String
module List from FSharp.Data.Sql
--------------------
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
SqlDataProvider<...>.dataContext.mainSchema.main.Employees.Create(data: Collections.Generic.IEnumerable<string * obj>) : SqlDataProvider<...>.dataContext.main.EmployeesEntity
<summary>Item array of database columns: FirstName,LastName</summary>
SqlDataProvider<...>.dataContext.mainSchema.main.Employees.Create(FirstName: string, LastName: string) : SqlDataProvider<...>.dataContext.main.EmployeesEntity
<summary>FirstName: nvarchar(10)</summary>
<summary>LastName: nvarchar(20)</summary>
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
<summary>Save changes to data-source. May throws errors: Use Async.Catch and to deal with non-saved items use GetUpdates() and ClearUpdates().</summary>
<summary> Determines what should happen when saving this entity if it is newly-created but another entity with the same primary key already exists </summary>
<summary> If the primary key already exists, updates the existing row's columns to match the new entity. Currently supported only on PostgreSQL 9.5+ </summary>
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...
--------------------
type Async<'T>
val seq: sequence: 'T seq -> 'T seq
--------------------
type 'T seq = Collections.Generic.IEnumerable<'T>
DateTime.Parse(s: string, provider: IFormatProvider) : DateTime
DateTime.Parse(s: ReadOnlySpan<char>, provider: IFormatProvider) : DateTime
DateTime.Parse(s: string, provider: IFormatProvider, styles: Globalization.DateTimeStyles) : DateTime
DateTime.Parse(s: ReadOnlySpan<char>, ?provider: IFormatProvider, ?styles: Globalization.DateTimeStyles) : DateTime
<summary>Gets a <see cref="T:System.DateTime" /> object that is set to the current date and time on this computer, expressed as the Coordinated Universal Time (UTC).</summary>
<returns>An object whose value is the current UTC date and time.</returns>
DateTime.ToString(format: string) : string
DateTime.ToString(provider: IFormatProvider) : string
DateTime.ToString(format: string, provider: IFormatProvider) : string
<summary>BirthDate: datetime</summary>