Dapper.Entities.SqlServer
8.0.11
See the version list below for details.
dotnet add package Dapper.Entities.SqlServer --version 8.0.11
NuGet\Install-Package Dapper.Entities.SqlServer -Version 8.0.11
<PackageReference Include="Dapper.Entities.SqlServer" Version="8.0.11" />
paket add Dapper.Entities.SqlServer --version 8.0.11
#r "nuget: Dapper.Entities.SqlServer, 8.0.11"
// Install Dapper.Entities.SqlServer as a Cake Addin #addin nuget:?package=Dapper.Entities.SqlServer&version=8.0.11 // Install Dapper.Entities.SqlServer as a Cake Tool #tool nuget:?package=Dapper.Entities.SqlServer&version=8.0.11
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
This assumes use of the SQL Server package, but the instructions are essentially the same with PostgreSql.
In your entity project, add the Dapper.Entities.Abstractions package. This gives you the
IEntity
interface you need in subsequent steps. All your entity classes should implementIEntity
. This is the same regardless of your database platform.In your application project, create 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>
- In the startup of your application add your
Database
class to your services collection as either a scoped or singleton dependency. Now throughout your application you can inject it where needed and have access to your repository classes.
Entity class considerations
The only requirement for entity classes you use with this library is that they implement IEntity<TKey>, which you install with the Dapper.Entities.Abstractions package. 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.
Note that if you use your entity classes with EF Core, you can't use multiple [Key]
attributes on the same class. If you need your code to be EF-compatible, use the IAlternateKey interface, as in this example.
Extension Methods
If you need more direct entity access without a repository class, there are IDbConnection
CRUD extension methods. These methods don't apply any "business logic" per se that a repository class would, but are offered for convenience. See tests to see these in use. (The Postgres tests are essentially the same.)
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.
There's also a PostgreSql implementation. As of this writing, I don't have any Postgres experience to speak of. I wanted to show that this architecture could work for a variety of backend databases, and might even start using Postgres for some things.
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.
Check out my Ensync project to see how you can do code-first entity development in SQL Server without migrations. Please note I don't have this working for PostgreSql.
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.2)
- Microsoft.Data.SqlClient (>= 5.1.5)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
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.1.0 | 76 | 11/9/2024 |
8.0.12 | 129 | 1/31/2024 |
8.0.11 | 89 | 1/30/2024 |
8.0.10 | 98 | 1/30/2024 |
8.0.9 | 167 | 1/3/2024 |
8.0.8 | 119 | 1/3/2024 |
8.0.7 | 132 | 1/3/2024 |
8.0.6 | 134 | 1/2/2024 |
8.0.5 | 147 | 1/1/2024 |
8.0.4 | 121 | 12/31/2023 |
8.0.3 | 130 | 12/30/2023 |
8.0.2 | 125 | 12/28/2023 |
8.0.0 | 181 | 11/15/2023 |
1.0.5 | 179 | 7/31/2023 |