WIP on proof of concept w/docfx
This commit is contained in:
parent
004f91bc01
commit
9ea10cc6db
@ -19,7 +19,7 @@
|
||||
"content": [
|
||||
{
|
||||
"files": [
|
||||
"api/*.{md,yml}"
|
||||
"**/*.{md,yml}"
|
||||
],
|
||||
"exclude": [
|
||||
"_site/**"
|
||||
|
148
docs/basic-usage.md
Normal file
148
docs/basic-usage.md
Normal file
@ -0,0 +1,148 @@
|
||||
# Basic Usage
|
||||
|
||||
_<small>Documentation pages for `BitBadger.Npgsql.Documents` redirect here. This library replaced it as of v3; see project home if this applies to you.</small>_
|
||||
|
||||
## Overview
|
||||
|
||||
There are several categories of operations that can be accomplished against documents.
|
||||
|
||||
- **Count** returns the number of documents matching some criteria
|
||||
- **Exists** returns true if any documents match the given criteria
|
||||
- **Insert** adds a new document, failing if the ID field is not unique
|
||||
- **Save** adds a new document, updating an existing one if the ID is already present ("upsert")
|
||||
- **Update** updates an existing document, doing nothing if no documents satisfy the criteria
|
||||
- **Patch** updates a portion of an existing document, doing nothing if no documents satisfy the criteria
|
||||
- **Find** returns the documents matching some criteria as domain objects
|
||||
- **Json** returns or writes documents matching some criteria as JSON text
|
||||
- **RemoveFields** removes fields from documents matching some criteria
|
||||
- **Delete** removes documents matching some criteria
|
||||
|
||||
`Insert` and `Save` were the only two that don't mention criteria. For the others, "some criteria" can be defined a few different ways:
|
||||
- **All** references all documents in the table; applies to Count and Find
|
||||
- **ById** looks for a single document on which to operate; applies to all but Count
|
||||
- **ByFields** uses JSON field comparisons to select documents for further processing (PostgreSQL will use a numeric comparison if the field value is numeric, or a string comparison otherwise; SQLite will do its usual [best-guess on types][]{target=_blank rel=noopener}); applies to all but Update
|
||||
- **ByContains** (PostgreSQL only) uses a JSON containment query (the `@>` operator) to find documents where the given sub-document occurs (think of this as an `=` comparison based on one or more properties in the document; looking for hotels with `{ "Country": "USA", "Rating": 4 }` would find all hotels with a rating of 4 in the United States); applies to all but Update
|
||||
- **ByJsonPath** (PostgreSQL only) uses a JSON patch match query (the `@?` operator) to make specific queries against a document's structure (it also supports more operators than a containment query; to find all hotels rated 4 _or higher_ in the United States, we could query for `"$ ? (@.Country == \"USA\" && @.Rating > 4)"`); applies to all but Update
|
||||
|
||||
Finally, `Find` and `Json` also have `FirstBy*` implementations for all supported criteria types, and `Find*Ordered` implementations to sort the results in the database.
|
||||
|
||||
## Saving Documents
|
||||
|
||||
The library provides three different ways to save data. The first equates to a SQL `INSERT` statement, and adds a single document to the repository.
|
||||
|
||||
```csharp
|
||||
// C#, All
|
||||
var room = new Room(/* ... */);
|
||||
// Parameters are table name and document
|
||||
await Document.Insert("room", room);
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, All
|
||||
let room = { Room.empty with (* ... *) }
|
||||
do! insert "room" room
|
||||
```
|
||||
|
||||
The second is `Save`; and inserts the data it if does not exist and replaces the document if it does exist (what some call an "upsert"). It utilizes the `ON CONFLICT` syntax to ensure an atomic statement. Its parameters are the same as those for `Insert`.
|
||||
|
||||
The third equates to a SQL `UPDATE` statement. `Update` applies to a full document and is usually used by ID, while `Patch` is used for partial updates and may be done by field comparison, JSON containment, or JSON Path match. For a few examples, let's begin with a query that may back the "edit hotel" page. This page lets the user update nearly all the details for the hotel, so updating the entire document would be appropriate.
|
||||
|
||||
```csharp
|
||||
// C#, All
|
||||
var hotel = await Document.Find.ById<Hotel>("hotel", hotelId);
|
||||
if (!(hotel is null))
|
||||
{
|
||||
// update hotel properties from the posted form
|
||||
await Update.ById("hotel", hotel.Id, hotel);
|
||||
}
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, All
|
||||
match! Find.byId<Hotel> "hotel" hotelId with
|
||||
| Some hotel ->
|
||||
do! Update.byId "hotel" hotel.Id updated
|
||||
{ hotel with (* properties from posted form *) }
|
||||
| None -> ()
|
||||
```
|
||||
|
||||
For the next example, suppose we are upgrading our hotel, and need to take rooms 221-240 out of service*. We can utilize a patch via JSON Path** to accomplish this.
|
||||
|
||||
```csharp
|
||||
// C#, PostgreSQL
|
||||
await Patch.ByJsonPath("room",
|
||||
"$ ? (@.HotelId == \"abc\" && (@.RoomNumber >= 221 && @.RoomNumber <= 240)",
|
||||
new { InService = false });
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, PostgreSQL
|
||||
do! Patch.byJsonPath "room"
|
||||
"$ ? (@.HotelId == \"abc\" && (@.RoomNumber >= 221 && @.RoomNumber <= 240)"
|
||||
{| InService = false |};
|
||||
```
|
||||
|
||||
_* - we are ignoring the current reservations, end date, etc. This is very naïve example!_
|
||||
|
||||
\** - Both PostgreSQL and SQLite can also accomplish this using the `Between` comparison and a `ByFields` query:
|
||||
|
||||
```csharp
|
||||
// C#, Both
|
||||
await Patch.ByFields("room", FieldMatch.Any, [Field.Between("RoomNumber", 221, 240)],
|
||||
new { InService = false });
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, Both
|
||||
do! Patch.byFields "room" Any [ Field.Between "RoomNumber" 221 240 ] {| InService = false |}
|
||||
```
|
||||
|
||||
This could also be done with `All`/`FieldMatch.All` and `GreaterOrEqual` and `LessOrEqual` field comparisons, or even a custom query; these are fully explained in the [Advanced Usage][] section.
|
||||
|
||||
> There is an `Update.ByFunc` variant that takes an ID extraction function run against the document instead of its ID. This is detailed in the [Advanced Usage][] section.
|
||||
|
||||
## Finding Documents as Domain Items
|
||||
|
||||
Functions to find documents start with `Find.`. There are variants to find all documents in a table, find by ID, find by JSON field comparisons, find by JSON containment, or find by JSON Path. The hotel update example above utilizes an ID lookup; the descriptions of JSON containment and JSON Path show examples of the criteria used to retrieve using those techniques.
|
||||
|
||||
`Find` methods and functions are generic; specifying the return type is crucial. Additionally, `ById` will need the type of the key being passed. In C#, `ById` and the `FirstBy*` methods will return `TDoc?`, with the value if it was found or `null` if it was not; `All` and other `By*` methods return `List<TDoc>` (from `System.Collections.Generic`). In F#, `byId` and the `firstBy*` functions will return `'TDoc option`; `all` and other `by*` functions return `'TDoc list`.
|
||||
|
||||
`Find*Ordered` methods and function append an `ORDER BY` clause to the query that will sort the results in the database. These take, as their last parameter, a sequence of `Field` items; a `.Named` method allows for field creation for these names. Within these names, prefixing the name with `n:` will tell PostgreSQL to sort this field numerically rather than alphabetically; it has no effect in SQLite (it does its own [type coercion][best-guess on types]). Adding " DESC" at the end will sort high-to-low instead of low-to-high.
|
||||
|
||||
## Finding Documents as JSON
|
||||
|
||||
All `Find` methods and functions have two corresponding `Json` functions.
|
||||
|
||||
* The first set return the expected document(s) as a `string`, and will always return valid JSON. Single-document queries with nothing found will return `{}`, while zero-to-many queries will return `[]` if no documents match the given criteria.
|
||||
* The second set are prefixed with `Write`, and take a `StreamWriter` immediately after the table name parameter. These functions write results to the given stream instead of returning them, which can be useful for JSON API scenarios.
|
||||
|
||||
## Deleting Documents
|
||||
|
||||
Functions to delete documents start with `Delete.`. Document deletion is supported by ID, JSON field comparison, JSON containment, or JSON Path match. The pattern is the same as for finding or partially updating. _(There is no library method provided to delete all documents, though deleting by JSON field comparison where a non-existent field is null would accomplish this.)_
|
||||
|
||||
## Counting Documents
|
||||
|
||||
Functions to count documents start with `Count.`. Documents may be counted by a table in its entirety, by JSON field comparison, by JSON containment, or by JSON Path match. _(Counting by ID is an existence check!)_
|
||||
|
||||
## Document Existence
|
||||
|
||||
Functions to check for existence start with `Exists.`. Documents may be checked for existence by ID, JSON field comparison, JSON containment, or JSON Path match.
|
||||
|
||||
## What / How Cross-Reference
|
||||
|
||||
The table below shows which commands are available for each access method. (X = supported for both, P = PostgreSQL only)
|
||||
|
||||
Operation | `All` | `ById` | `ByFields` | `ByContains` | `ByJsonPath` | `FirstByFields` | `FirstByContains` | `FirstByJsonPath`
|
||||
----------|:-----:|:------:|:---------:|:------------:|:------------:|:--------------:|:-----------------:|:----------------:|
|
||||
`Count` | X | | X | P | P |
|
||||
`Exists` | | X | X | P | P |
|
||||
`Find` | X | X | X | P | P | X | P | P |
|
||||
`Patch` | | X | X | P | P |
|
||||
`RemoveFields` | | X | X | P | P |
|
||||
`Delete` | | X | X | P | P |
|
||||
|
||||
`Insert`, `Save`, and `Update.*` operate on single documents.
|
||||
|
||||
[best-guess on types]: https://sqlite.org/datatype3.html "Datatypes in SQLite • SQLite"
|
||||
[JSON Path]: https://www.postgresql.org/docs/15/functions-json.html#FUNCTIONS-SQLJSON-PATH "JSON Functions and Operators • PostgreSQL Documentation"
|
||||
[Advanced Usage]: /open-source/relational-documents/dotnet/advanced-usage.html "Advanced Usage • BitBadger.Documents • Bit Badger Solutions"
|
@ -1 +1,187 @@
|
||||
# Getting Started
|
||||
# Getting Started
|
||||
## Overview
|
||||
|
||||
Each library has three different ways to execute commands:
|
||||
- Functions/methods that have no connection parameter at all; for these, each call obtains a new connection. _(Connection pooling greatly reduced this overhead and churn on the database)_
|
||||
- Functions/methods that take a connection as the last parameter; these use the given connection to execute the commands.
|
||||
- Extensions on the `NpgsqlConnection` or `SqliteConnection` type (native for both C# and F#); these are the same as the prior ones, and the names follow a similar pattern (ex. `Count.All()` is exposed as `conn.CountAll()`).
|
||||
|
||||
This provides flexibility in how connections are managed. If your application does not care about it, configuring the library is all that is required. If your application generally does not care, but needs a connection on occasion, one can be obtained from the library and used as required. If you are developing a web application, and want to use one connection per request, you can register the library's connection functions as a factory, and have that connection injected. We will cover the how-to below for each scenario, but it is worth considering before getting started.
|
||||
|
||||
> A note on functions: the F# functions use `camelCase`, while C# calls use `PascalCase`. To cut down on the noise, this documentation will generally use the C# `Count.All` form; know that this is `Count.all` for F#, `conn.CountAll()` for the C# extension method, and `conn.countAll` for the F# extension.
|
||||
|
||||
## Namespaces
|
||||
|
||||
### C#
|
||||
|
||||
```csharp
|
||||
using BitBadger.Documents;
|
||||
using BitBadger.Documents.[Postgres|Sqlite];
|
||||
```
|
||||
|
||||
### F#
|
||||
|
||||
```fsharp
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.[Postgres|Sqlite]
|
||||
```
|
||||
|
||||
For F#, this order is significant; both namespaces have modules that share names, and this order will control which one shadows the other.
|
||||
|
||||
## Configuring the Connection
|
||||
|
||||
### The Connection String
|
||||
|
||||
Both PostgreSQL and SQLite use the standard ADO.NET connection string format ([`Npgsql` docs][], [`Microsoft.Data.Sqlite` docs][]). The usual location for these is an `appsettings.json` file, which is then parsed into an `IConfiguration` instance. For SQLite, all the library needs is a connection string:
|
||||
|
||||
```csharp
|
||||
// C#, SQLite
|
||||
// ...
|
||||
var config = ...; // parsed IConfiguration
|
||||
Sqlite.Configuration.UseConnectionString(config.GetConnectionString("SQLite"));
|
||||
// ...
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, SQLite
|
||||
// ...
|
||||
let config = ...; // parsed IConfiguration
|
||||
Configuration.useConnectionString (config.GetConnectionString("SQLite"))
|
||||
// ...
|
||||
```
|
||||
|
||||
For PostgreSQL, the library needs an `NpgsqlDataSource` instead. There is a builder that takes a connection string and creates it, so it still is not a lot of code: _(although this implements `IDisposable`, do not declare it with `using` or `use`; the library handles disposal if required)_
|
||||
|
||||
```csharp
|
||||
// C#, PostgreSQL
|
||||
// ...
|
||||
var config = ...; // parsed IConfiguration
|
||||
var dataSource = new NpgsqlDataSourceBuilder(config.GetConnectionString("Postgres")).Build();
|
||||
Postgres.Configuration.UseDataSource(dataSource);
|
||||
// ...
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, PostgreSQL
|
||||
// ...
|
||||
let config = ...; // parsed IConfiguration
|
||||
let dataSource = new NpgsqlDataSourceBuilder(config.GetConnectionString("Postgres")).Build()
|
||||
Configuration.useDataSource dataSource
|
||||
// ...
|
||||
```
|
||||
|
||||
### The Connection
|
||||
|
||||
- If the application does not care to control the connection, use the methods/functions that do not require one.
|
||||
- To retrieve an occasional connection (possibly to do multiple updates in a transaction), the `Configuration` static class/module for each implementation has a way. (For both of these, define the result with `using` or `use` so that they are disposed properly.)
|
||||
- For PostgreSQL, the `DataSource()` method returns the configured `NpgsqlDataSource` instance; from this, `OpenConnection[Async]()` can be used to obtain a connection.
|
||||
- For SQLite, the `DbConn()` method returns a new, open `SqliteConnection`.
|
||||
- To use a connection per request in a web application scenario, register it with <abbr title="Dependency Injection">DI</abbr>.
|
||||
|
||||
```csharp
|
||||
// C#, PostgreSQL
|
||||
builder.Services.AddScoped<NpgsqlConnection>(svcProvider =>
|
||||
Postgres.Configuration.DataSource().OpenConnection());
|
||||
// C#, SQLite
|
||||
builder.Services.AddScoped<SqliteConnection>(svcProvider => Sqlite.Configuration.DbConn());
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, PostgreSQL
|
||||
let _ = builder.Services.AddScoped<NpgsqlConnection(fun sp -> Configuration.dataSource().OpenConnection())
|
||||
// F#, SQLite
|
||||
let _ = builder.Services.AddScoped<SqliteConnection>(fun sp -> Configuration.dbConn ())
|
||||
```
|
||||
|
||||
After registering, this connection will be available on the request context and can be injected in the constructor for things like Razor Pages or MVC Controllers.
|
||||
|
||||
## Configuring Document IDs
|
||||
|
||||
### Field Name
|
||||
|
||||
A common .NET pattern when naming unique identifiers for entities / documents / etc. is the name `Id`. By default, this library assumes that this field is the identifier for your documents. If your code follows this pattern, you will be happy with the default behavior. If you use a different property, or [implement a custom serializer][ser] to modify the JSON representation of your documents' IDs, though, you will need to configure that field name before you begin calling other functions or methods. A great spot for this is just after you configure the connection string or data source (above). If you have decided that the field "Name" is the unique identifier for your documents, your setup would look something like...
|
||||
|
||||
```csharp
|
||||
// C#, All
|
||||
Configuration.UseIdField("Name");
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, All
|
||||
Configuration.useIdField "Name"
|
||||
```
|
||||
|
||||
Setting this will make `EnsureTable` create the unique index on that field when it creates a table, and will make all the `ById` functions and methods look for `data->>'Name'` instead of `data->>'Id'`. JSON is case-sensitive, so if the JSON is camel-cased, this should be configured to be `id` instead of `Id` (or `name` to follow the example above).
|
||||
|
||||
### Generation Strategy
|
||||
|
||||
The library can also generate IDs if they are missing. There are three different types of IDs, and each case of the `AutoId` enumeration/discriminated union can be passed to `Configuration.UseAutoIdStrategy()` to configure the library.
|
||||
|
||||
- `Number` generates a "max ID plus 1" query based on the current values of the table.
|
||||
- `Guid` generates a 32-character string from a Globally Unique Identifier (GUID), lowercase with no dashes.
|
||||
- `RandomString` generates random bytes and converts them to a lowercase hexadecimal string. By default, the string is 16 characters, but can be changed via `Configuration.UseIdStringLength()`. _(You can also use `AutoId.GenerateRandomString(length)` to generate these strings for other purposes; they make good salts, transient keys, etc.)_
|
||||
|
||||
All of these are off by default (the `Disabled` case). Even when ID generation is configured, though, only IDs of 0 (for `Number`) or empty strings (for `Guid` and `RandomString`) will be generated. IDs are only generated on `Insert`.
|
||||
|
||||
> Numeric IDs are a one-time decision. In PostgreSQL, once a document has a non-numeric ID, attempts to insert an automatic number will fail. One could switch from numbers to strings, and the IDs would be treated as such (`"33"` instead of `33`, for example). SQLite does a best-guess typing of columns, but once a string ID is there, the "max + 1" algorithm will not return the expected results.
|
||||
|
||||
## Ensuring Tables and Indexes Exist
|
||||
|
||||
Both PostgreSQL and SQLite store data in tables and can utilize indexes to retrieve that data efficiently. Each application will need to determine the tables and indexes it expects.
|
||||
|
||||
To discover these concepts, let's consider a naive example of a hotel chain; they have several hotels, and each hotel has several rooms. While each hotel could have its rooms as part of a `Hotel` document, there would likely be a lot of contention when concurrent updates for rooms, so we will put rooms in their own table. The hotel will store attributes like name, address, etc.; while each room will have the hotel's ID (named `Id`), along with things like room number, floor, and a list of date ranges where the room is not available. (This could be for customer reservation, maintenance, etc.)
|
||||
|
||||
_(Note that all "ensure" methods/functions below use the `IF NOT EXISTS` clause; they are safe to run each time the application starts up, and will do nothing if the tables or indexes already exist.)_
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
We have a few options when it comes to indexing our documents. We can index a specific JSON field; each table's primary key is implemented as a unique index on the configured ID field. We can also use a <abbr title="Generalized Inverted Index">GIN</abbr> index to index the entire document, and that index can even be [optimized for a subset of JSON Path operators][json-index].
|
||||
|
||||
Let's create a general-purpose index on hotels, a "HotelId" index on rooms, and an optimized document index on rooms.
|
||||
|
||||
```csharp
|
||||
// C#, Postgresql
|
||||
await Definition.EnsureTable("hotel");
|
||||
await Definition.EnsureDocumentIndex("hotel", DocumentIndex.Full);
|
||||
await Definition.EnsureTable("room");
|
||||
// parameters are table name, index name, and fields to be indexed
|
||||
await Definition.EnsureFieldIndex("room", "hotel_id", new[] { "HotelId" });
|
||||
await Definition.EnsureDocumentIndex("room", DocumentIndex.Optimized);
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#, PostgreSQL
|
||||
do! Definition.ensureTable "hotel"
|
||||
do! Definition.ensureDocumentIndex "hotel" Full
|
||||
do! Definition.ensureTable "room"
|
||||
do! Definition.ensureFieldIndex "room" "hotel_id" [ "HotelId" ]
|
||||
do! Definition.ensureDocumentIndex "room" Optimized
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
For SQLite, the only option for JSON indexes (outside some quite complex techniques) are indexes on fields. Just as traditional relational indexes, these fields can be specified in expected query order. In our example, if we indexed our rooms on hotel ID and room number, it could also be used for efficient retrieval just by hotel ID.
|
||||
|
||||
Let's create hotel and room tables, then index rooms by hotel ID and room number.
|
||||
|
||||
```csharp
|
||||
// C#, SQLite
|
||||
await Definition.EnsureTable("hotel");
|
||||
await Definition.EnsureTable("room");
|
||||
await Definition.EnsureIndex("room", "hotel_and_nbr", new[] { "HotelId", "RoomNumber" });
|
||||
```
|
||||
|
||||
```fsharp
|
||||
// F#
|
||||
do! Definition.ensureTable "hotel"
|
||||
do! Definition.ensureTable "room"
|
||||
do! Definition.ensureIndex "room" "hotel_and_nbr", [ "HotelId"; "RoomNumber" ]
|
||||
```
|
||||
|
||||
Now that we have tables, let's [use them][]!
|
||||
|
||||
[`Npgsql` docs]: https://www.npgsql.org/doc/connection-string-parameters "Connection String Parameter • Npgsql"
|
||||
[`Microsoft.Data.Sqlite` docs]: https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/connection-strings "Connection Strings • Microsoft.Data.Sqlite • Microsoft Learn"
|
||||
[ser]: ./advanced/custom-serialization.html "Advanced Usage: Custom Serialization • BitBadger.Documents"
|
||||
[json-index]: https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING
|
||||
[use them]: ./basic-usage.html "Basic Usage • BitBadger.Documents"
|
||||
|
@ -1 +0,0 @@
|
||||
# Introduction
|
@ -1,4 +1,4 @@
|
||||
- name: Introduction
|
||||
href: introduction.md
|
||||
- name: Getting Started
|
||||
href: getting-started.md
|
||||
href: getting-started.md
|
||||
- name: Basic Usage
|
||||
href: basic-usage.md
|
94
index.md
94
index.md
@ -2,10 +2,96 @@
|
||||
_layout: landing
|
||||
---
|
||||
|
||||
# This is the **HOMEPAGE**.
|
||||
BitBadger.Documents provides a lightweight document-style interface over [PostgreSQL][]'s and [SQLite][]'s JSON storage capabilities, with first-class support for both C# and F# programs. _(It is developed by the community; it is not officially affiliated with either project.)_
|
||||
|
||||
Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files.
|
||||
> NOTE: v4.1 is the latest version. See below for upgrading.
|
||||
|
||||
## Quick Start Notes:
|
||||
> Expecting `BitBadger.Npgsql.Documents`? This library replaced it as of v3.
|
||||
|
||||
1. Add images to the *images* folder if the file is referencing an image.
|
||||
## Installing
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
[![Nuget (with prereleases)][pkg-shield-pgsql]][pkg-link-pgsql]
|
||||
|
||||
```
|
||||
dotnet add package BitBadger.Documents.Postgres
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
[![Nuget (with prereleases)][pkg-shield-sqlite]][pkg-link-sqlite]
|
||||
|
||||
```
|
||||
dotnet add package BitBadger.Documents.Sqlite
|
||||
```
|
||||
|
||||
## Using
|
||||
|
||||
- **[Getting Started][]** provides an overview of the libraries' functions, how to provide connection details, and how to ensure required tables and indexes exist.
|
||||
- **[Basic Usage][]** details document-level retrieval, persistence, and deletion.
|
||||
- **[Advanced Usage][]** demonstrates how to use the building blocks provided by this library to write slightly-more complex queries.
|
||||
|
||||
## Upgrading Major Versions
|
||||
|
||||
* [v3 to v4][v3v4] ([Release][v4rel]) - Multiple field queries, ordering support, and automatic IDs
|
||||
* [v2 to v3][v2v3] ([Release][v3rel]; upgrade from `BitBadger.Npgsql.Documents`) - Namespace / project change
|
||||
* [v1 to v2][v1v2] ([Release][v2rel]) - Data storage format change
|
||||
|
||||
## Why Documents?
|
||||
|
||||
Document databases usually store <abbr title="JavaScript Object Notation">JSON</abbr> objects (as their "documents") to provide a schemaless persistence of data; they also provide fault-tolerant ways to query that possibly-unstructured data. [MongoDB][] was the pioneer and is the leader in this space, but there are several who provide their own take on it, and their own programming <abbr title="Application Programming Interface">API</abbr> to come along with it. They also usually have some sort of clustering, replication, and sharding solution that allows them to be scaled out (horizontally) to handle a large amount of traffic.
|
||||
|
||||
As a mature relational database, PostgreSQL has a long history of robust data access from the .NET environment; Npgsql is actively developed, and provides both ADO.NET and <abbr title="Entity Framework">EF</abbr> Core APIs. PostgreSQL also has well-established, battle-tested horizontal scaling options. Additionally, the [Npgsql.FSharp][] project provides a functional API over Npgsql's ADO.NET data access. These three factors make PostgreSQL an excellent choice for document storage, and its relational nature can help in areas where traditional document databases become more complex.
|
||||
|
||||
SQLite is another mature relational database implemented as a single file, with its access run in-process with the calling application. It works very nicely on its own, with caching and write-ahead logging options; a companion project called [Litestream][] allows these files to be continuously streamed elsewhere, providing point-in-time recovery capabilities one would expect from a relational database. Microsoft provides ADO.NET (and EF Core) drivers for SQLite as part of .NET. These combine to make SQLite a compelling choice, and the hybrid relational/document model allows users to select the model of data that fits their model the best.
|
||||
|
||||
In both cases, the document access functions provided by this library are dead-simple. For more complex queries, it also provides the building blocks to construct these with minimal code.
|
||||
|
||||
## Why Not [something else]?
|
||||
|
||||
We are blessed to live in a time where there are a lot of good data storage options that are more than efficient enough for the majority of use cases. Rather than speaking ill of other projects, here is the vision of the benefits these libraries aim to provide:
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
PostgreSQL is the most popular non-WordPress database for good reason.
|
||||
|
||||
- **Quality** - PostgreSQL's reputation is one of a rock-solid, well-maintained, and continually evolving database.
|
||||
- **Availability** - Nearly every cloud database provider offers PostgreSQL, and for custom servers, it is a package install away from being up and running.
|
||||
- **Efficiency** - PostgreSQL is very efficient, and its indexing of JSONB allows for quick access via any field in a document.
|
||||
- **Maintainability** - The terms "separation of concerns" and "locality of behavior" often compete within a code base, and separation of concerns often wins out; cluttering your logic with SQL can be less than optimal. Using this library, though, it may separate the concerns enough that the calls can be placed directly in the regular logic, providing one fewer place that must be looked up when tracing through the code.
|
||||
- **Simplicity** - <abbr title="Structured Query Language">SQL</abbr> is a familiar language; even when writing manual queries against the data store created by this library, everything one knows about SQL applies, with [a few operators added][json-ops].
|
||||
- **Reliability** - The library has a full suite of tests against both the C# and F# APIs, [run against every supported PostgreSQL version][tests] to ensure the functionality provided is what is advertised.
|
||||
|
||||
### SQLite
|
||||
|
||||
The [SQLite "About" page][sqlite-about] has a short description of the project and its strengths. Simplicity, flexibility, and a large install base speak for themselves. A lot of people believe they will need a lot of features offered by server-based relational databases, and live with that complexity even when the project is small. A smarter move may be to build with SQLite; if the need arises for something more, the project is very likely a success!
|
||||
|
||||
Many of the benefits listed for PostgreSQL apply here as well, including its test coverage - but SQLite removes the requirement to run it as a server!
|
||||
|
||||
## Support
|
||||
|
||||
Issues can be filed on the project's GitHub repository.
|
||||
|
||||
|
||||
[PostgreSQL]: https://www.postgresql.org/ "PostgreSQL"
|
||||
[SQLite]: https://sqlite.org/ "SQLite"
|
||||
[pkg-shield-pgsql]: https://img.shields.io/nuget/vpre/BitBadger.Documents.Postgres
|
||||
[pkg-link-pgsql]: https://www.nuget.org/packages/BitBadger.Documents.Postgres/ "BitBadger.Documents.Postgres • NuGet"
|
||||
[pkg-shield-sqlite]: https://img.shields.io/nuget/vpre/BitBadger.Documents.Sqlite
|
||||
[pkg-link-sqlite]: https://www.nuget.org/packages/BitBadger.Documents.Sqlite/ "BitBadger.Documents.Sqlite • NuGet"
|
||||
[Getting Started]: docs/getting-started.html "Getting Started • BitBadger.Documents"
|
||||
[Basic Usage]: /open-source/relational-documents/dotnet/basic-usage.html "Basic Usage • BitBadger.Documents • Bit Badger Solutions"
|
||||
[Advanced Usage]: /open-source/relational-documents/dotnet/advanced-usage.html "Advanced Usage • BitBadger.Documents • Bit Badger Solutions"
|
||||
[v3v4]: /open-source/relational-documents/dotnet/upgrade-v3-to-v4.html "Upgrade from v3 to v4 • BitBadger.Documents • Bit Badger Solutions"
|
||||
[v4rel]: https://git.bitbadger.solutions/bit-badger/BitBadger.Documents/releases/tag/v4 "Version 4 • Releases • BitBadger.Documents • Bit Badger Solutions Git"
|
||||
[v2v3]: /open-source/relational-documents/dotnet/upgrade-v2-to-v3.html "Upgrade from v2 to v3 • BitBadger.Documents • Bit Badger Solutions"
|
||||
[v3rel]: https://git.bitbadger.solutions/bit-badger/BitBadger.Documents/releases/tag/v3 "Version 3 • Releases • BitBadger.Documents • Bit Badger Solutions Git"
|
||||
[v1v2]: /open-source/relational-documents/dotnet/upgrade-v1-to-v2.html "Upgrade from v1 to v2 • BitBadger.Npgsql.Documents • Bit Badger Solutions"
|
||||
[v2rel]: https://github.com/bit-badger/BitBadger.Npgsql.Documents/releases/tag/v2 "Version 2 • Releases • BitBadger.Npgsql.Documents • GitHub"
|
||||
[MongoDB]: https://www.mongodb.com/ "MongoDB"
|
||||
[Npgsql.FSharp]: https://zaid-ajaj.github.io/Npgsql.FSharp/#/ "Npgsql.FSharp"
|
||||
[Litestream]: https://litestream.io/ "Litestream"
|
||||
[sqlite-about]: https://sqlite.org/about.html "About • SQLite"
|
||||
[json-ops]: https://www.postgresql.org/docs/15/functions-json.html#FUNCTIONS-JSON-OP-TABLE "JSON Functions and Operators • Documentation • PostgreSQL"
|
||||
[tests]: https://github.com/bit-badger/BitBadger.Documents/actions/workflows/ci.yml "Actions • BitBadger.Documents • GitHub"
|
||||
|
Loading…
x
Reference in New Issue
Block a user