Getting started
Install
Most applications need one package. The others add integrations.
| Package | Use it for | Targets |
|---|---|---|
Open.Database.Extensions.Core |
Everything in this guide except channels, dataflow and SQL Server helpers. Provider-agnostic. | netstandard2.0, netstandard2.1, net10.0 |
Open.Database.Extensions.Channel |
Reading into channels | same |
Open.Database.Extensions.Dataflow |
Reading into dataflow blocks | same |
Open.Database.Extensions.MSSqlClient |
SqlConnectionFactory and SQL Server specializations on Microsoft.Data.SqlClient |
net472, net10.0 |
.NET 8 and .NET 9 applications use the netstandard2.1 build; .NET Framework applications use netstandard2.0 (or net472 for the SQL Server package). Version 11 is a preview, so ask for prereleases:
dotnet add package Open.Database.Extensions.Core --prerelease
Core does not reference any database provider. Bring your own: the examples in this guide use Microsoft.Data.Sqlite so that you can run them without a server.
Your first query
A model needs a public parameterless constructor and public settable properties. Columns are matched to property names, ignoring case.
public class Post
{
public int Id { get; set; }
public int Author { get; set; }
public string Title { get; set; } = "";
public string? Body { get; set; }
}
There are two ways to start a command. Both end the same way, with AddParam for parameters and a terminal that runs the query. Side by side, row against row ("nothing" marks a step the factory makes unnecessary):
From a connection factory
// nothing: the factory creates, opens and disposes the connection
List<Post> posts = connectionFactory.Command(ListSql)
.AddParam("author", 42)
.Map<Post>().ToList();
From a connection you own
using var connection = new SqliteConnection(connectionString);
List<Post> posts = connection.Command(ListSql)
.AddParam("author", 42)
.Map<Post>().ToList();
The factory is created once per application:
var connectionFactory = new DbConnectionFactory<SqliteConnection>(() => new SqliteConnection(connectionString));
What is different:
- Who creates the connection. The factory, for each execution; or you.
- Open and close. In both forms the command opens the connection only if it is closed. The factory form disposes the connection when the terminal returns. In the connection form the command closes only what it opened and never disposes it.
Before and after
The same query with plain ADO.NET and with the extensions, row against row: the connection, the command, the parameter, then the read loop and the mapping.
Plain ADO.NET
using var connection = new SqliteConnection(connectionString);
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = ListSql;
var parameter = command.CreateParameter();
parameter.ParameterName = "@author";
parameter.Value = 42;
command.Parameters.Add(parameter);
var posts = new List<Post>();
using var reader = command.ExecuteReader();
while (reader.Read())
{
posts.Add(new Post
{
Id = reader.GetInt32(0),
Author = reader.GetInt32(1),
Title = reader.GetString(2),
Body = reader.IsDBNull(3) ? null : reader.GetString(3),
});
}
Open.Database.Extensions (factory)
// nothing: the factory creates, opens and disposes the connection
List<Post> posts = connectionFactory
.Command(ListSql)
.AddParam("author", 42)
.Map<Post>().ToList();
Lines you write per call: plain ADO.NET 20, Open.Database.Extensions 4 (plus one line of one-time setup per application).
What is different:
- Plain ADO.NET needs you to open the connection, create and dispose the command and the reader, add each parameter by hand and copy each column by ordinal, with an
IsDBNullcheck for nullable columns. - With the extensions the factory opens and disposes the connection, the command is one call, and the terminal does the read loop and the mapping. Matching columns to properties is by name, ignoring case.
With SQL Server
Open.Database.Extensions.MSSqlClient provides SqlConnectionFactory. Everything above works the same way, and stored procedures are one call away:
// Open.Database.Extensions.MSSqlClient wraps Microsoft.Data.SqlClient.
var connectionFactory = new SqlConnectionFactory(@"Server=(localdb)\MSSQLLocalDB;Database=Blog;Integrated Security=true");
Post[] posts = await connectionFactory
.Command("select * from posts where author = @author")
.AddParam("author", 42)
.Map<Post>()
.ToArrayAsync();
int touched = await connectionFactory
.StoredProcedure("[dbo].[TouchPosts]")
.AddParam("author", 42)
.ExecuteNonQueryAsync();
Next steps
- The foundation explains the extensions underneath, and who owns a connection.
- Expressive commands covers parameters and every terminal.
- Mapping rows to objects lists exactly what
Map<T>()does and does not do.