Vp.FSharp.SqlОсновная библиотека, которая позволяет вам постоянно работать с F# и любым поставщиком ADO.
В большинстве случаев эта библиотека используется только для создания других библиотек F#, использующих соответствующих поставщиков ADO.
Если вы просто хотите выполнить команды SQL A-LA-F#, вы можете посмотреть на этот раздел.
Мы следуем «весьма спорным практикам» в меру своих возможностей!
| Статус | Упаковка |
|---|---|
| ХОРОШО | |
| ОК (вроде) | |
| TBD | |
| TBD |
| Имя | Версия | Командование |
|---|---|---|
Vp.FSharp.Sql | Install-Package Vp.FSharp.Sql |
Эта библиотека в основном направлена на то, чтобы стать основой для создания других библиотек с соответствующими поставщиками ADO.NET, чтобы обеспечить капризный опыт.
Вы можете проверить библиотеки ниже, каждый из которых использует Vp.FSharp.Sql и соответствующий поставщик Ado.net:
| Имя | Ado.net Provider | Версия | Командование |
|---|---|---|---|
Vp.FSharp.Sql.Sqlite | System.Data.SQLite.Core | Install-Package Vp.FSharp.Sql.Sqlite | |
Vp.FSharp.Sql.SqlServer | Microsoft.Data.SqlClient | Install-Package Vp.FSharp.Sql.SqlServer | |
Vp.FSharp.Sql.PostgreSql | Npgsql | Install-Package Vp.FSharp.Sql.PostgreSql |
Короче говоря, вы можете создать своего собственного полного поставщика, но вы можете просто использовать только то, что вам нужно.
Давайте пройдемся через реализацию поставщика Vp.FSharp.Sql.Sqlite .
Сначала вам нужен самый важный тип всех, тип значения базы данных.
В случае SQLite SqliteDbValue может смоделировать как простой дискриминационный союз (DU):
/// Native SQLite DB types.
/// See https://www.sqlite.org/datatype3.html
type SqliteDbValue =
| Null
| Integer of int64
| Real of double
| Text of string
| Blob of byte arrayИсточник фрагмента | якорь
Эти случаи создаются после официальной документации SQLite.
Именно здесь мы конвертируем DU, открытый в общественном API в фактический класс DbParameter , который можно использовать из основных библиотечных функций.
В большинстве сценариев реализация состоит из сопоставления шаблонов в различных случаях типа базы данных и создания соответствующих специфических типов DbParameter , доступных в поставщике ADO.NET, если таковые имеются:
let dbValueToParameter name value =
let parameter = SQLiteParameter ()
parameter.ParameterName <- name
match value with
| Null ->
parameter.TypeName <- ( nameof Null ) .ToUpperInvariant ()
| Integer value ->
parameter.TypeName <- ( nameof Integer ) .ToUpperInvariant ()
parameter.Value <- value
| Real value ->
parameter.TypeName <- ( nameof Real ) .ToUpperInvariant ()
parameter.Value <- value
| Text value ->
parameter.TypeName <- ( nameof Text ) .ToUpperInvariant ()
parameter.Value <- value
| Blob value ->
parameter.TypeName <- ( nameof Blob ) .ToUpperInvariant ()
parameter.Value <- value
parameterПримечание: эта функция не должна быть публичной, только DU должен быть публичным.
SqlDependencies действует как клей, который придерживается всех наиболее важных базовых операций ADO:
/// SQLite Dependencies
type SqliteDependencies =
SqlDependencies <
SQLiteConnection ,
SQLiteCommand ,
SQLiteParameter ,
SQLiteDataReader ,
SQLiteTransaction ,
SqliteDbValue >Экземпляр этого типа может реализовать с помощью:
let beginTransactionAsync ( connection : SQLiteConnection ) ( isolationLevel : IsolationLevel ) _ =
ValueTask.FromResult ( connection.BeginTransaction ( isolationLevel ))
let executeReaderAsync ( command : SQLiteCommand ) _ =
Task.FromResult ( command.ExecuteReader ())
{ CreateCommand = fun connection -> connection.CreateCommand ()
SetCommandTransaction = fun command transaction -> command.Transaction <- transaction
BeginTransaction = fun connection -> connection.BeginTransaction
BeginTransactionAsync = beginTransactionAsync
ExecuteReader = fun command -> command.ExecuteReader ()
ExecuteReaderAsync = executeReaderAsync
DbValueToParameter = Constants.DbValueToParameter } В этом конкретном случае, System.Data.SQLite , наиболее специфические типы доступны только через неасинхронное API.
Например, мы используем command.ExecuteReader вместо command.ExecuteDbDataReader , потому что возвратный тип является наиболее конкретным:
SQLiteCommand.ExecuteDbDataReader()SQLiteCommand.ExecuteReader()Кроме того, как вы могли заметить, что нет происшествия асинхронного API, что означает, что асинхронная «реализация» (или отсутствие их) зависит от реализации базового класса:
DbCommand.ExecuteReaderAsync()DbCommand.ExecuteDbDataReader(CommandBehavior behavior)Это просто асинхронная обертка вокруг синхронной версии.
Точно так же, когда дело доходит до connection.BeginTransaction вместо command.BeginTransactionAsync :
SQLiteConnection.BeginTransaction()DbConnection.BeginTransactionAsync()В одном только этом примере показывается такие расхождения, которые вы можете ожидать в наиболее доступных реализациях поставщика ADO.NET.
Для простоты вы можете ограничить тип CommandDefinition соответствующими типами поставщиков ADO, как какое -то связующее тип:
/// SQLite Command Definition
type SqliteCommandDefinition =
CommandDefinition <
SQLiteConnection ,
SQLiteCommand ,
SQLiteParameter ,
SQLiteDataReader ,
SQLiteTransaction ,
SqliteDbValue > Это может быть позже использовано с функциями SqlCommand , которые принимают CommandDefinition в качестве одного из их параметров.
Есть еще одна специализация с точки зрения общих ограничений:
/// SQLite Configuration
type SqliteConfiguration =
SqlConfigurationCache <
SQLiteConnection ,
SQLiteCommand >Этот тип является еще одним связующим для типов и действует как кэш; Он будет передаваться вместе с определением команды при выполнении команды.
Это довольно просто, все, что вам нужно сделать, это:
SqlCommand . [<RequireQualifiedAccess>]
module Vp.FSharp.Sql.Sqlite.SqliteCommand
open Vp. FSharp . Sql
/// Initialize a new command definition with the given text contained in the given string.
let text value : SqliteCommandDefinition =
SqlCommand.text value
/// Initialize a new command definition with the given text spanning over several strings (ie. list).
let textFromList value : SqliteCommandDefinition =
SqlCommand.textFromList value
/// Update the command definition so that when executing the command, it doesn't use any logger.
/// Be it the default one (Global, if any.) or a previously overriden one.
let noLogger commandDefinition = { commandDefinition with Logger = LoggerKind.Nothing }
/// Update the command definition so that when executing the command, it use the given overriding logger.
/// instead of the default one, aka the Global logger, if any.
let overrideLogger value commandDefinition = { commandDefinition with Logger = LoggerKind.Override value }
/// Update the command definition with the given parameters.
let parameters value ( commandDefinition : SqliteCommandDefinition ) : SqliteCommandDefinition =
SqlCommand.parameters value commandDefinition
/// Update the command definition with the given cancellation token.
let cancellationToken value ( commandDefinition : SqliteCommandDefinition ) : SqliteCommandDefinition =
SqlCommand.cancellationToken value commandDefinition
/// Update the command definition with the given timeout.
/// Note: kludged because SQLite doesn't support per-command timeout values.
let timeout value ( commandDefinition : SqliteCommandDefinition ) : SqliteCommandDefinition =
SqlCommand.timeout value commandDefinition
/// Update the command definition and sets whether the command should be prepared or not.
let prepare value ( commandDefinition : SqliteCommandDefinition ) : SqliteCommandDefinition =
SqlCommand.prepare value commandDefinition
/// Update the command definition and sets whether the command should be wrapped in the given transaction.
let transaction value ( commandDefinition : SqliteCommandDefinition ) : SqliteCommandDefinition =
SqlCommand.transaction value commandDefinitionАналогичным образом, выполнение команды следует тем же принципам, так как передавая соответствующие параметры с тесными типами (соответствующие вашему текущему и конкретному поставщику ADO.NET) к основным функциям SQLCommand.
/// Execute the command and return the sets of rows as an AsyncSeq accordingly to the command definition.
/// This function runs asynchronously.
let queryAsyncSeq connection read ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.queryAsyncSeq
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read commandDefinition
/// Execute the command and return the sets of rows as an AsyncSeq accordingly to the command definition.
/// This function runs synchronously.
let querySeqSync connection read ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.querySeqSync
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read commandDefinition
/// Execute the command and return the sets of rows as a list accordingly to the command definition.
/// This function runs asynchronously.
let queryList connection read ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.queryList
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read commandDefinition
/// Execute the command and return the sets of rows as a list accordingly to the command definition.
/// This function runs synchronously.
let queryListSync connection read ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.queryListSync
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read commandDefinition
/// Execute the command and return the first set of rows as a list accordingly to the command definition.
/// This function runs asynchronously.
let querySetList connection read ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.querySetList
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read commandDefinition
/// Execute the command and return the first set of rows as a list accordingly to the command definition.
/// This function runs synchronously.
let querySetListSync connection read ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.querySetListSync
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read commandDefinition
/// Execute the command and return the 2 first sets of rows as a tuple of 2 lists accordingly to the command definition.
/// This function runs asynchronously.
let querySetList2 connection read1 read2 ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.querySetList2
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read1 read2 commandDefinition
/// Execute the command and return the 2 first sets of rows as a tuple of 2 lists accordingly to the command definition.
/// This function runs synchronously.
let querySetList2Sync connection read1 read2 ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.querySetList2Sync
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read1 read2 commandDefinition
/// Execute the command and return the 3 first sets of rows as a tuple of 3 lists accordingly to the command definition.
/// This function runs asynchronously.
let querySetList3 connection read1 read2 read3 ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.querySetList3
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read1 read2 read3 commandDefinition
/// Execute the command and return the 3 first sets of rows as a tuple of 3 lists accordingly to the command definition.
/// This function runs synchronously.
let querySetList3Sync connection read1 read2 read3 ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.querySetList3Sync
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) read1 read2 read3 commandDefinition
/// Execute the command accordingly to its definition and,
/// - return the first cell value, if it is available and of the given type.
/// - throw an exception, otherwise.
/// This function runs asynchronously.
let executeScalar < 'Scalar > connection ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.executeScalar < 'Scalar , _, _, _, _, _, _, _, _>
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) commandDefinition
/// Execute the command accordingly to its definition and,
/// - return the first cell value, if it is available and of the given type.
/// - throw an exception, otherwise.
/// This function runs synchronously.
let executeScalarSync < 'Scalar > connection ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.executeScalarSync < 'Scalar , _, _, _, _, _, _, _, _>
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) commandDefinition
/// Execute the command accordingly to its definition and,
/// - return Some, if the first cell is available and of the given type.
/// - return None, if first cell is DBNull.
/// - throw an exception, otherwise.
/// This function runs asynchronously.
let executeScalarOrNone < 'Scalar > connection ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.executeScalarOrNone < 'Scalar , _, _, _, _, _, _, _, _>
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) commandDefinition
/// Execute the command accordingly to its definition and,
/// - return Some, if the first cell is available and of the given type.
/// - return None, if first cell is DBNull.
/// - throw an exception, otherwise.
/// This function runs synchronously.
let executeScalarOrNoneSync < 'Scalar > connection ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.executeScalarOrNoneSync < 'Scalar , _, _, _, _, _, _, _, _>
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) commandDefinition
/// Execute the command accordingly to its definition and, return the number of rows affected.
/// This function runs asynchronously.
let executeNonQuery connection ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.executeNonQuery
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) commandDefinition
/// Execute the command accordingly to its definition and, return the number of rows affected.
/// This function runs synchronously.
let executeNonQuerySync connection ( commandDefinition : SqliteCommandDefinition ) =
SqlCommand.executeNonQuerySync
connection ( Constants.Deps ) ( SqliteConfiguration.Snapshot ) commandDefinitionМы можем создать еще один модуль для нулевых помощников, а остальное касается передачи соответствующих параметров базовым основным функциям.
[<RequireQualifiedAccess>]
module Vp.FSharp.Sql.Sqlite.SqliteNullDbValue
open Vp. FSharp . Sql
/// Return SQLite DB Null value if the given option is None, otherwise the underlying wrapped in Some.
let ifNone toDbValue = NullDbValue.ifNone toDbValue SqliteDbValue.Null
/// Return SQLite DB Null value if the option is Error, otherwise the underlying wrapped in Ok.
let ifError toDbValue = NullDbValue.ifError toDbValue ( fun _ -> SqliteDbValue.Null ) Больше того же здесь тоже.
[<RequireQualifiedAccess>]
module Vp.FSharp.Sql.Sqlite.SqliteTransaction
open Vp. FSharp . Sql
open Vp. FSharp . Sql . Sqlite
let private beginTransactionAsync = Constants.Deps.BeginTransactionAsync
let private beginTransaction = Constants.Deps.BeginTransaction
/// Create and commit an automatically generated transaction with the given connection, isolation,
/// cancellation token and transaction body.
/// This function runs asynchronously.
let commit cancellationToken isolationLevel connection body =
Transaction.commit cancellationToken isolationLevel connection beginTransactionAsync body
/// Create and commit an automatically generated transaction with the given connection, isolation,
/// and transaction body.
/// This function runs synchronously.
let commitSync isolationLevel connection body =
Transaction.commitSync isolationLevel connection beginTransaction body
/// Create and do not commit an automatically generated transaction with the given connection, isolation,
/// cancellation token and transaction body.
/// This function runs asynchronously.
let notCommit cancellationToken isolationLevel connection body =
Transaction.notCommit cancellationToken isolationLevel connection beginTransactionAsync body
/// Create and do not commit an automatically generated transaction with the given connection, isolation,
/// and transaction body.
/// This function runs synchronously.
let notCommitSync isolationLevel connection body =
Transaction.notCommitSync isolationLevel connection beginTransaction body
/// Create and commit an automatically generated transaction with the given connection, isolation,
/// cancellation token and transaction body.
/// The commit phase only occurs if the transaction body returns Some.
/// This function runs asynchronously.
let commitOnSome cancellationToken isolationLevel connection body =
Transaction.commitOnSome cancellationToken isolationLevel connection beginTransactionAsync body
/// Create and commit an automatically generated transaction with the given connection, isolation,
/// and transaction body.
/// The commit phase only occurs if the transaction body returns Some.
/// This function runs synchronously.
let commitOnSomeSync isolationLevel connection body =
Transaction.commitOnSomeSync isolationLevel connection beginTransaction body
/// Create and commit an automatically generated transaction with the given connection, isolation,
/// cancellation token and transaction body.
/// The commit phase only occurs if the transaction body returns Ok.
/// This function runs asynchronously.
let commitOnOk cancellationToken isolationLevel connection body =
Transaction.commitOnOk cancellationToken isolationLevel connection beginTransactionAsync body
/// Create and commit an automatically generated transaction with the given connection, isolation,
/// and transaction body.
/// The commit phase only occurs if the transaction body returns Ok.
/// This function runs synchronously.
let commitOnOkSync isolationLevel connection body =
Transaction.commitOnOkSync isolationLevel connection beginTransaction body
/// Create and commit an automatically generated transaction with the given connection and transaction body.
/// This function runs asynchronously.
let defaultCommit connection body = Transaction.defaultCommit connection beginTransactionAsync body
/// Create and commit an automatically generated transaction with the given connection and transaction body.
/// This function runs synchronously.
let defaultCommitSync connection body = Transaction.defaultCommitSync connection beginTransaction body
/// Create and do not commit an automatically generated transaction with the given connection and transaction body.
/// This function runs asynchronously.
let defaultNotCommit connection body = Transaction.defaultNotCommit connection beginTransactionAsync body
/// Create and do not commit an automatically generated transaction with the given connection and transaction body.
/// This function runs synchronously.
let defaultNotCommitSync connection body = Transaction.defaultNotCommitSync connection beginTransaction body
/// Create and commit an automatically generated transaction with the given connection and transaction body.
/// The commit phase only occurs if the transaction body returns Ok.
/// This function runs asynchronously.
let defaultCommitOnSome connection body = Transaction.defaultCommitOnSome connection beginTransactionAsync body
/// Create and commit an automatically generated transaction with the given connection and transaction body.
/// The commit phase only occurs if the transaction body returns Ok.
/// This function runs synchronously.
let defaultCommitOnSomeSync connection body = Transaction.defaultCommitOnSomeSync connection beginTransaction body
/// Create and commit an automatically generated transaction with the given connection and transaction body.
/// The commit phase only occurs if the transaction body returns Some.
/// This function runs asynchronously.
let defaultCommitOnOk connection body = Transaction.defaultCommitOnOk connection beginTransactionAsync body
/// Create and commit an automatically generated transaction with the given connection and transaction body.
/// The commit phase only occurs if the transaction body returns Some.
/// This function runs synchronously.
let defaultCommitOnOkSync connection body = Transaction.defaultCommitOnOkSync connection beginTransaction bodyИ вуаля! Теперь вы все урегулированы и готовы выполнить самые дикие команды против вашей любимой базы данных!
TransactionScope Помощники Эти помощники работают независимо от поставщика ADO.net, которого вы используете, если он поддерживает TransactionScope .
При этом мы решительно отговариваем вас от использования этих помощников :
TransactionScope (с этими помощниками или без них очень подвержено ошибкам, и вы можете столкнуться с неожиданным поведением без четких сообщений об ошибках.TransactionScope без поддержки распределенных транзакций, эти помощники могут перейти в отдельную библиотеку (т.е. репозиторий + пакет Nuget).Если вам нужен жизнеспособный обходной путь до 2 % или распределенных транзакций, вы можете проверить некоторые архитектурные шаблоны, такие как шаблон саги.
Отчеты об ошибках, запросы на функции и запросы на привлечение очень приветствуются!
Пожалуйста, прочитайте рекомендации по вкладу, чтобы начать.
Проект лицензирован под MIT.
Для получения дополнительной информации о лицензии см. Файл лицензии.