Dapper.Entities.PostgreSql
8.0.0-alpha
See the version list below for details.
dotnet add package Dapper.Entities.PostgreSql --version 8.0.0-alpha
NuGet\Install-Package Dapper.Entities.PostgreSql -Version 8.0.0-alpha
<PackageReference Include="Dapper.Entities.PostgreSql" Version="8.0.0-alpha" />
paket add Dapper.Entities.PostgreSql --version 8.0.0-alpha
#r "nuget: Dapper.Entities.PostgreSql, 8.0.0-alpha"
// Install Dapper.Entities.PostgreSql as a Cake Addin #addin nuget:?package=Dapper.Entities.PostgreSql&version=8.0.0-alpha&prerelease // Install Dapper.Entities.PostgreSql as a Cake Tool #tool nuget:?package=Dapper.Entities.PostgreSql&version=8.0.0-alpha&prerelease
SqlServer package:
This is a minimal ORM framework that uses Dapper and a repository pattern approach. The only hard dependency on your entity classes is that they implement IEntity<TKey>. It lets you write code like this (Blazor):
@page "/Business/{Id:int}"
@inject MyDatabase Db
<EditForm Model="model" OnValidSubmit="Save">
// markup omitted for clarity
</EditForm>
@code {
Entities.Business model = new();
[Parameter] public int Id { get; set; }
protected override async OnInitializedAsync()
{
if (Id is not 0)
{
model = await Db.Business.GetAsync(Id);
}
}
async Task Save() => await Db.Business.SaveAsync(model);
}
Service MyDatabase
is injected, giving access to any number of database tables. In this example it's using a Business
table and calling GetAsync
to fetch a row, and SaveAsync
to insert or update a row.
Walkthrough
- Start by creating a class that derives from
SqlServerDatabase
, passing a connection string andILogger
in the constructor.
<details> <summary>Code</summary>
public class MyDatabase : SqlServerDatabase
{
public MyDatabase(string connectionString, ILogger<MyDatabase> logger) : base(connectionString, logger)
{
}
// todo: add Repository properties for the tables in your database
}
</details>
- Add a
Repository
class that encapsulates conventions the tables in your database follow. In this example, I'm setting a convention that all my tables will haveint
keys, but you can choose any struct type you want. If there's business logic that applies to all or most tables, it would go in this class as well. There are many overrides you can implement to customize repository behavior, adding trigger-like behavior, permission checks, and multi-tenant isolation, for example. This is a bare-bones example below.
<details> <summary>Code</summary>
public class BaseRepository<TEntity> : Repository<MyDatabase, TEntity, int> where TEntity : IEntity<int>
{
public BaseRepository(MyDatabase database) : base(database)
{
}
}
</details>
- Go back and add repository properties to your
MyDatabase
class like this. In this example, I'm adding aBusiness
repository along withAnother
andYetAnother
. These should be model classes in your application. TheBusiness
example comes from the test here.
<details> <summary>Code</summary>
public class MyDatabase : SqlServerDatabase
{
public MyDatabase(string connectionString, ILogger<MyDatabase> logger) : base(connectionString, logger)
{
}
// todo: add Repository properties for the tables in your database
public BaseRepository<Business> Business => new(this);
public BaseRepository<AnotherTable> Another => new(this);
public BaseRepository<YetAnotherTable> YetAnother => new(this);
}
</details>
Entity class considerations
The only requirement for entity classes you use with this library is that they implement IEntity<TKey>. This gives your entity classes an Id
property in the struct type of your choice.
You can use the [NotMapped]
attribute on columns that don't save directly to your database table. Likewise, you can also use [NotUpdated]
and [NotInserted]
to get finer control on column save behavior.
Use the [Key]
attribute on any combination of properties to define an entity's alternate key. This lets you take advantage of the MergeAsync method, which searches for an existing row before inserting a new row. Do not use [Key]
on the Id
property. It's already understood to be a key. In my tests, notice I use the [Key]
attribute here to define uniqueness of the UserId
property.
SQL generation
The low-level Database
class (from which the SQL Server implementation derives) constructor accepts an ISqlBuilder. This is responsible for generating the SQL statements used by Repository
classes. I offer a default implementation DefaultSqlBuilder for SQL Server. You can implement this yourself to generate SQL however you like. My implementation is a bare-bones approach that does not do concurrency checking, for example.
Note that you can also use stored procedures or completely custom SQL for select repositories.
Next Steps
In the walkthrough above, I have a single BaseRepository
assumed to be used with all tables. In a realistic application, you'd have tables with unique business logic such as trigger-like behavior, permission checks, validation, change tracking, audit tracking, and so on. This library doesn't provide any of that capability built-in. Rather, this library provides many virtual methods in the Repository class such as BeforeSaveAsync
, AfterSaveAsync
, BeforeDeleteAsync
to let you richly customize your data access.
Background
This is an evolution of Dapper.Repository, which I feel has gotten a bit complicated due to tight integration with authentication. I felt it was time to drop back and refactor, rethink some dependencies, and re-architect this from scratch. I've probably made two dozen or more ORM libraries over my career, so this is definitely a weird obsession I have. Crafting ORM libraries is one of those things devs are told not to do because the ORM problem is well-solved by much smarter people using very mature, well-tested libraries. But a truly great dev experience with data in C# remains somewhat elusive, in my opinion. I've played with EF Core a bit more than usual lately, and that's part of what's driving this effort. The truth is that I really do not enjoy working with EF Core. ALthough I've made some peace with migrations, having practiced some more, I still run into too many gotchas and annoyances with EF.
Note that as a "minimal" library, this is focused on CRUD operations only. It's not a general purpose query library nor intended to compete with LINQ, for example. (I love LINQ!) There are many interesting query helper libraries out there. I have my own that uses Dapper internally as well Dapper.QX.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. 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. |
-
net8.0
- Dapper (>= 2.1.24)
- Dapper.Entities.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Npgsql (>= 8.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
8.0.10-alpha | 170 | 1/31/2024 |
8.0.8-alpha | 57 | 1/30/2024 |
8.0.7-alpha | 119 | 1/3/2024 |
8.0.6-alpha | 81 | 1/3/2024 |
8.0.5-alpha | 74 | 1/3/2024 |
8.0.4-alpha | 70 | 1/2/2024 |
8.0.3-alpha | 72 | 1/1/2024 |
8.0.2-alpha | 77 | 12/31/2023 |
8.0.1-alpha | 70 | 12/30/2023 |
8.0.0-alpha | 82 | 12/29/2023 |