Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test.
$ dotnet add package Rystem.RepositoryFramework.Infrastructure.EntityFrameworkThis package enables seamless integration between Entity Framework Core and the Repository Framework, allowing you to leverage the Repository pattern with automatic mapping between your Domain Model and Database Model.
In Domain-Driven Design (DDD), you often have:
MappingUser): Your business model with domain logic and clean interfacesUser): The actual Entity Framework entity that maps to your database schemaThe Entity Framework integration allows you to work with your domain model while the framework automatically handles mapping to/from the database model.
t.DbSet = x => x.Users;
This specifies which DbSet from your Entity Framework context holds the data for this repository. It tells the framework: "Find the data in the Users table through this DbSet property."
t.References = x => x.Include(x => x.IdGruppos);
These are Entity Framework Include() statements that specify which related entities should be eagerly loaded with your main entity. In this example, whenever you fetch a User, its related IdGruppos collection will automatically be loaded. You can chain multiple includes here.
Why this matters: Without proper references, your related data won't be loaded, causing N+1 query problems.
builder.Translate<User>()
.With(x => x.Username, x => x.Nome)
.With(x => x.Username, x => x.Cognome)
.With(x => x.Email, x => x.IndirizzoElettronico)
.With(x => x.Groups, x => x.IdGruppos)
.With(x => x.Id, x => x.Identificativo)
.WithKey(x => x, x => x.Identificativo);
This configures how properties are mapped between your domain model and database model:
.With(domainProperty, databaseProperty): Maps individual properties.WithKey(domainKey, databaseKey): Specifies the primary key mappingWhen to use Translation:
Username → Nome)Use different classes for domain and database, with translation configured:
// Domain Model - Clean, business-focused
public class MappingUser
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public List<Group> Groups { get; set; }
}
// Database Model - Maps to actual DB schema
public class User
{
public int Identificativo { get; set; } // Different naming
public string Nome { get; set; }
public string Cognome { get; set; }
public string IndirizzoElettronico { get; set; }
public List<Gruppo> IdGruppos { get; set; } // Different structure
}
// Configuration
services.AddRepository<MappingUser, int>(builder =>
{
builder.WithEntityFramework<MappingUser, int, User, SampleContext>(
t =>
{
t.DbSet = x => x.Users;
t.References = x => x.Include(x => x.IdGruppos);
});
// Map domain model properties to database model properties
builder.Translate<User>()
.With(x => x.Username, x => x.Nome)
.With(x => x.Email, x => x.IndirizzoElettronico)
.With(x => x.Groups, x => x.IdGruppos)
.With(x => x.Id, x => x.Identificativo)
.WithKey(x => x, x => x.Identificativo);
});
Benefits:
Use the same class for both domain and database:
// Single Model - Used for both domain and database
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public List<Group> Groups { get; set; }
}
// Configuration - No translation needed!
services.AddRepository<User, int>(builder =>
{
builder.WithEntityFramework<User, int, User, SampleContext>(
t =>
{
t.DbSet = x => x.Users;
t.References = x => x.Include(x => x.Groups);
});
// No Translate() call needed when domain model = database model
});
Benefits:
Trade-offs:
services.AddDbContext<SampleContext>(options =>
{
options.UseSqlServer(configuration["ConnectionString:Database"]);
}, ServiceLifetime.Scoped);
services.AddRepository<MappingUser, int>(builder =>
{
// Step 1: Configure Entity Framework
builder.WithEntityFramework<MappingUser, int, User, SampleContext>(
t =>
{
t.DbSet = x => x.Users;
t.References = x => x.Include(x => x.IdGruppos);
});
// Step 2: Configure Translation (if using separate models)
builder.Translate<User>()
.With(x => x.Username, x => x.Nome)
.With(x => x.Username, x => x.Cognome)
.With(x => x.Email, x => x.IndirizzoElettronico)
.With(x => x.Groups, x => x.IdGruppos)
.With(x => x.Id, x => x.Identificativo)
.WithKey(x => x, x => x.Identificativo);
// Step 3: Add Business Logic Interceptors
// See: RepositoryFramework.Abstractions > Business > IRepositoryBusiness
builder.AddBusiness()
.AddBusinessBeforeInsert<MappingUserBeforeInsertBusiness>()
.AddBusinessBeforeInsert<MappingUserBeforeInsertBusiness2>();
});
Now available in Dependency Injection:
public class UserService(IRepository<MappingUser, int> repository)
{
public async Task CreateUserAsync(MappingUser user)
{
// Business interceptors run here automatically
await repository.InsertAsync(user);
}
}
Business interceptors run at specific points in the repository lifecycle:
See the IRepositoryBusiness interface in RepositoryFramework.Abstractions for detailed documentation on implementing custom business logic.
Key Takeaway:
Once your repository is configured, you can automatically expose it as a fully-featured REST API without writing endpoint code:
var builder = WebApplicationBuilder.CreateBuilder(args);
// ... your repository configuration ...
builder.Services.AddApiFromRepositoryFramework()
.WithDescriptiveName("Repository API")
.WithPath("/api")
.WithSwagger()
.WithVersion("v1")
.WithDocumentation()
.WithDefaultCors("http://example.com");
var app = builder.Build();
app.UseApiFromRepositoryFramework()
.WithNoAuthorization();
app.Run();
This automatically generates REST endpoints for all your repositories:
GET /api/mappinguser - List all usersGET /api/mappinguser/{id} - Get user by IDPOST /api/mappinguser - Create userPUT /api/mappinguser/{id} - Update userDELETE /api/mappinguser/{id} - Delete userYour business interceptors run automatically for each operation!
See Repository API Server Documentation for advanced configuration.