DustyTables
Thin F# API for SqlClient for easy data access to ms sql server with functional seasoning on top
Install / Use
/learn @Zaid-Ajaj/DustyTablesREADME
DustyTables 
Functional wrapper around plain old (dusty?) SqlClient to simplify data access when talking to MS Sql Server databases.
Install
# nuget client
dotnet add package DustyTables
Query a table
open DustyTables
// get the connection from the environment
let connectionString() = Env.getVar "app_db"
type User = { Id: int; Username: string }
let getUsers() : User list =
connectionString()
|> Sql.connect
|> Sql.query "SELECT * FROM dbo.[Users]"
|> Sql.execute (fun read ->
{
Id = read.int "user_id"
Username = read.string "username"
})
Handle null values from table columns:
open DustyTables
// get the connection from the environment
let connectionString() = Env.getVar "app_db"
type User = { Id: int; Username: string; LastModified : Option<DateTime> }
let getUsers() : User list =
connectionString()
|> Sql.connect
|> Sql.query "SELECT * FROM dbo.[users]"
|> Sql.execute(fun read ->
{
Id = read.int "user_id"
Username = read.string "username"
// Notice here using `orNone` reader variants
LastModified = read.dateTimeOrNone "last_modified"
})
Providing default values for null columns:
open DustyTables
// get the connection from the environment
let connectionString() = Env.getVar "app_db"
type User = { Id: int; Username: string; Biography : string }
let getUsers() : User list =
connectionString()
|> Sql.connect
|> Sql.query "select * from dbo.[users]"
|> Sql.execute (fun read ->
{
Id = read.int "user_id";
Username = read.string "username"
Biography = defaultArg (read.stringOrNone "bio") ""
})
Execute a parameterized query
open DustyTables
// get the connection from the environment
let connectionString() = Env.getVar "app_db"
// get product names by category
let productsByCategory (category: string) =
connectionString()
|> Sql.connect
|> Sql.query "SELECT name FROM dbo.[Products] where category = @category"
|> Sql.parameters [ "@category", Sql.string category ]
|> Sql.execute (fun read -> read.string "name")
Executing a stored procedure with parameters
open DustyTables
// get the connection from the environment
let connectionString() = Env.getVar "app_db"
// check whether a user exists or not
let userExists (username: string) : bool =
connectionString()
|> Sql.connect
|> Sql.storedProcedure "user_exists"
|> Sql.parameters [ "@username", Sql.string username ]
|> Sql.executeRow (fun read -> read.bool 0)
Executing a stored procedure with table-valued parameters
open DustyTables
open System.Data
// get the connection from the environment
let connectionString() = Env.getVar "app_db"
let executeMyStoredProcedure () : Async<int> =
// create a table-valued parameter
let customSqlTypeName = "MyCustomSqlTypeName"
let dataTable = new DataTable()
dataTable.Columns.Add "FirstName" |> ignore
dataTable.Columns.Add "LastName" |> ignore
// add rows to the table parameter
dataTable.Rows.Add("John", "Doe") |> ignore
dataTable.Rows.Add("Jane", "Doe") |> ignore
dataTable.Rows.Add("Fred", "Doe") |> ignore
connectionString()
|> Sql.connect
|> Sql.storedProcedure "my_stored_proc"
|> Sql.parameters
[ "@foo", Sql.int 1
"@people", Sql.table (customSqlTypeName, dataTable) ]
|> Sql.executeNonQueryAsync
Building and running tests locally
You only need a working local SQL server. The tests will create databases when required and dispose them at the end of each test.
cd ./DustyTables.Build
# Build the solution
dotent run
# Run the tests
dotent run -- test
# Publish the nuget
dotnet run -- publish
Related Skills
oracle
337.7kBest practices for using the oracle CLI (prompt + file bundling, engines, sessions, and file attachment patterns).
prose
337.7kOpenProse VM skill pack. Activate on any `prose` command, .prose files, or OpenProse mentions; orchestrates multi-agent workflows.
Command Development
83.3kThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
Plugin Structure
83.3kThis skill should be used when the user asks to "create a plugin", "scaffold a plugin", "understand plugin structure", "organize plugin components", "set up plugin.json", "use ${CLAUDE_PLUGIN_ROOT}", "add commands/agents/skills/hooks", "configure auto-discovery", or needs guidance on plugin directory layout, manifest configuration, component organization, file naming conventions, or Claude Code plugin architecture best practices.
