Open.Database.Extensions.Core
11.0.0-preview01
See the version list below for details.
dotnet add package Open.Database.Extensions.Core --version 11.0.0-preview01
NuGet\Install-Package Open.Database.Extensions.Core -Version 11.0.0-preview01
<PackageReference Include="Open.Database.Extensions.Core" Version="11.0.0-preview01" />
<PackageVersion Include="Open.Database.Extensions.Core" Version="11.0.0-preview01" />
<PackageReference Include="Open.Database.Extensions.Core" />
paket add Open.Database.Extensions.Core --version 11.0.0-preview01
#r "nuget: Open.Database.Extensions.Core, 11.0.0-preview01"
#:package Open.Database.Extensions.Core@11.0.0-preview01
#addin nuget:?package=Open.Database.Extensions.Core&version=11.0.0-preview01&prerelease
#tool nuget:?package=Open.Database.Extensions.Core&version=11.0.0-preview01&prerelease
Open.Database.Extensions
Useful set of utilities and abstractions for simplifying modern database operations and ensuring dependency injection compatibility.
Principles
- Minimize connection open time.
- Deferred/lazy transformation.
- Optimize for specific use cases.
- Minimize boilerplate code.
Features
- Provides a fluent interface for database operations.
- Supports dependency injection for connection factories.
- Support both synchronous and asynchronous operations.
- Provides expressive commands for executing SQL queries and stored procedures.
Connection Factories
Connection factories facilitate creation and disposal of connections without the concern of a connection reference or need for awareness of a connection string.
A SqlConnectionFactory is provided and can be overridden to provide more specific dependency injection configurations.
Expressive Commands
The provided expressive command classes allow for an expressive means to append parameters and execute the results without lengthy complicated setup.
Extensions are provided to create commands from connection factories.
Example
var result = connectionFactory
.StoredProcedure("[procedure name]")
.AddParam("a",1)
.AddParam("b",true)
.AddParam("c","hello")
.ExecuteScalar();
Extensions
Instead of writing this:
var myResult = new List<T>();
using(var reader = await mySqlCommand.ExecuteReaderAsync(CommandBehavior.CloseConnection))
{
while(await reader.ReadAsync())
myResult.Add(transform(reader));
}
Is now simplified to this:
var myResult = await mySqlCommand.ToListAsync(transform);
Deferred Transformation
In order to keep connection open time to a minimum, some methods cache data before closing the connection and then subsequently applying the transformations as needed.
Results<T>() and ResultsAsync<T>()
Queues all the data. Then using the provided type T entity, the data is coerced by which properties intersect with the ones available to the IDataReader.
Optionally a field to column override map can be passed as a parameter. If a column is set as null then that field is ignored (not applied to the model).
Examples
If all the columns in the database map exactly to a field: (A column that has no associated field/property is ignored.)
var people = cmd.Results<Person>();
If the database fields don't map exactly:
var people = cmd.Results<Person>(
(Field:"FirstName", Column:"first_name"),
(Field:"LastName", Column:"last_name")));
or
var people = cmd.Results<Person>(
("FirstName", "first_name"),
("LastName", "last_name"));
or
var people = cmd.Results<Person>(new Dictionary<string,string>{
{"FirstName", "first_name"},
{"LastName", "last_name"});
Retrieve() and RetrieveAsync()
Queues all the data. Returns a QueryResult<Queue<object[]>> containing the requested data and column information. The .DequeueAsMappedDictionaries() extension will iteratively convert the results to dictionaries for ease of access.
ResultsAsync<T>
ResultsAsync<T>() is fully asynchronous from end-to-end but returns an IEnumerable<T> that although has fully buffered the all the data into memory, has deferred the transformation until enumerated. This way, the asynchronous data pipeline is fully complete before synchronously transforming the data.
Mapped queries: Map<T>()
Map<T>() is the fast, expressive way to read rows into a model. It is deferred: nothing runs until a terminal is called, and rows are mapped as the reader streams.
var posts = await connection
.Command("select * from posts where author = @author")
.AddParam("author", 42)
.Map<Post>()
.ToArrayAsync();
var page = connection.Command("select * from posts order by id")
.Map<Post>()
.Skip(20).Take(10) // skipped rows are advanced, never mapped; reading stops once the take is met
.ToArray();
var one = connection.Command("select * from posts where id = @id").AddParam("id", 7).Map<Post>().SingleOrDefault();
Prefer ToArray for results you only read: it allocates one exactly-sized array (no spare capacity, no collection object) and an empty result is the shared static empty array. Use ToList when you need a mutable collection.
Terminals: ToArray, ToList, ToImmutableArray, ToDictionary, ToHashSet, FirstOrDefault, SingleOrDefault (each with an …Async twin) and Read(...)/ReadAsync(...) for streaming.
How it stays fast:
- The mapping from a result set to
Tis compiled once per result-set shape (column names + column types) and cached, so a repeat query does no reflection and no compilation. - Values are read with the provider's typed getters (
GetInt32,GetString, …), so nothing is boxed. A column whose type differs from the property converts numerically (long→int,int→ enum, …) or through a general conversion (string → enum/Guid). NULLis honored for reference types andNullable<T>properties. ANULLin a column mapped to a non-nullable value-type property is the provider's error to raise, and the exception names the property and column.- Columns are matched to properties case-insensitively; unmatched columns are ignored, and unmatched properties keep their defaults. Field/column overrides are supported:
.Map<Post>(("Id", "PostId"), ("Body", null))(anullcolumn ignores the field). - Readers are opened with
SequentialAccess | SingleResult(SingleRowforFirstOrDefault), like Dapper. - The execution path allocates no closures: state-passing
ExecuteReader/ExecuteReaderAsyncoverloads onIExecuteReadertake astaticlambda plus the state it needs.
Transactions
Example:
// Returns true if the transaction is successful.
public static bool TryTransaction()
=> ConnectionFactory.Using(connection =>
// Open a connection and start a transaction.
connection.ExecuteTransactionConditional(transaction => {
// First procedure does some updates.
var count = transaction
.StoredProcedure("[Updated Procedure]")
.ExecuteNonQuery();
// Second procedure validates the results.
// If it returns true, then the transaction is committed.
// If it returns false, then the transaction is rolled back.
return transaction
.StoredProcedure("[Validation Procedure]")
.AddParam("@ExpectedCount", count)
.ExecuteScalar<bool>();
}));
8.0 Release Notes
- All
.ConfigureAwait(true)are now.ConfigureAwait(false)as they should be. The caller will need to.ConfigureAwait(true)if they need to resume on the calling context. - Added
Open.Database.Extensions.MSSqlClientforMicrosoft.Data.SqlClientsupport. - .NET 8.0 added to targets to ensure potential compilation and performance improvements are available.
- Improved nullable integrity.
9.0 Release Notes
- Open.Database.Extensions meta-package is discontinued.
- .NET 9.0 added to targets to ensure potential compilation and performance improvements are available.
- Implemented some .NET 8 and 9 specific features.
- Significant cleanup and simplification where possible.
10.1.0 Release Notes
A modernization and allocation-reduction pass. Source-compatible with 10.0 — existing code recompiles cleanly.
Target frameworks:
net10.0is now the primary/modern target.netstandard2.0andnetstandard2.1are retained as shimmed legacy paths (net8/net9 consumers use thenetstandard2.1build);Open.Database.Extensions.MSSqlClientalso targetsnet472. The explicitnet8.0/net9.0targets were dropped.Fewer allocations on the hot paths: case-insensitive column-to-property matching now uses
StringComparer.OrdinalIgnoreCase(andFrozenDictionaryonnet10.0) instead of allocating an upper-cased string per column; assorted per-query LINQ and dead code removed.Ergonomic field-mapping overrides:
Results<T>,ResultsAsync<T>,To<T>, etc. now accept target-typednew(...)and collection expressions:cmd.Results<Person>(new("FirstName", "first_name"), new("LastName", "last_name")); cmd.Results<Person>([new("FirstName", "first_name"), new("LastName", "last_name")]);Modernized
params: ordinal helpers (Retrieve,AsEnumerable, …) acceptparams IEnumerable<int>/params IEnumerable<string>— any enumerable, not just arrays.Read-only inputs: several read-only
IList<T>parameters were widened toIReadOnlyList<T>(GetValuesFromOrdinals,EnumerateValuesFromOrdinals,ToDictionary). Arrays,List<T>,ImmutableArray<T>, etc. all still bind. (A custom type implementing onlyIList<T>— and not alsoIReadOnlyList<T>— would need to recompile; no BCL collection is affected.)Lower per-query overhead:
Transformer<T>caches its per-type reflection once instead of rebuilding it on every query; buffered/data-table paths pre-size collections and avoid redundantSelectiterators.Fixed a wasted-allocation / incorrect-return bug in
CopyToDBNullAsNull.Public API surface is now tracked with
Microsoft.CodeAnalysis.PublicApiAnalyzers, and unit-test coverage was substantially expanded.
11.0.0 Release Notes
11.0.0-preview01. This is a preview: please try it and tell us what you find. See Known limitations below.
A performance release: result mapping is rewritten and now beats Dapper 2.1.89 in the included benchmarks (Benchmarks/, see Benchmarks/RESULTS.md), with a fraction of the allocations on multi-row results.
- Cached, typed row mapping. The mapping for a result-set shape is compiled once and reused. Previously every query compiled a setter per property (~1 ms fixed cost per
Map<T>()). Values are read with typed getters, so nothing is boxed. - Direct read loops.
ToList,ToArray,FirstOrDefault,SingleOrDefault,ToDictionary,ToHashSet,ToImmutableArray(and async twins) read straight from the reader.Skipadvances without mapping,Takestops reading when met. They are now instance methods onMappedQuery<T>(no boxing of the query struct); theIMappedQuery<T>extensions remain for generic code. - No per-call closures.
IExecuteReadergained state-passingExecuteReader<TState, TResult>/ExecuteReaderAsync<TState, TResult>; pass astaticlambda and the state it needs. - Leaner commands. A command created from a connection no longer allocates a pool wrapper,
Paramsis created on first use, and async execution skips awaiting when the connection is already open. - Readers use
SequentialAccess | SingleResultforMap<T>()(andSingleRowforFirstOrDefault), like Dapper.Map(selector)keeps the default access mode because a selector may read columns in any order. - Channel and Dataflow fixes.
complete: falseno longer runs the command twice. The mappedToChannel<T>/ToChannelAsync<T>andToTargetBlock<T>overloads complete your target only after every row has been delivered and the database reader has been released (before, up to about 1,000 buffered rows could be lost with a bounded target), no longer lose the last row when the reader fails mid-stream, and map correctly when the reader has extra unmapped columns. - Connection and command fixes.
OpenAsync(IDbConnection)no longer opens aDbConnectiontwice,EnsureOpenAsynccloses a connection that finished opening just as cancellation arrived, andFirstOrdinalResults<T>(IDbCommand, behavior)honorsbehavior.
Breaking changes
IExecuteReaderhas two new members (above). Custom implementations must add them;ExpressiveCommand*already do.- A
NULLin a column mapped to a non-nullable value-type property is the provider's error to raise (it is wrapped in anInvalidOperationExceptionnaming the property and column). Map nullable columns toNullable<T>/reference properties. - Field-mapping overrides naming a property that does not exist throw
ArgumentException(wasKeyNotFoundException). Only properties with a public setter are mapped. FirstOrdinalResults<T>readsNULLasdefault. (ResultsBuffered*andFirstOrdinalResults<T>still buffer into a queue and return a dequeuing enumerable, so items are released to the GC as they are drained.)ExpressiveCommandBase.Paramsis created lazily.- Async command execution passes the command's
CancellationTokento the connection open. - Mapped Channel and Dataflow overloads: a mapping failure now faults the target when
completeistrue; the target is never completed or faulted before the reader is released;ArgumentNullException.ParamNamefor a null target is"target"; and a mapped async channel call that is cancelled succeeds only if the reader had already reported the end of the results, otherwise it throwsOperationCanceledException.
Known limitations in this preview
Retrieve()followed byDequeueAs<T>()is a two-pass route (rows are copied intoobject[]and mapped as they are drained); it is slower and allocates more than a directMap<T>()at larger sizes. For typed results preferMap<T>()orResultsBuffered<T>(). SeeBenchmarks/RESULTS.md.- The unmapped
ToChanneloverloads can still lose the row that was in flight when a read fails while the target is full; the cause is in the underlying channel library and a fix is planned. - Benchmarks and tests cover the in-memory provider, SQLite and SQL Server LocalDB. Real-world feedback on other providers and workloads is welcome.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.14)
- System.Collections.Immutable (>= 10.0.14)
- System.Linq.AsyncEnumerable (>= 10.0.14)
- System.Memory (>= 4.6.3)
- System.Threading.Channels (>= 10.0.14)
- System.Threading.Tasks.Extensions (>= 4.6.3)
-
.NETStandard 2.1
- System.Collections.Immutable (>= 10.0.14)
- System.Linq.AsyncEnumerable (>= 10.0.14)
- System.Threading.Channels (>= 10.0.14)
-
net10.0
- No dependencies.
NuGet packages (5)
Showing the top 5 NuGet packages that depend on Open.Database.Extensions.Core:
| Package | Downloads |
|---|---|
|
Open.Database.Extensions
Useful set of utilities and abstractions for simplifying modern data-access operations and ensuring DI compatibility. This is simply a meta-package that includes Open.Database.Extensions.Core. |
|
|
Open.Database.Extensions.Channel
Database extensions for pipelining data through channels. Includes Open.Database.Extensions.Core. |
|
|
Open.Database.Extensions.SqlClient
Useful set of utilities and abstractions for simplifying modern SQL Client data-access operations and ensuring DI compatibility. |
|
|
Open.Database.Extensions.Dataflow
Useful set of utilities and abstractions for simplifying modern data-access operations with Dataflow and ensuring DI compatibility. |
|
|
Open.Database.Extensions.MSSqlClient
Useful set of utilities and abstractions for simplifying modern SQL Client data-access operations and ensuring DI compatibility. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 11.0.0-rc.3 | 0 | 11/9/2026 |
| 11.0.0-rc.2 | 35 | 11/9/2026 |
| 11.0.0-rc.1 | 44 | 11/8/2026 |
| 11.0.0-preview06 | 40 | 11/8/2026 |
| 11.0.0-preview05 | 52 | 11/8/2026 |
| 11.0.0-preview04 | 54 | 11/6/2026 |
| 11.0.0-preview03 | 45 | 11/6/2026 |
| 11.0.0-preview02 | 52 | 11/6/2026 |
| 11.0.0-preview01 | 58 | 11/5/2026 |
| 10.3.0 | 135 | 11/5/2026 |
| 10.2.0 | 8,985 | 8/14/2026 |
| 10.1.0 | 577 | 8/13/2026 |
| 10.0.1 | 183 | 8/12/2026 |
| 10.0.0 | 238 | 8/12/2026 |
| 9.0.3 | 5,149 | 3/17/2025 |
| 9.0.3-debug | 348 | 3/18/2025 |
| 9.0.2 | 445 | 3/17/2025 |
| 9.0.2-debug | 331 | 3/17/2025 |
| 9.0.1 | 861 | 3/17/2025 |
| 9.0.1-debug | 445 | 3/17/2025 |