36 Commits

Author SHA1 Message Date
Developer 02
fb9449d701 chore: upradge versions 2025-04-22 23:05:04 +02:00
Developer 02
72f735272f feat(Repository): Add and implement ReadOrDefaultAsync method.
- add read method can map for each
2025-04-22 23:04:11 +02:00
Developer 02
304f5b7b4c chore: unnötiges Projektverzeichnis entfernt 2025-04-22 20:47:47 +02:00
Developer 02
0238310290 chore(Abstractions.Security): Hochgestuft auf 1.0.1 2025-04-22 20:46:09 +02:00
Developer 02
3ac0501231 chore: covert from debug to release to publish package 2025-04-22 18:11:34 +02:00
Developer 02
db8a560805 chore(Infrastructure.AutoMapper): Konfiguration für das Packen 2025-04-22 18:07:40 +02:00
Developer 02
e67361bfe1 chore(DigitalData.Core.Infrastructure): Hochgestuft auf 2.0.2 2025-04-22 18:00:29 +02:00
Developer 02
91594e80bf refactor(EntityConfigurationOptions): aktualisiert, um IServiceCollection mit Callback zu konfigurieren 2025-04-22 17:58:49 +02:00
Developer 02
8d98159ba8 fix: Korrektur der Update- und Löschlogik in DbRepository zur Vermeidung von Laufzeitproblemen 2025-04-22 17:33:55 +02:00
Developer 02
f1f5b9e16d refactor(DbRepositoryTests): Update AddDbRepository configuration 2025-04-22 16:32:17 +02:00
Developer 02
3955dede16 feat(EntityConfigurationOptions): Erstellt, um Entitäten wie Mapper konfigurieren zu können 2025-04-22 16:21:57 +02:00
Developer 02
65e834784a feat(EntityAutoMapper): Erstellt mit der Konfiguration der Dependency Injection. 2025-04-22 15:15:52 +02:00
Developer 02
3c1bbc1151 feat(Repository): CreateAsync-Methoden für DTO wurden in Erweiterungsmethoden konvertiert 2025-04-22 11:21:21 +02:00
Developer 02
5465fe5b49 feat(IEntityMapper): Erstellt, um Mapper zu abstrahieren.
- Integriert in IRepository und Repository
2025-04-22 11:10:55 +02:00
Developer 02
85787e7054 feat(DbRepositoryTests): ReadAsync_ShouldReturnUpdated und ReadAsync_ShouldNotReturnDeleted Tests 2025-04-22 10:09:47 +02:00
Developer 02
c955220310 feat (Mapping): Porfile hinzufügen 2025-04-22 09:53:20 +02:00
Developer 02
7d2098092a Refactor user DTOs and update faker method
- Aktualisiert `CreateUserDtoFaker` um `UserCreateDto` anstelle von `UserDto` zu generieren.
- Die Klasse `UserDto` wurde entfernt, da sie nicht mehr benötigt wird.
- Hinzufügen von `UserCreateDto` für die Erstellung von Benutzern, Erweiterung von `UserBase`.
- Einführung von `UserReadDto` zum Lesen von Benutzerdaten, ebenfalls eine Erweiterung von `UserBase`.
2025-04-17 17:39:48 +02:00
Developer 02
e3b9d2971b Refactor User and UserDto to inherit from UserBase
- Aktualisierte `User` und `UserDto` Klassen um von einer neuen `UserBase` Klasse zu erben.
- Verschieben der Eigenschaften `Vorname`, `Email` und `Alter` zu `UserBase`.
- Implementierung der überschriebenen Methoden `GetHashCode` und `Equals` sowohl in `User` als auch in `UserDto`, um die Eigenschaften der Basisklasse zu nutzen.
2025-04-17 17:36:07 +02:00
Developer 02
3a604ede88 Refactor user creation and retrieval in tests
Updated `DbRepositoryTests` to await `CreateAsync` directly for user creation. Changed assertion to compare the created user with the user retrieved from the repository, enhancing test accuracy.
2025-04-17 16:43:15 +02:00
Developer 02
476c86ff0a feat: Verbesserung von IRepository mit neuen asynchronen Methoden und Erweiterungen
Aktualisiert IRepository<TEntity> um UpdateAsync<TDto>, DeleteAsync<TDto> und eine neue Überladung von ReadAsync. Die frühere ReadAsync-Überladung für Core.Tests.Mock.User wurde entfernt.

Einführung einer neuen Extensions-Klasse mit Methoden für ReadFirstOrDefaultAsync, ReadFirstAsync, ReadSingleOrDefaultAsync und ReadSingleAsync unter Nutzung der aktualisierten ReadAsync-Methode.
2025-04-17 16:24:52 +02:00
Developer 02
9376fcff86 Enhance IRepository and update DbRepositoryTests
- Added ReadAsync method to IRepository for user retrieval.
- Introduced _userRepo field in DbRepositoryTests for better clarity.
- Modified Setup method to initialize _userRepo from the service provider.
- Updated CreateAsync_ShouldNotThrow to use _userRepo.
- Added ReadAsync_ShouldReturnCreated test for user retrieval validation.
2025-04-17 16:00:59 +02:00
Developer 02
06df97597e Refactor user creation tests in DbRepositoryTests
Die Testmethode `CreateAsync_ShouldPersistUser` wurde entfernt und `CreateAsync_ShouldNotThrow` mit Testfällen für die Erstellung einzelner und mehrerer Benutzer hinzugefügt. Die neue Methode prüft, dass `CreateAsync` keine Ausnahmen auslöst, während Benutzerdaten mit einem Faker erzeugt werden.
2025-04-17 15:48:06 +02:00
Developer 02
266d03e0a1 Add user creation test to DbRepositoryTests
This commit introduces a new test method `CreateAsync_ShouldPersistUser` in `DbRepositoryTests.cs`. The method tests the asynchronous creation of a user in the repository using the `Faker` library to generate user data. It asserts that no exceptions are thrown during the creation process. Additionally, the using directives have been updated to include `DigitalData.Core.Abstractions.Infrastructure`.
2025-04-17 15:31:37 +02:00
Developer 02
e752c6f6ab Add AutoMapper setup to DbRepositoryTests
This commit introduces a using directive for `System.Reflection` in `DbRepositoryTests.cs` to support reflection features. The `Setup` method is updated to include `AddAutoMapper`, ensuring AutoMapper services are registered for dependency injection in the test environment.
2025-04-17 15:17:57 +02:00
Developer 02
561a751de4 Update dependency injection and repository tests
Modified `AddDbRepository` method to allow broader entity types and registered `queryFactory` as a singleton. Added necessary using directives in `DbRepositoryTests.cs` and updated the `Setup` method to configure an in-memory database for testing with `MockDbContext` and `User` entity.
2025-04-17 15:05:23 +02:00
Developer 02
35050d65a8 Refactor Fake class and enhance MockDbContext
Updated the `Fake` class to introduce flexible methods for generating fake `User` and `UserDto` objects with optional parameters. Removed the static `UserFaker` instance and related properties.

Added a new `DbSet<User>` property to `MockDbContext` for improved management of `User` entities.
2025-04-17 14:47:37 +02:00
Developer 02
cf9041980d feat(Mock): Hinzufügen von gefälschten Daten und Benutzermodellen zum Testen
Aktualisiert `DigitalData.Core.Tests.csproj`, um `Bogus` zur Erzeugung gefälschter Daten einzuschließen. Fake.cs„ für die Erstellung von gefälschten “User"-Objekten hinzugefügt. MockDbContext" für das Testen von Datenbank-Interaktionen eingeführt. Definierte `User` und `UserDto` Klassen für die Benutzerdarstellung und Datenübertragung.
2025-04-17 14:34:00 +02:00
Developer 02
cf2ee73ca1 Update target frameworks to include .NET 9.0
Expanded the project's target frameworks to support .NET 9.0, in addition to .NET 7.0 and .NET 8.0.
2025-04-17 13:55:06 +02:00
Developer 02
52f6dc161e Add EF Core package references and DbRepositoryTests
Updated `DigitalData.Core.Tests.csproj` to include conditional
package references for Entity Framework Core and its InMemory
provider for net7.0, net8.0, and net9.0.

Added `DbRepositoryTests` class with setup and teardown
methods for managing the lifecycle of a host instance.
2025-04-17 13:54:25 +02:00
Developer 02
7670898e24 Hinzufügen der Projekte "src" und "tests" zur Projektmappe
Der Projektmappendatei `DigitalData.Core.sln` wurden zwei neue Projekte mit den Namen "src" und "tests" hinzugefügt. Das Projekt „src“ hat einen eindeutigen Bezeichner `{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}`, und das Projekt „tests“ hat den Bezeichner `{EDF84A84-1E01-484E-B073-383F7139C891}`. Darüber hinaus wurden in den Lösungseigenschaften mehrere verschachtelte Projektbeziehungen eingerichtet, die diese neuen Projekte mit den bestehenden Projekten verknüpfen.
2025-04-17 13:32:15 +02:00
Developer 02
f5b202c325 Enhance IRepository and DbRepository with DTO support
Updated IRepository<TEntity> to include new generic methods for creating and reading entities from DTOs. Modified UpdateAsync to accept DTOs instead of generic types. Implemented corresponding methods in DbRepository<TDbContext, TEntity> for mapping DTOs to entities during creation and updating processes.
2025-04-17 13:30:35 +02:00
Developer 02
fffbdf752f Revert "Add mapping methods to IRepository and DbRepository"
This reverts commit 6916e169b1.
2025-04-17 13:15:22 +02:00
Developer 02
6916e169b1 Add mapping methods to IRepository and DbRepository
Updated the `IRepository<TEntity>` interface to include two new mapping methods: `Map<TSource>(TSource source)` and `Map<TDto>(TEntity source)`.

Implemented these methods in the `DbRepository<TDbContext, TEntity>` class, utilizing a `Mapper` for conversions between source types and entity types, as well as between entity types and DTOs.
2025-04-17 13:01:51 +02:00
Developer 02
0c529b199b Bump version numbers and enhance repository interfaces
- Incremented version numbers in project files for updates.
- Marked `ICRUDRepository` as obsolete; use `IRepository` instead.
- Improved parameter names and signatures in `IRepository`.
- Changed access modifiers in `DbRepository` for broader access.
- Updated `CreateAsync` and `UpdateAsync` methods for async operations.
- Implemented `ReadAsync` and `DeleteAsync` methods in `DbRepository`.
- Minor whitespace change in `.csproj` files.
- Retained package description in `DigitalData.Core.Infrastructure.csproj`.
2025-04-17 12:53:40 +02:00
Developer 02
5427a9722d Update package references and modify DbRepository access
- Added conditional package references for AutoMapper in
  `DigitalData.Core.Application.csproj` to support
  different target framework versions.

- Changed `DbRepository` class access modifier from
  `public` to `internal` to encapsulate implementation
  details within the current assembly.
2025-04-16 13:56:53 +02:00
Developer 02
bccfae59cd Update EntityFrameworkCore references for multiple frameworks
The project file `DigitalData.Core.Infrastructure.csproj` has been modified to include conditional `PackageReference` entries for different target frameworks. The previous reference to `Microsoft.EntityFrameworkCore` version `7.0.16` has been replaced with:
- version `7.0.20` for `net7.0`
- version `8.0.15` for `net8.0`
- version `9.0.4` for `net9.0`

This change ensures the project uses the appropriate version of Entity Framework Core based on the target framework.
2025-04-16 13:51:04 +02:00
25 changed files with 552 additions and 41 deletions

View File

@@ -5,9 +5,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>DigitalData.Core.Abstractions.Security</PackageId>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Version>1.0.1</Version>
<AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.1</FileVersion>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>Digital Data GmbH</Product>

View File

@@ -17,9 +17,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon>
<Version>3.4.0</Version>
<AssemblyVersion>3.4.0</AssemblyVersion>
<FileVersion>3.4.0</FileVersion>
<Version>3.4.3</Version>
<AssemblyVersion>3.4.3</AssemblyVersion>
<FileVersion>3.4.3</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -0,0 +1,34 @@
using System.Linq.Expressions;
namespace DigitalData.Core.Abstractions.Infrastructure;
public static class Extensions
{
#region Create
public static Task<TEntity> CreateAsync<TEntity, TDto>(this IRepository<TEntity> repository, TDto dto, CancellationToken ct = default)
{
var entity = repository.Mapper.Map(dto);
return repository.CreateAsync(entity, ct);
}
public static Task<IEnumerable<TEntity>> CreateAsync<TEntity, TDto>(this IRepository<TEntity> repository, IEnumerable<TDto> dtos, CancellationToken ct = default)
{
var entities = dtos.Select(dto => repository.Mapper.Map(dto));
return repository.CreateAsync(entities, ct);
}
#endregion
#region Read
public static async Task<TEntity?> ReadFirstOrDefaultAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
=> (await repository.ReadAllAsync(expression)).FirstOrDefault();
public static async Task<TEntity> ReadFirstAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
=> (await repository.ReadAllAsync(expression)).First();
public static async Task<TEntity?> ReadSingleOrDefaultAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
=> (await repository.ReadAllAsync(expression)).SingleOrDefault();
public static async Task<TEntity> ReadSingleAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
=> (await repository.ReadAllAsync(expression)).Single();
#endregion
}

View File

@@ -5,7 +5,6 @@
/// </summary>
/// <typeparam name="TEntity">The type of the entity this repository works with.</typeparam>
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
[Obsolete("ICRUDRepository has been deprecated. Please use the IRepository interface instead, which provides a better abtraction (e.g. without tracking) and flexibility.")]
public interface ICRUDRepository<TEntity, TId> where TEntity : class, IUnique<TId>
{
/// <summary>

View File

@@ -0,0 +1,50 @@
namespace DigitalData.Core.Abstractions.Infrastructure
{
/// <summary>
/// Defines methods for mapping between entities and Data Transfer Objects (DTOs).
/// </summary>
/// <typeparam name="TEntity">The type of the entity to be mapped.</typeparam>
public interface IEntityMapper<TEntity>
{
/// <summary>
/// Maps an entity to a DTO.
/// </summary>
/// <typeparam name="TDto">The type of the DTO to map to.</typeparam>
/// <param name="entity">The entity to be mapped.</param>
/// <returns>The mapped DTO.</returns>
TDto Map<TDto>(TEntity entity);
/// <summary>
/// Maps an entity list to a DTO list.
/// </summary>
/// <typeparam name="TDto">The type of the DTO to map to.</typeparam>
/// <param name="entities">The entity list to be mapped.</param>
/// <returns>The mapped DTO list.</returns>
IEnumerable<TDto> Map<TDto>(IEnumerable<TEntity> entities);
/// <summary>
/// Maps a DTO to an entity.
/// </summary>
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
/// <param name="dto">The DTO to be mapped.</param>
/// <returns>The mapped entity.</returns>
TEntity Map<TDto>(TDto dto);
/// <summary>
/// Maps a DTO list to an entity list.
/// </summary>
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
/// <param name="dtos">The DTO list to be mapped.</param>
/// <returns>The mapped entity list.</returns>
IEnumerable<TEntity> Map<TDto>(IEnumerable<TDto> dtos);
/// <summary>
/// Maps a DTO to an existing entity.
/// </summary>
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
/// <param name="dto">The DTO to be mapped.</param>
/// <param name="entity">The existing entity to be updated with the mapped values.</param>
/// <returns>The updated entity.</returns>
TEntity Map<TDto>(TDto dto, TEntity entity);
}
}

View File

@@ -4,13 +4,21 @@ namespace DigitalData.Core.Abstractions.Infrastructure;
public interface IRepository<TEntity>
{
public Task<TEntity> CreateAsync(TEntity dto, CancellationToken ct = default);
public IEntityMapper<TEntity> Mapper { get; }
public Task<TEntity> CreateAsync(IEnumerable<TEntity> dtos, CancellationToken ct = default);
public Task<TEntity> CreateAsync(TEntity entity, CancellationToken ct = default);
public Task<IEnumerable<TEntity>> ReadAsync(Expression? expression = null, CancellationToken ct = default);
public Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken ct = default);
public Task<IEnumerable<TEntity>> ReadAllAsync(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default);
public Task<IEnumerable<TEntity>> UpdateAsync<TDto>(TDto dto, Expression expression, CancellationToken ct = default);
public Task<TEntity?> ReadOrDefaultAsync(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default);
public Task<IEnumerable<TEntity>> DeleteAsync<TDto>(Expression expression, CancellationToken ct = default);
public Task<IEnumerable<TDto>> ReadAllAsync<TDto>(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default);
public Task<TDto?> ReadOrDefaultAsync<TDto>(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default);
public Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken ct = default);
public Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken ct = default);
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
@@ -14,9 +14,9 @@
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core application clean architecture</PackageTags>
<Version>3.2.0</Version>
<AssemblyVersion>3.2.0</AssemblyVersion>
<FileVersion>3.2.0</FileVersion>
<Version>3.2.1</Version>
<AssemblyVersion>3.2.1</AssemblyVersion>
<FileVersion>3.2.1</FileVersion>
</PropertyGroup>
<ItemGroup>
@@ -27,7 +27,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
@@ -38,6 +37,18 @@
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
</ItemGroup>

View File

@@ -0,0 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Infrastructure.AutoMapper;
public static class DependencyInjection
{
public static EntityConfigurationOptions<TEntity> UseAutoMapper<TEntity>(this EntityConfigurationOptions<TEntity> options, params Type[] typeOfDtos)
{
options.AddCustomMapper<EntityAutoMapper<TEntity>>(services =>
{
if (typeOfDtos.Length != 0)
{
services.AddAutoMapper(cnf =>
{
foreach (var typeOfDto in typeOfDtos)
{
cnf.CreateMap(typeof(TEntity), typeOfDto);
cnf.CreateMap(typeOfDto, typeof(TEntity));
}
});
}
});
return options;
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>DigitalData.Core.Infrastructure.AutoMapper</PackageId>
<Version>1.0.2</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Infrastructure.AutoMapper</Product>
<Description>This package provides AutoMapper support for the DigitalData.Core.Infrastructure module. It includes mapping configurations and abstractions used for object-to-object mapping within the infrastructure layer.</Description>
<Copyright>Copyright 2024</Copyright>
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<RepositoryType>digital data core abstractions clean architecture mapping</RepositoryType>
<PackageTags>digital data core infrastructure clean architecture mapping</PackageTags>
<AssemblyVersion>1.0.2</AssemblyVersion>
<FileVersion>1.0.2</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\nuget-package-icons\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ProjectReference Include="..\DigitalData.Core.Infrastructure\DigitalData.Core.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Infrastructure;
namespace DigitalData.Core.Infrastructure.AutoMapper;
public class EntityAutoMapper<TEntity> : IEntityMapper<TEntity>
{
private readonly IMapper _rootMapper;
public EntityAutoMapper(IMapper rootMapper)
{
_rootMapper = rootMapper;
}
public TDto Map<TDto>(TEntity entity) => _rootMapper.Map<TDto>(entity);
public IEnumerable<TDto> Map<TDto>(IEnumerable<TEntity> entities) => _rootMapper.Map<IEnumerable<TDto>>(entities);
public TEntity Map<TDto>(TDto dto) => _rootMapper.Map<TEntity>(dto);
public IEnumerable<TEntity> Map<TDto>(IEnumerable<TDto> dtos) => _rootMapper.Map<IEnumerable<TEntity>>(dtos);
public TEntity Map<TDto>(TDto dto, TEntity entity) => _rootMapper.Map(dto, entity);
}

View File

@@ -1,5 +1,4 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Abstractions.Infrastructure;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
@@ -7,41 +6,74 @@ namespace DigitalData.Core.Infrastructure;
public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
{
protected readonly TDbContext Context;
protected internal readonly TDbContext Context;
protected readonly DbSet<TEntity> Entities;
protected internal readonly DbSet<TEntity> Entities;
protected readonly IMapper Mapper;
public IEntityMapper<TEntity> Mapper { get; }
public DbRepository(TDbContext context, Func<TDbContext, DbSet<TEntity>> queryFactory, IMapper mapper)
public DbRepository(TDbContext context, Func<TDbContext, DbSet<TEntity>> queryFactory, IEntityMapper<TEntity> mapper)
{
Context = context;
Entities = queryFactory(context);
Mapper = mapper;
}
public virtual Task<TEntity> CreateAsync(TEntity entity, CancellationToken ct = default)
public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken ct = default)
{
throw new NotImplementedException();
Entities.Add(entity);
await Context.SaveChangesAsync(ct);
return entity;
}
public virtual Task<TEntity> CreateAsync(IEnumerable<TEntity> entities, CancellationToken ct = default)
public virtual async Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken ct = default)
{
throw new NotImplementedException();
Entities.AddRange(entities);
await Context.SaveChangesAsync(ct);
return entities;
}
public virtual Task<IEnumerable<TEntity>> DeleteAsync<TDto>(Expression expression, CancellationToken ct = default)
public virtual async Task<IEnumerable<TEntity>> ReadAllAsync(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default)
=> expression is null
? await Entities.AsNoTracking().ToListAsync(ct)
: await Entities.AsNoTracking().Where(expression).ToListAsync(ct);
public virtual async Task<TEntity?> ReadOrDefaultAsync(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default)
=> single
? await Entities.AsNoTracking().Where(expression).SingleOrDefaultAsync(ct)
: await Entities.AsNoTracking().Where(expression).FirstOrDefaultAsync(ct);
public virtual async Task<IEnumerable<TDto>> ReadAllAsync<TDto>(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default)
=> Mapper.Map<TDto>(await ReadAllAsync(expression, ct));
public virtual async Task<TDto?> ReadOrDefaultAsync<TDto>(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default)
{
throw new NotImplementedException();
var entity = await ReadOrDefaultAsync(expression, single, ct);
return entity is null ? default : Mapper.Map<TDto>(entity);
}
public virtual Task<IEnumerable<TEntity>> ReadAsync(Expression? expression = null, CancellationToken ct = default)
public virtual async Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken ct = default)
{
throw new NotImplementedException();
var entities = await Entities.Where(expression).ToListAsync(ct);
for (int i = entities.Count - 1; i >= 0; i--)
{
Mapper.Map(dto, entities[i]);
Entities.Update(entities[i]);
}
await Context.SaveChangesAsync(ct);
}
public virtual Task<IEnumerable<TEntity>> UpdateAsync<TDto>(TDto dto, Expression expression, CancellationToken ct = default)
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken ct = default)
{
throw new NotImplementedException();
var entities = await Entities.Where(expression).ToListAsync(ct);
for (int i = entities.Count - 1; i >= 0; i--)
{
Entities.Remove(entities[i]);
}
await Context.SaveChangesAsync(ct);
}
}

View File

@@ -6,12 +6,14 @@ namespace DigitalData.Core.Infrastructure;
public static class DependencyInjection
{
public static IServiceCollection AddDbRepository<TDbContext, TEntity>(this IServiceCollection services, Func<TDbContext, DbSet<TEntity>> queryFactory)
public static EntityConfigurationOptions<TEntity> AddDbRepository<TDbContext, TEntity>(this IServiceCollection services, Func<TDbContext, DbSet<TEntity>> queryFactory)
where TDbContext : DbContext
where TEntity : DbContext
where TEntity : class
{
return services
services
.AddScoped<IRepository<TEntity>, DbRepository<TDbContext, TEntity>>()
.AddSingleton(queryFactory);
return new EntityConfigurationOptions<TEntity>(services);
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>DigitalData.Core.Infrastructure</PackageId>
<Version>2.0.0.0</Version>
<Version>2.0.4</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Infrastructure</Product>
@@ -16,6 +16,8 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<RepositoryType>digital data core abstractions clean architecture</RepositoryType>
<PackageTags>digital data core infrastructure clean architecture</PackageTags>
<AssemblyVersion>2.0.4</AssemblyVersion>
<FileVersion>2.0.4</FileVersion>
</PropertyGroup>
<ItemGroup>
@@ -25,8 +27,16 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.16" />
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.15" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,27 @@
using DigitalData.Core.Abstractions.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Infrastructure;
public class EntityConfigurationOptions<TEntity>
{
private readonly IServiceCollection _services;
public EntityConfigurationOptions(IServiceCollection services)
{
_services = services;
}
public EntityConfigurationOptions<TEntity> AddCustomMapper<TEntityMapper>(Action<IServiceCollection> configure, Func<IServiceProvider, TEntityMapper>? factory = null)
where TEntityMapper : class, IEntityMapper<TEntity>
{
configure(_services);
if (factory is null)
_services.AddSingleton<IEntityMapper<TEntity>, TEntityMapper>();
else
_services.AddSingleton(factory);
return this;
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -10,6 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.3" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.20.70" />
@@ -25,8 +26,24 @@
<ProjectReference Include="..\DigitalData.Core.Application\DigitalData.Core.Application.csproj" />
<ProjectReference Include="..\DigitalData.Core.Client\DigitalData.Core.Client.csproj" />
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
<ProjectReference Include="..\DigitalData.Core.Infrastructure.AutoMapper\DigitalData.Core.Infrastructure.AutoMapper.csproj" />
<ProjectReference Include="..\DigitalData.Core.Infrastructure\DigitalData.Core.Infrastructure.csproj" />
<ProjectReference Include="..\DigitalData.Core.Security\DigitalData.Core.Security.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.20" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.15" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.15" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.4" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,113 @@
namespace DigitalData.Core.Tests.Infrastructure;
using DigitalData.Core.Infrastructure;
using DigitalData.Core.Tests.Mock;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure.AutoMapper;
public class DbRepositoryTests
{
private IHost _host;
private IRepository<User> _userRepo;
[SetUp]
public void Setup()
{
var builder = Host.CreateApplicationBuilder();
builder.Services.AddDbContext<MockDbContext>(opt => opt.UseInMemoryDatabase("MockDB"));
builder.Services.AddDbRepository<MockDbContext, User>(context => context.Users).UseAutoMapper(typeof(UserCreateDto), typeof(UserReadDto), typeof(UserBase));
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
_host = builder.Build();
_userRepo = _host.Services.GetRequiredService<IRepository<User>>();
}
[TearDown]
public void TearDown()
{
if (_host is IDisposable disposableHost)
disposableHost.Dispose();
}
[TestCase(true, TestName = "WhenGivenMultipleUsers")]
[TestCase(false, TestName = "WhenGivenSingleUser")]
public void CreateAsync_ShouldNotThrow(bool multiple)
{
// Arrange
var faker = Fake.CreateUserFaker();
// Act & Assert
if (multiple)
Assert.DoesNotThrowAsync(async () => await _userRepo.CreateAsync(faker.Generate(Random.Shared.Next(1, 10))));
else
Assert.DoesNotThrowAsync(async () => await _userRepo.CreateAsync(faker.Generate()));
}
[TestCase(true, TestName = "WhenDtoUsed")]
[TestCase(false, TestName = "WhenEntityUsed")]
public async Task ReadAsync_ShouldReturnCreated(bool useDto)
{
// Act
var createdUser = useDto
? await _userRepo.CreateAsync(Fake.UserCreateDto)
: await _userRepo.CreateAsync(Fake.User);
var readUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
// Assert
Assert.Multiple(() =>
{
Assert.That(readUser, Is.Not.Null);
Assert.That(readUser, Is.EqualTo(createdUser));
});
}
[Test]
public async Task ReadAsync_ShouldReturnUpdated()
{
// Arrange
var createdUser = await _userRepo.CreateAsync(Fake.UserCreateDto);
var userUpdateDto = new UserUpdateDto() { Age = 10, Email = "Bar", FirstName = "Foo" };
// Act
await _userRepo.UpdateAsync(userUpdateDto, u => u.Id == createdUser!.Id);
var upToDateUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser!.Id);
// Assert
Assert.Multiple(() =>
{
Assert.That(upToDateUser, Is.Not.Null);
Assert.That(upToDateUser?.FirstName, Is.EqualTo(userUpdateDto.FirstName));
Assert.That(upToDateUser?.Email, Is.EqualTo(userUpdateDto.Email));
Assert.That(upToDateUser?.Age, Is.EqualTo(userUpdateDto.Age));
});
}
[Test]
public async Task ReadAsync_ShouldNotReturnDeleted()
{
// Arrange
var createdUser = await _userRepo.CreateAsync(Fake.UserCreateDto);
var readUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
// Act
await _userRepo.DeleteAsync(u => u.Id == createdUser.Id);
var deletedUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
// Assert
Assert.Multiple(() =>
{
Assert.That(readUser, Is.Not.Null);
Assert.That(deletedUser, Is.Null);
});
}
}

View File

@@ -0,0 +1,28 @@
using Bogus;
namespace DigitalData.Core.Tests.Mock;
public static class Fake
{
public static Faker<User> CreateUserFaker(string? firstName = null, string? email = null, int? age = null) => new Faker<User>()
.RuleFor(u => u.FirstName, f => firstName ?? f.Name.FirstName())
.RuleFor(u => u.Email, f => email ?? f.Internet.Email())
.RuleFor(u => u.Age, f => age ?? f.Random.Int(18, 99));
private static readonly Faker<User> UserFaker = CreateUserFaker();
public static User User => UserFaker.Generate();
public static List<User> Users => UserFaker.Generate(Random.Shared.Next(1, 10));
public static Faker<UserCreateDto> CreateUserCreateDtoFaker(string? firstName = null, string? email = null, int? age = null) => new Faker<UserCreateDto>()
.RuleFor(u => u.FirstName, f => firstName ?? f.Name.FirstName())
.RuleFor(u => u.Email, f => email ?? f.Internet.Email())
.RuleFor(u => u.Age, f => age ?? f.Random.Int(18, 99));
private static readonly Faker<UserCreateDto> UserCreateDtoFaker = CreateUserCreateDtoFaker();
public static UserCreateDto UserCreateDto => UserCreateDtoFaker.Generate();
public static List<UserCreateDto> UserCreateDtos => UserCreateDtoFaker.Generate(Random.Shared.Next(1, 10));
}

View File

@@ -0,0 +1,16 @@
using AutoMapper;
namespace DigitalData.Core.Tests.Mock;
public class MappingProfile : Profile
{
public MappingProfile()
{
// DTO ---> Entity
CreateMap<UserCreateDto, User>();
CreateMap<UserUpdateDto, User>();
// Entity ---> DTO
CreateMap<User, UserReadDto>();
}
}

View File

@@ -0,0 +1,12 @@
using Microsoft.EntityFrameworkCore;
namespace DigitalData.Core.Tests.Mock;
public class MockDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public MockDbContext(DbContextOptions options) : base(options)
{
}
}

View File

@@ -0,0 +1,12 @@
namespace DigitalData.Core.Tests.Mock;
public class User : UserBase
{
public required int Id { get; init; }
public override int GetHashCode() => HashCode.Combine(Id, FirstName, Email, Age);
public override bool Equals(object? obj)
=> (obj is User user && user.GetHashCode() == GetHashCode())
|| (obj is UserBase userBase && userBase.GetHashCode() == base.GetHashCode());
}

View File

@@ -0,0 +1,14 @@
namespace DigitalData.Core.Tests.Mock;
public class UserBase
{
public required string FirstName { get; set; }
public required string Email { get; set; }
public required int Age { get; set; }
public override int GetHashCode() => HashCode.Combine(FirstName, Email, Age);
public override bool Equals(object? obj) => obj is UserBase user && user.GetHashCode() == GetHashCode();
}

View File

@@ -0,0 +1,5 @@
namespace DigitalData.Core.Tests.Mock;
public class UserCreateDto : UserBase
{
}

View File

@@ -0,0 +1,5 @@
namespace DigitalData.Core.Tests.Mock;
public class UserReadDto : UserBase
{
}

View File

@@ -0,0 +1,5 @@
namespace DigitalData.Core.Tests.Mock;
public class UserUpdateDto : UserBase
{
}

View File

@@ -31,6 +31,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Security", "Security", "{72
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Abstractions.Security", "DigitalData.Core.Abstractions.Security\DigitalData.Core.Abstractions.Security.csproj", "{C9266749-9504-4EA9-938F-F083357B60B7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDF84A84-1E01-484E-B073-383F7139C891}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Infrastructure.AutoMapper", "DigitalData.Core.Infrastructure.AutoMapper\DigitalData.Core.Infrastructure.AutoMapper.csproj", "{CE00E1F7-2771-4D9C-88FB-E564894E539E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{41795B74-A757-4E93-B907-83BFF04EEE5C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -88,13 +96,31 @@ Global
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.Build.0 = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.Build.0 = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A765EBEA-3D1E-4F36-869B-6D72F87FF3F6} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
{DB404CD9-CBB8-4771-AB1B-FD4FDE2C28CC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{C57B2480-F632-4691-9C4C-8CC01237203C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{B54DEF90-C30C-44EA-9875-76F1B330CBB7} = {EDF84A84-1E01-484E-B073-383F7139C891}
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{6A80FFEC-9B83-40A7-8C78-124440B48B33} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{13E40DF1-6123-4838-9BF8-086C94E6ADF6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{84B18026-F9A0-4366-BC69-1662D9E7342D} = {EDF84A84-1E01-484E-B073-383F7139C891}
{E009A053-A9F4-48F2-984F-EF5C376A9B14} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{47D80C65-74A2-4EB8-96A5-D571A9108FB3} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
{0FA93730-8084-4907-B172-87D610323796} = {EDF84A84-1E01-484E-B073-383F7139C891}
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6} = {EDF84A84-1E01-484E-B073-383F7139C891}
{72CBAFBA-55CC-49C9-A484-F8F4550054CB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{C9266749-9504-4EA9-938F-F083357B60B7} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
{CE00E1F7-2771-4D9C-88FB-E564894E539E} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
{41795B74-A757-4E93-B907-83BFF04EEE5C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8E2C3187-F848-493A-9E79-56D20DDCAC94}