### Complete DI Container Setup Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/07-di-container.md This example demonstrates the complete setup of DotEmilu interceptors and DbContext within a .NET application's dependency injection container. It includes adding interceptors, configuring the DbContext with SQL Server and interceptors, and setting up Swagger. ```csharp var builder = WebApplication.CreateBuilder(args); // 1. Add DotEmilu interceptors builder.Services .AddSoftDeleteInterceptor() .AddAuditableEntityInterceptors(Assembly.GetExecutingAssembly()); // 2. Add DbContext with interceptors builder.Services.AddDbContext((sp, options) => { options .UseSqlServer(builder.Configuration.GetSection("DefaultConnection").Get()) .AddInterceptors(sp.GetServices()); }); // 3. Add other services builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.Run(); ``` -------------------------------- ### Complete DbContext Configuration Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/08-model-builder-extensions.md Example of configuring a DbContext using assembly-based Model Builder extensions for automatic entity configuration discovery. ```csharp public class MyDbContext : DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Apply base entity configurations modelBuilder.ApplyBaseAuditableEntityConfiguration( Assembly.GetExecutingAssembly(), userKeyConfigurations: new() { { typeof(Guid), MappingStrategy.Tpc } }) .ApplyBaseEntityConfiguration( Assembly.GetExecutingAssembly(), keyTypeConfigurations: new() { { typeof(Guid), (MappingStrategy.Tpc, enableRowVersion: true) } }) .ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } } ``` -------------------------------- ### Usage Example with DbContext and Interceptors Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/07-di-container.md Demonstrates how to configure DbContext with SQL Server and add the discovered auditable entity interceptors. This setup requires the assembly to contain at least one ContextUser implementation that inherits from IContextUser. ```csharp // Assembly contains ContextUser : IContextUser builder.Services .AddAuditableEntityInterceptors(Assembly.GetExecutingAssembly()) .AddDbContext((sp, options) => { options.UseSqlServer(connectionString) .AddInterceptors(sp.GetServices()); }); ``` -------------------------------- ### Usage Examples in Code Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/README.md Demonstrates how to perform soft delete operations, how auditable entity fields are auto-populated, and how to use the pagination feature. ```csharp // Soft delete - record marked as deleted but not removed context.Remove(person); await context.SaveChangesAsync(); // person.IsDeleted = true (in database) // Auditable entity - fields auto-populated var song = new Song { Name = "Thriller" }; context.Add(song); await context.SaveChangesAsync(); // Created = now, CreatedBy = currentUserId, etc. (auto-set) // Pagination var page = await context.Songs .OrderBy(s => s.Created) .AsPaginatedListAsync(pageNumber: 1, pageSize: 10, cancellationToken); ``` -------------------------------- ### Usage Example: AddSoftDeleteInterceptor Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/07-di-container.md Demonstrates how to register the SoftDeleteInterceptor and add it to the DbContext configuration in a web application. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services .AddSoftDeleteInterceptor() .AddDbContext((sp, options) => { options.UseSqlServer(connectionString) .AddInterceptors(sp.GetServices()); }); ``` -------------------------------- ### ApplyBaseEntityConfiguration Usage Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/08-model-builder-extensions.md Demonstrates how to apply base entity configuration to entities with different key types, specifying mapping strategy and row versioning. ```csharp protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Apply to all Guid-keyed base entities modelBuilder.ApplyBaseEntityConfiguration( strategy: MappingStrategy.Tpc, enableRowVersion: true); // Apply to other key types modelBuilder.ApplyBaseEntityConfiguration( strategy: MappingStrategy.Tph); } ``` -------------------------------- ### Time Source Usage Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/05-interceptors.md Demonstrates how to obtain the current UTC time using the injected TimeProvider within the interceptor. ```csharp var utcNow = timeProvider.GetUtcNow(); ``` -------------------------------- ### Repository Pattern Implementation with IUnitOfWork Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/13-unit-of-work.md Shows how to implement a generic repository pattern that leverages the IUnitOfWork interface for data access and change persistence. Includes usage example. ```csharp public interface IRepository { IUnitOfWork UnitOfWork { get; } Task GetByIdAsync(Guid id, CancellationToken ct); void Add(T entity); void Update(T entity); void Remove(T entity); } public class PersonRepository : IRepository { private readonly IUnitOfWork _unitOfWork; public IUnitOfWork UnitOfWork => _unitOfWork; public PersonRepository(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public async Task GetByIdAsync(Guid id, CancellationToken ct) { return await ((DbContext)_unitOfWork) .Set() .FirstOrDefaultAsync(p => p.Id == id, ct); } public void Add(Person entity) => ((DbContext)_unitOfWork).Add(entity); public void Update(Person entity) => ((DbContext)_unitOfWork).Update(entity); public void Remove(Person entity) => ((DbContext)_unitOfWork).Remove(entity); } // Usage public class PersonHandler { private readonly IRepository _repository; public PersonHandler(IRepository repository) { _repository = repository; } public async Task UpdateAsync(Guid id, string newName, CancellationToken ct) { var person = await _repository.GetByIdAsync(id, ct); if (person != null) { person.Name = newName; _repository.Update(person); await _repository.UnitOfWork.SaveChangesAsync(ct); } } } ``` -------------------------------- ### ApplyBaseEntityConfiguration Usage Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/14-types.md Example of applying base entity configuration with key type configurations, specifying mapping strategies and row version enablement. Requires Assembly and keyTypeConfigurations. ```csharp modelBuilder.ApplyBaseEntityConfiguration( Assembly.GetExecutingAssembly(), keyTypeConfigurations: new() { { typeof(Guid), (MappingStrategy.Tpc, enableRowVersion: false) }, { typeof(int), (MappingStrategy.Tph, enableRowVersion: false) } }); ``` -------------------------------- ### ApplyBaseAuditableEntityConfiguration Usage Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/14-types.md Example of applying base auditable entity configuration with user key configurations and mapping strategies. Requires Assembly and userKeyConfigurations. ```csharp modelBuilder.ApplyBaseAuditableEntityConfiguration( Assembly.GetExecutingAssembly(), userKeyConfigurations: new() { { typeof(Guid), MappingStrategy.Tpt } }); ``` -------------------------------- ### Soft Delete Behavior Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/05-interceptors.md Illustrates the behavior of the SoftDeleteInterceptor before and after SaveChangesAsync, showing how entities are marked as deleted in memory and the database. ```csharp // Before interception var person = await context.Persons.FirstAsync(); context.Remove(person); await context.SaveChangesAsync(); // Interceptor fires // After save: // Database: Person record still exists, IsDeleted = true // Memory: person.IsDeleted = true // Querying: var active = await context.Persons.ToListAsync(); // Result: Does NOT include soft-deleted persons (query filter applied) var deleted = await context.Persons.IgnoreQueryFilters().Where(p => p.IsDeleted).ToListAsync(); // Result: Includes soft-deleted persons (query filter bypassed) ``` -------------------------------- ### HttpContext-Based User Resolution Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/04-context-user.md An implementation of IContextUser that directly parses the user identifier from HttpContext claims. Assumes the claim is always present and valid. ```csharp public class HttpContextUser : IContextUser { private readonly IHttpContextAccessor _httpContextAccessor; public HttpContextUser(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor; public Guid Id => Guid.Parse( _httpContextAccessor.HttpContext!.User .FindFirst(ClaimTypes.NameIdentifier)!.Value); } ``` -------------------------------- ### Entity Implementation and DbContext Configuration Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/02-base-entities.md Example of inheriting from `BaseEntity` and configuring Entity Framework Core to use soft delete interceptors and base entity configurations. ```csharp public class Person : BaseEntity { public string Name { get; set; } = null!; public string? LastName { get; set; } } // In DbContext configuration: protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyBaseEntityConfiguration(Assembly.GetExecutingAssembly()); modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } // Register interceptor: builder.Services.AddSoftDeleteInterceptor(); ``` -------------------------------- ### Applying Base Entity Configuration in OnModelCreating Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/06-configurations.md Example of how to apply the BaseEntityConfiguration automatically using an extension method in the OnModelCreating method of your DbContext. It demonstrates configuring different key types with varying options. ```csharp // Applied automatically via extension method: protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyBaseEntityConfiguration( Assembly.GetExecutingAssembly(), keyTypeConfigurations: new() { { typeof(Guid), (MappingStrategy.Tpc, enableRowVersion: true) }, { typeof(int), (MappingStrategy.Tph, enableRowVersion: false) } }); } ``` -------------------------------- ### Create Song API Request (with automatic audit) Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Example `curl` command to create a song. The API automatically populates audit fields like 'created', 'createdBy', etc. ```bash curl -X POST https://localhost:7001/api/songs \ -H "Content-Type: application/json" \ -d '{ "name": "Thriller", "artist": "Michael Jackson", "genre": 1 }' ``` -------------------------------- ### ApplyBaseEntityConfiguration with Custom Key Type Configurations Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/08-model-builder-extensions.md Shows how to provide custom configurations for specific key types (e.g., Guid, int) when applying base entity configurations, overriding default mapping strategies and row version settings. ```csharp protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyBaseEntityConfiguration( Assembly.GetExecutingAssembly(), keyTypeConfigurations: new() { { typeof(Guid), (MappingStrategy.Tpc, enableRowVersion: true) }, { typeof(int), (MappingStrategy.Tph, enableRowVersion: false) } }); } ``` -------------------------------- ### Usage Example for Auditable Entity Interceptor Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/07-di-container.md Demonstrates how to register the AuditableEntityInterceptor and configure DbContext with it. The ContextUser class provides the current user's ID. ```csharp public class ContextUser : IContextUser { private readonly IHttpContextAccessor _httpContextAccessor; public ContextUser(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor; public Guid Id => Guid.Parse( _httpContextAccessor.HttpContext!.User .FindFirst(ClaimTypes.NameIdentifier)!.Value); } // Registration: builder.Services .AddAuditableEntityInterceptor() .AddDbContext((sp, options) => { options.UseSqlServer(connectionString) .AddInterceptors(sp.GetServices()); }); ``` -------------------------------- ### Pagination Navigation Logic Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/11-query-extensions.md Provides an example of how to check for the presence of previous and next pages using the `HasPreviousPage` and `HasNextPage` properties of the `PaginatedList` object and construct navigation URLs. ```csharp var page = await context.Persons.AsPaginatedListAsync(currentPage, 10, ct); // Check for navigation if (page.HasPreviousPage) prevPageUrl = $"/api/persons?page={page.PageNumber - 1}&size=10"; if (page.HasNextPage) nextPageUrl = $"/api/persons?page={page.PageNumber + 1}&size=10"; // Output Console.WriteLine($"Showing page {page.PageNumber}/{page.TotalPages}"); Console.WriteLine($"Items: {page.Items.Count} / {page.TotalCount}"); ``` -------------------------------- ### SqlServerContext Implementation of IUnitOfWork Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/13-unit-of-work.md An example implementation of the IUnitOfWork interface using EF Core's DbContext for SQL Server. It exposes DbSets and applies entity configurations. ```csharp public class SqlServerContext : DbContext, IUnitOfWork { public SqlServerContext(DbContextOptions options) : base(options) { } // IUnitOfWork implementation DatabaseFacade IUnitOfWork.Database => base.Database; Task IUnitOfWork.SaveChangesAsync(CancellationToken cancellationToken) => base.SaveChangesAsync(cancellationToken); // DbSets (exposed separately, not on interface) public DbSet Persons => Set(); public DbSet Songs => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder .ApplyBaseAuditableEntityConfiguration(Assembly.GetExecutingAssembly()) .ApplyBaseEntityConfiguration(Assembly.GetExecutingAssembly()) .ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } } ``` -------------------------------- ### Auditable Entity Usage Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/03-auditable-entities.md Demonstrates how to define a concrete entity inheriting from BaseAuditableEntity and configure the DbContext and services to enable auditable entity features. ```csharp public class Song : BaseAuditableEntity { public required string Name { get; set; } } ``` ```csharp // In DbContext configuration: protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyBaseAuditableEntityConfiguration(Assembly.GetExecutingAssembly()); modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } ``` ```csharp // Register interceptor: builder.Services.AddAuditableEntityInterceptors(Assembly.GetExecutingAssembly()); ``` -------------------------------- ### Get Songs Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a paginated list of songs, ordered by creation date in descending order. ```APIDOC ## GET /api/songs ### Description Retrieves a paginated list of songs. ### Method GET ### Endpoint /api/songs #### Query Parameters - **pageNumber** (int) - Optional - The page number to retrieve. - **pageSize** (int) - Optional - The number of items per page. ### Response #### Success Response (200) - **data** (array) - The list of songs. - **pagination** (object) - Pagination details. - **pageNumber** (int) - The current page number. - **pageSize** (int) - The number of items per page. - **totalCount** (int) - The total number of items. - **totalPages** (int) - The total number of pages. - **hasPreviousPage** (bool) - Indicates if there is a previous page. - **hasNextPage** (bool) - Indicates if there is a next page. ### Response Example { "data": [ { "id": "guid", "name": "string", "artist": "string", "genre": "string" } ], "pagination": { "pageNumber": 1, "pageSize": 10, "totalCount": 50, "totalPages": 5, "hasPreviousPage": false, "hasNextPage": true } } ``` -------------------------------- ### Usage Example for HasShortConversion Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/10-property-builder-extensions.md Demonstrates how to apply the HasShortConversion extension method to boolean properties within an EntityTypeConfiguration. It also shows setting default values for 'IsDeleted' and 'IsActive'. ```csharp public class PersonConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder .Property(p => p.IsDeleted) .HasShortConversion() .HasDefaultValue(0); builder .Property(p => p.IsActive) .HasShortConversion() .HasDefaultValue(1); } } ``` -------------------------------- ### Dependency-Injected User Service Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/04-context-user.md An implementation of IContextUser that delegates user ID retrieval to an injected IUserService. Requires IUserService to be available in the DI container. ```csharp public class ServiceBasedContextUser : IContextUser { private readonly IUserService _userService; public ServiceBasedContextUser(IUserService userService) => _userService = userService; public int Id => _userService.GetCurrentUserId(); } ``` -------------------------------- ### Pagination Logic for Navigation Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/12-paginated-list.md Provides examples of logic used to determine navigation controls for a paginated list. It shows how to check for the existence of next and previous pages and how to generate a range of page numbers for UI display. ```csharp // Determine next page if (currentPage.HasNextPage) nextUrl = BuildUrl(pageNumber: currentPage.PageNumber + 1); // Determine previous page if (currentPage.HasPreviousPage) previousUrl = BuildUrl(pageNumber: currentPage.PageNumber - 1); // Build page range for UI var pageRange = Enumerable .Range(1, currentPage.TotalPages) .Select(p => new { PageNumber = p, IsActive = p == currentPage.PageNumber }) .ToList(); ``` -------------------------------- ### Behavior Example: Entity Operations Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/05-interceptors.md Illustrates the behavior of the AuditableEntityInterceptor during entity creation, update, and deletion (soft delete) operations, showing the state of audit fields after each operation. ```csharp // Create new entity var song = new Song { Name = "Thriller" }; context.Songs.Add(song); await context.SaveChangesAsync(); // After save (interceptor populated): // song.Created = 2024-01-15 14:30:00 UTC // song.CreatedBy = // song.LastModified = 2024-01-15 14:30:00 UTC // song.LastModifiedBy = // Update entity song.Name = "Billie Jean"; context.Update(song); await context.SaveChangesAsync(); // After update: // song.Created = unchanged // song.CreatedBy = unchanged // song.LastModified = 2024-01-15 14:35:00 UTC (new time) // song.LastModifiedBy = // Delete entity context.Remove(song); await context.SaveChangesAsync(); // After delete (soft): // song.IsDeleted = true // song.Deleted = 2024-01-15 14:40:00 UTC // song.DeletedBy = // Record still exists in database ``` -------------------------------- ### Simple ContextUser Implementation Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/04-context-user.md A basic implementation of IContextUser that retrieves the user ID from HTTP request claims. Requires IHttpContextAccessor to be registered. ```csharp public class ContextUser : IContextUser { private readonly IHttpContextAccessor _httpContextAccessor; public ContextUser(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public Guid Id { get { var userIdClaim = _httpContextAccessor.HttpContext?.User .FindFirst(ClaimTypes.NameIdentifier)?.Value; return Guid.TryParse(userIdClaim, out var userId) ? userId : Guid.Empty; } } } ``` -------------------------------- ### Returning Pagination Metadata in API Response Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/12-paginated-list.md Example of returning all necessary pagination metadata along with the data in an API response for UI navigation. ```csharp return Ok(new { data, totalPages, pageNumber, hasNextPage, hasPreviousPage }); ``` -------------------------------- ### JSON Serialization of Paginated Result Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/12-paginated-list.md Example of the JSON output for a paginated list, showing the data and metadata fields. ```json { "data": [ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" } ], "pageNumber": 1, "pageSize": 2, "totalCount": 5, "totalPages": 3, "hasPreviousPage": false, "hasNextPage": true } ``` -------------------------------- ### Create Person API Response Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Example JSON response after successfully creating a person. Includes the assigned ID and other entity properties. ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "isDeleted": false, "name": "John", "lastName": "Doe", "dateOfBirth": "1990-01-15T00:00:00" } ``` -------------------------------- ### Registering Base Configurations with EntityFrameworkCore Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/README.md Register base configurations using IEntityTypeConfiguration. Configurations can be applied by assembly or individually by type. This example shows applying configurations for BaseAuditableEntity and BaseEntity. ```csharp public class SqlServerContext(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder //.ApplyBaseAuditableEntityConfiguration(strategy: MappingStrategy.Tpt) //method 1 .ApplyBaseAuditableEntityConfiguration(Assembly.GetExecutingAssembly()) //method 2 //.ApplyBaseEntityConfiguration(strategy: MappingStrategy.Tph, enableRowVersion: true) //method 1 .ApplyBaseEntityConfiguration(Assembly.GetExecutingAssembly()) //method 2 .ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } public DbSet Persons => Set(); public DbSet Songs => Set(); } ``` -------------------------------- ### Mock Unit of Work for Testing Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/13-unit-of-work.md Implement a mock `IUnitOfWork` for unit testing to simulate database interactions without a real database. This example mocks entity tracking and `SaveChangesAsync`. ```csharp public class MockUnitOfWork : IUnitOfWork { private List _persons = new(); public DatabaseFacade Database => throw new NotImplementedException(); public Task SaveChangesAsync(CancellationToken cancellationToken) { // In-memory "save" for testing return Task.FromResult(_persons.Count); } } // Test [Fact] public async Task UpdatePersonAsync_NameChanged() { var unitOfWork = new MockUnitOfWork(); var service = new PersonService(unitOfWork); var result = await service.UpdatePersonAsync( Guid.NewGuid(), "New Name", CancellationToken.None); Assert.Equal(1, result); } ``` -------------------------------- ### Registering Interceptors with Dependency Injection Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/README.md Register custom interceptors with the dependency injection container. This example shows how to add SoftDeleteInterceptor and AuditableEntityInterceptors. ```csharp builder.Services .AddSoftDeleteInterceptor() //.AddAuditableEntityInterceptor() //method 1 .AddAuditableEntityInterceptors(Assembly.GetExecutingAssembly()) //method 2 ``` -------------------------------- ### Support for Multiple User Types Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/07-di-container.md Shows how the system automatically handles and creates separate interceptors for different user key types (e.g., Guid and int) within the same application. This requires distinct implementations of IContextUser for each type. ```csharp // ContextUser : IContextUser // SystemUser : IContextUser builder.Services.AddAuditableEntityInterceptors(Assembly.GetExecutingAssembly()); // Automatically creates both interceptors ``` -------------------------------- ### Static/Ambient Context User Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/04-context-user.md An implementation of IContextUser that retrieves the user ID from a static or ambient context. Relies on an external UserContext class. ```csharp public class AmbientContextUser : IContextUser { public long Id => UserContext.Current.Id; } ``` -------------------------------- ### Create Person API Request Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Example `curl` command to create a new person via the API. Includes the endpoint, HTTP method, content type, and JSON payload. ```bash curl -X POST https://localhost:7001/api/persons \ -H "Content-Type: application/json" \ -d '{ "name": "John", "lastName": "Doe", "dateOfBirth": "1990-01-15" }' ``` -------------------------------- ### Create Song API Response (audit fields auto-populated) Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Example JSON response after creating a song, showing auto-populated audit fields. Note the 'created', 'createdBy', 'lastModified', and 'lastModifiedBy' fields. ```json { "id": "660e8400-e29b-41d4-a716-446655440001", "isDeleted": false, "created": "2024-01-15T14:30:00-05:00", "createdBy": "550e8400-e29b-41d4-a716-446655440000", "lastModified": "2024-01-15T14:30:00-05:00", "lastModifiedBy": "550e8400-e29b-41d4-a716-446655440000", "deleted": null, "deletedBy": null, "name": "Thriller", "artist": "Michael Jackson", "genre": 1 } ``` -------------------------------- ### Error Handling for Duplicate User Key Types Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/07-di-container.md Illustrates a scenario that throws an ArgumentException due to multiple implementations of IContextUser for the same key type (e.g., Guid). To resolve this, ensure only one implementation exists per user key type within the scanned assembly. ```csharp // Example that throws: // ContextUser1 : IContextUser // ContextUser2 : IContextUser // Error! // Exception message: // "There are multiple implementations of IContextUser // for the following key types: Guid" ``` -------------------------------- ### Working with Unit of Work Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/README.md Inherit entity interfaces and use EF Core features in a decoupled manner with the Unit of Work pattern. This example shows updating and saving a Person entity. ```csharp public interface ICommands : IUnitOfWork { DbSet Persons { get; } } public class PersonHandler(ICommands commands) { public async Task WorkAsync() { var person = await commands.Persons.FirstOrDefaultAsync(); person!.Name = "NEW NAME!"; await commands.SaveChangesAsync(); } } ``` -------------------------------- ### Get Paginated Persons API Response Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Example JSON response for a paginated persons request. Includes the data array and pagination metadata. ```json { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "isDeleted": false, "name": "John", "lastName": "Doe", "dateOfBirth": "1990-01-15T00:00:00" } ], "pagination": { "pageNumber": 1, "pageSize": 10, "totalCount": 1, "totalPages": 1, "hasPreviousPage": false, "hasNextPage": false } } ``` -------------------------------- ### Basic Usage of ApplyBaseEntityConfiguration Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/08-model-builder-extensions.md Demonstrates how to use the ApplyBaseEntityConfiguration extension to automatically discover and apply default configurations for entities within the executing assembly. ```csharp protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Auto-discover and apply with defaults modelBuilder.ApplyBaseEntityConfiguration(Assembly.GetExecutingAssembly()); } ``` -------------------------------- ### Get Paginated Persons API Request Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Example `curl` command to retrieve a paginated list of persons. Demonstrates using query parameters for page number and size. ```bash curl https://localhost:7001/api/persons?pageNumber=1&pageSize=10 ``` -------------------------------- ### Get Song Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a specific song by its ID. ```APIDOC ## GET /api/songs/{id} ### Description Retrieves a specific song by its ID. ### Method GET ### Endpoint /api/songs/{id} #### Path Parameters - **id** (guid) - Required - The ID of the song to retrieve. ### Response #### Success Response (200) - **id** (guid) - The ID of the song. - **name** (string) - The name of the song. - **artist** (string) - The artist of the song. - **genre** (string) - The genre of the song. #### Error Response (404) - Not Found ### Response Example { "id": "guid", "name": "string", "artist": "string", "genre": "string" } ``` -------------------------------- ### Entity Configuration with Mapping Strategy Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/14-types.md Demonstrates how to configure a specific mapping strategy for an entity within its configuration class. Requires IEntityTypeConfiguration and EntityTypeBuilder. ```csharp public class PersonConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ApplyMappingStrategy(MappingStrategy.Tph); // ... other configuration } } ``` -------------------------------- ### Get Person Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a specific person by their ID. ```APIDOC ## GET /api/persons/{id} ### Description Retrieves a specific person by their ID. ### Method GET ### Endpoint /api/persons/{id} #### Path Parameters - **id** (guid) - Required - The ID of the person to retrieve. ### Response #### Success Response (200) - **id** (guid) - The ID of the person. - **name** (string) - The name of the person. - **lastName** (string) - The last name of the person. - **dateOfBirth** (datetime) - The date of birth of the person. #### Error Response (404) - Not Found ### Response Example { "id": "guid", "name": "string", "lastName": "string", "dateOfBirth": "datetime" } ``` -------------------------------- ### Creating an Empty Paginated List Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/12-paginated-list.md Demonstrates how to create an empty `PaginatedList` instance. Useful for scenarios with no results. ```csharp var empty = new PaginatedList( items: new List(), count: 0, pageNumber: 1, pageSize: 10); // Items.Count = 0 // PageNumber = 1 // TotalPages = 0 // TotalCount = 0 // HasPreviousPage = false // HasNextPage = false ``` -------------------------------- ### Get Persons Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a paginated list of persons, ordered by creation date in descending order. ```APIDOC ## GET /api/persons ### Description Retrieves a paginated list of persons. ### Method GET ### Endpoint /api/persons #### Query Parameters - **pageNumber** (int) - Optional - The page number to retrieve. - **pageSize** (int) - Optional - The number of items per page. ### Response #### Success Response (200) - **data** (array) - The list of persons. - **pagination** (object) - Pagination details. - **pageNumber** (int) - The current page number. - **pageSize** (int) - The number of items per page. - **totalCount** (int) - The total number of items. - **totalPages** (int) - The total number of pages. - **hasPreviousPage** (bool) - Indicates if there is a previous page. - **hasNextPage** (bool) - Indicates if there is a next page. ### Response Example { "data": [ { "id": "guid", "name": "string", "lastName": "string", "dateOfBirth": "datetime" } ], "pagination": { "pageNumber": 1, "pageSize": 10, "totalCount": 50, "totalPages": 5, "hasPreviousPage": false, "hasNextPage": true } } ``` -------------------------------- ### Get Song Endpoint Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a single song by its ID. Returns NotFound if the song does not exist. ```csharp async Task GetSong( Guid id, ApplicationDbContext context, CancellationToken cancellationToken) { var song = await context.Songs .AsNoTracking() .FirstOrDefaultAsync(s => s.Id == id, cancellationToken); return song is not null ? Results.Ok(song) : Results.NotFound(); } ``` -------------------------------- ### Get Songs Endpoint Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a paginated list of songs from the database. Supports sorting and filtering. ```csharp async Task GetSongs( ApplicationDbContext context, int pageNumber = 1, int pageSize = 10, CancellationToken cancellationToken = default) { var page = await context.Songs .OrderByDescending(s => s.Created) .AsNoTracking() .AsPaginatedListAsync(pageNumber, pageSize, cancellationToken); return Results.Ok(new { data = page.Items, pagination = new { page.PageNumber, pageSize, page.TotalCount, page.TotalPages, page.HasPreviousPage, page.HasNextPage } }); } ``` -------------------------------- ### Basic PaginatedList Construction Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/12-paginated-list.md Demonstrates how to manually construct a PaginatedList instance by first retrieving a subset of items using LINQ's Skip and Take, and then passing these, along with total count and pagination details, to the constructor. ```csharp var allItems = new[] { "A", "B", "C", "D", "E" }; var totalCount = allItems.Length; var pageSize = 2; var pageNumber = 1; var pageItems = allItems .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToList(); var paginated = new PaginatedList( items: pageItems, count: totalCount, pageNumber: pageNumber, pageSize: pageSize); // paginated.Items = ["A", "B"] // paginated.PageNumber = 1 // paginated.TotalPages = 3 // paginated.TotalCount = 5 // paginated.HasNextPage = true // paginated.HasPreviousPage = false ``` -------------------------------- ### Get Person Endpoint Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a single person by their ID. Returns NotFound if the person does not exist. ```csharp async Task GetPerson( Guid id, ApplicationDbContext context, CancellationToken cancellationToken) { var person = await context.Persons .AsNoTracking() .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); return person is not null ? Results.Ok(person) : Results.NotFound(); } ``` -------------------------------- ### Get Persons Endpoint Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a paginated list of persons from the database. Supports sorting and filtering. ```csharp async Task GetPersons( ApplicationDbContext context, int pageNumber = 1, int pageSize = 10, CancellationToken cancellationToken = default) { var page = await context.Persons .OrderByDescending(p => p.CreatedAt) .AsNoTracking() .AsPaginatedListAsync(pageNumber, pageSize, cancellationToken); return Results.Ok(new { data = page.Items, pagination = new { page.PageNumber, pageSize, page.TotalCount, page.TotalPages, page.HasPreviousPage, page.HasNextPage } }); } ``` -------------------------------- ### Get Paginated Persons Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Retrieves a paginated list of persons. Allows specifying page number and page size. ```APIDOC ## GET /api/persons ### Description Retrieves a paginated list of persons. ### Method GET ### Endpoint /api/persons ### Query Parameters - **pageNumber** (integer) - Optional - The page number to retrieve. - **pageSize** (integer) - Optional - The number of items per page. ### Response #### Success Response (200 OK) - **data** (array) - An array of person objects. - **id** (string) - The unique identifier for the person. - **isDeleted** (boolean) - Indicates if the record is soft-deleted. - **name** (string) - The first name of the person. - **lastName** (string) - The last name of the person. - **dateOfBirth** (string) - The date of birth in ISO 8601 format. - **pagination** (object) - Pagination details. - **pageNumber** (integer) - The current page number. - **pageSize** (integer) - The number of items per page. - **totalCount** (integer) - The total number of items available. - **totalPages** (integer) - The total number of pages. - **hasPreviousPage** (boolean) - Indicates if there is a previous page. - **hasNextPage** (boolean) - Indicates if there is a next page. ### Response Example ```json { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "isDeleted": false, "name": "John", "lastName": "Doe", "dateOfBirth": "1990-01-15T00:00:00" } ], "pagination": { "pageNumber": 1, "pageSize": 10, "totalCount": 1, "totalPages": 1, "hasPreviousPage": false, "hasNextPage": false } } ``` ``` -------------------------------- ### Registering SoftDeleteInterceptor Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/05-interceptors.md Demonstrates how to register the SoftDeleteInterceptor with the DI container and add it to DbContext options. ```csharp builder.Services.AddSoftDeleteInterceptor(); builder.Services.AddDbContext((sp, options) => { options.UseSqlServer(connectionString) .AddInterceptors(sp.GetServices()); }); ``` -------------------------------- ### Database Configuration (appsettings.json) Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Specifies the connection string for the database and logging level. Ensure the 'Default' connection string points to your MusicDb instance. ```json { "ConnectionStrings": { "Default": "Server=(localdb)\mssqllocaldb;Database=MusicDb;Trusted_Connection=true;" }, "Logging": { "LogLevel": { "Default": "Information" } }, "AllowedHosts": "*" } ``` -------------------------------- ### Apply Base Auditable Entity Configuration (Generic) Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/08-model-builder-extensions.md Applies auditable configuration to all entities implementing IBaseAuditableEntity. Specify the user key type and optionally the mapping strategy. ```csharp public static ModelBuilder ApplyBaseAuditableEntityConfiguration( this ModelBuilder modelBuilder, MappingStrategy strategy = MappingStrategy.Tpc) where TUserKey : struct ``` ```csharp protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyBaseAuditableEntityConfiguration( strategy: MappingStrategy.Tpc); } ``` -------------------------------- ### Soft Delete API Request Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Example `curl` command to perform a soft delete on a song. This marks the record as deleted without removing it from the database. ```bash curl -X DELETE https://localhost:7001/api/songs/660e8400-e29b-41d4-a716-446655440001 ``` -------------------------------- ### DI Registration for Unit of Work and Interceptors Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/13-unit-of-work.md Register the `DbContext` with SQL Server support and add `ISaveChangesInterceptor`s. Then, register the `DbContext` as the `IUnitOfWork` implementation using dependency injection. ```csharp builder.Services.AddDbContext((sp, options) => { options.UseSqlServer(connectionString) .AddInterceptors(sp.GetServices()); }); // Register as IUnitOfWork builder.Services.AddScoped(sp => sp.GetRequiredService()); ``` -------------------------------- ### Accessing Database Facade and Transactions Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/13-unit-of-work.md Use the `Database` property to access the underlying connection, begin and manage transactions, or execute raw SQL commands. Ensure proper rollback on exceptions. ```csharp // Get underlying connection var connection = unitOfWork.Database.GetDbConnection(); // Begin transaction var transaction = await unitOfWork.Database.BeginTransactionAsync(ct); try { // Operations... await unitOfWork.SaveChangesAsync(ct); await transaction.CommitAsync(ct); } catch { await transaction.RollbackAsync(ct); throw; } // Check connection state if (unitOfWork.Database.GetDbConnection().State == ConnectionState.Open) { // Connected... } // Execute raw SQL var count = await unitOfWork.Database .ExecuteSqlInterpolatedAsync($"TRUNCATE TABLE persons", ct); ``` -------------------------------- ### Get Paginated Results Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/00-table-of-contents.md Retrieve paginated results from a DbSet using the AsPaginatedListAsync extension method. Specify the desired page number and page size. ```csharp var page = await context.Songs .OrderBy(s => s.Created) .AsPaginatedListAsync(pageNumber: 1, pageSize: 10, cancellationToken); ``` -------------------------------- ### Basic Usage of AsPaginatedListAsync Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/11-query-extensions.md Demonstrates how to use `AsPaginatedListAsync` to retrieve a paginated list of persons, ordered by name. Includes printing pagination metadata and item names. ```csharp var query = context.Persons .OrderBy(p => p.Name); var page = await query.AsPaginatedListAsync( pageNumber: 1, pageSize: 10, cancellationToken: cancellationToken); Console.WriteLine($"Page {page.PageNumber} of {page.TotalPages}"); Console.WriteLine($"Total records: {page.TotalCount}"); foreach (var person in page.Items) { Console.WriteLine(person.Name); } ``` -------------------------------- ### Create Song Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Creates a new song with the provided details. ```APIDOC ## POST /api/songs ### Description Creates a new song. ### Method POST ### Endpoint /api/songs #### Request Body - **name** (string) - Required - The name of the song. - **artist** (string) - Required - The artist of the song. - **genre** (string) - Required - The genre of the song. ### Request Example { "name": "Bohemian Rhapsody", "artist": "Queen", "genre": "Rock" } ### Response #### Success Response (201) - **id** (guid) - The ID of the newly created song. - **name** (string) - The name of the song. - **artist** (string) - The artist of the song. - **genre** (string) - The genre of the song. ### Response Example { "id": "guid", "name": "Bohemian Rhapsody", "artist": "Queen", "genre": "Rock" } ``` -------------------------------- ### Implement Context User Information Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/README.md Implement the IContextUser interface to provide user context information, such as user ID, for auditing purposes. Dependencies can be injected as needed. ```csharp //Feel free to inject your necessary dependencies to provide user information. public class ContextUser : IContextUser { public Guid Id => Guid.Parse("66931274-341d-41a0-0ec4-08dd5e9e0461"); } ``` -------------------------------- ### SaveChangesAsync Behavior and Example Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/13-unit-of-work.md Calling `SaveChangesAsync` triggers interceptors, change tracking, and persists all changes atomically within a database transaction. It returns the number of affected entities. ```csharp var person = new Person { Name = "John" }; ((DbContext)_unitOfWork).Add(person); int affected = await _unitOfWork.SaveChangesAsync(ct); // Returns: 1 (one entity inserted) ``` -------------------------------- ### Service Registration in Program.cs Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/15-complete-example.md Registers DotEmilu interceptors, the ApplicationDbContext with SQL Server support and interceptors, and the HttpContextAccessor. It also sets up basic API endpoints for Persons and Songs. ```csharp var builder = WebApplication.CreateBuilder(args); // Add DotEmilu interceptors builder.Services .AddSoftDeleteInterceptor() .AddAuditableEntityInterceptors(Assembly.GetExecutingAssembly()); // Add DbContext builder.Services.AddDbContext((sp, options) => { options .UseSqlServer(builder.Configuration.GetConnectionString("Default")) .EnableDetailedErrors() .AddInterceptors(sp.GetServices()); }); // Register context user builder.Services.AddScoped(); // Add API services builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // Map endpoints var personGroup = app.MapGroup("/api/persons") .WithTags("Persons") .WithOpenApi(); personGroup.MapGet("/", GetPersons); personGroup.MapGet("/{id}", GetPerson); personGroup.MapPost("/", CreatePerson); personGroup.MapPut("/{id}", UpdatePerson); personGroup.MapDelete("/{id}", DeletePerson); var songGroup = app.MapGroup("/api/songs") .WithTags("Songs") .WithOpenApi(); songGroup.MapGet("/", GetSongs); songGroup.MapGet("/{id}", GetSong); songGroup.MapPost("/", CreateSong); songGroup.MapPut("/{id}", UpdateSong); songGroup.MapDelete("/{id}", DeleteSong); app.Run(); ``` -------------------------------- ### Using AsPaginatedListAsync Extension Method Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/12-paginated-list.md Shows the typical usage of the AsPaginatedListAsync extension method, which simplifies creating a PaginatedList directly from an IQueryable source. It handles ordering, skipping, taking, and constructing the paginated result in a single asynchronous operation. ```csharp var page = await context.Persons .OrderBy(p => p.Name) .AsPaginatedListAsync(pageNumber: 2, pageSize: 10, cancellationToken: ct); // Access properties Console.WriteLine($"Page {page.PageNumber} of {page.TotalPages}"); Console.WriteLine($"Total records: {page.TotalCount}"); Console.WriteLine($"Items on this page: {page.Items.Count}"); foreach (var person in page.Items) { Console.WriteLine(person.Name); } ``` -------------------------------- ### ApplyBaseEntityConfiguration Method Signature Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/08-model-builder-extensions.md Defines the generic method for applying base entity configuration. It accepts a mapping strategy and an option to enable row versioning. ```csharp public static ModelBuilder ApplyBaseEntityConfiguration( this ModelBuilder modelBuilder, MappingStrategy strategy = MappingStrategy.Tpc, bool enableRowVersion = false) where TKey : struct ``` -------------------------------- ### Applying Default and Max Page Size Constraints Source: https://github.com/renzojared/dotemilu.entityframeworkcore/blob/main/_autodocs/12-paginated-list.md Applies default and maximum constraints to the user-provided page size to prevent excessively large or small pages. ```csharp const int DefaultPageSize = 20; const int MaxPageSize = 100; var size = Math.Min(userPageSize ?? DefaultPageSize, MaxPageSize); ```