Add Infrastructure layer with EF6/EF Core support
Introduced a robust Infrastructure layer for the ECMJobRunner system: - Added `AGENTS.md` with detailed project documentation. - Implemented generic repository and unit-of-work patterns. - Added `CfgProfileRepository`, `ProfileSqlJobRepository`, and `ProfileHistoryRepository`. - Integrated AutoMapper for DTO-to-entity mapping. - Added multi-framework support for .NET Framework 4.8 (EF6) and .NET 8.0 (EF Core) using conditional compilation. - Updated `ECMJobRunner.Infrastructure.csproj` with metadata fixes and dependencies. - Introduced dependency injection extension for .NET 8.0. - Enhanced project structure and database context with entity mappings.
This commit is contained in:
205
ECMJobRunner.Infrastructure/AGENTS.md
Normal file
205
ECMJobRunner.Infrastructure/AGENTS.md
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
# ECMJobRunner.Infrastructure
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
ECMJobRunner.Infrastructure is the **infrastructure layer** for the ECM Job Runner system following **Clean Architecture** principles. This project contains:
|
||||||
|
- **DbContext implementation** with Entity Framework
|
||||||
|
- **Repository implementations** for data access
|
||||||
|
- **Entity configurations** (EF mappings)
|
||||||
|
- **Database connection management**
|
||||||
|
|
||||||
|
**Key Principle**: This layer implements the interfaces defined in the Domain layer and handles all database concerns.
|
||||||
|
|
||||||
|
## Target Frameworks
|
||||||
|
|
||||||
|
- **.NET Framework 4.8** (`net480`) - Uses **Entity Framework 6.5.1**
|
||||||
|
- **.NET 8.0** (`net8.0`) - Uses **Entity Framework Core 8.0.11**
|
||||||
|
|
||||||
|
The project uses conditional compilation to support both EF6 and EF Core with the same codebase.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
This project follows **Clean Architecture** principles:
|
||||||
|
- **Depends on Domain layer** (implements domain interfaces)
|
||||||
|
- **No dependencies from Domain** (dependency inversion)
|
||||||
|
- **Conditional compilation** for EF6 vs EF Core differences
|
||||||
|
- **Repository pattern** implementation
|
||||||
|
- **Unit of Work pattern** implementation
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
ECMJobRunner.Infrastructure/
|
||||||
|
├── Data/
|
||||||
|
│ └── JobRunnerDbContext.cs # DbContext with conditional compilation
|
||||||
|
├── Repositories/
|
||||||
|
│ ├── Repository.cs # Generic repository implementation
|
||||||
|
│ ├── ProfileRepository.cs # Profile-specific repository
|
||||||
|
│ ├── ProfileSqlJobRepository.cs
|
||||||
|
│ ├── ProfileHistoryRepository.cs
|
||||||
|
│ └── UnitOfWork.cs # Unit of Work implementation
|
||||||
|
├── ECMJobRunner.Infrastructure.csproj
|
||||||
|
└── AGENTS.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Context
|
||||||
|
|
||||||
|
### JobRunnerDbContext
|
||||||
|
|
||||||
|
Multi-targeted DbContext supporting both EF6 and EF Core:
|
||||||
|
|
||||||
|
**DbSets:**
|
||||||
|
- `Profiles`: DbSet<Profile>
|
||||||
|
- `ProfileSqlJobs`: DbSet<ProfileSqlJob>
|
||||||
|
- `ProfileHistories`: DbSet<ProfileHistory>
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Connection string via constructor parameter
|
||||||
|
- Conditional compilation directives (`#if NET48` / `#else`)
|
||||||
|
- Fluent API configurations in `OnModelCreating`
|
||||||
|
|
||||||
|
**Table Mappings:**
|
||||||
|
- `Profile` → `dbo.TBJR_CFG_PROFILE`
|
||||||
|
- `ProfileSqlJob` → `dbo.TBJR_CFG_PROFILE_SQLJOB`
|
||||||
|
- `ProfileHistory` → `dbo.TBJR_OUT_PROFILE_HISTORY`
|
||||||
|
|
||||||
|
**Column Mappings:** (Examples)
|
||||||
|
- `Profile.ProfileName` → `PROFILE_NAME`
|
||||||
|
- `ProfileSqlJob.SqlCheckQuery` → `SQL_CHECK_QUERY`
|
||||||
|
- All navigation properties configured with relationships
|
||||||
|
|
||||||
|
## Repository Implementations
|
||||||
|
|
||||||
|
### Repository<TEntity>
|
||||||
|
Generic repository implementing `IRepository<TEntity>` with full CRUD operations.
|
||||||
|
|
||||||
|
**Key Methods:**
|
||||||
|
- Synchronous: `GetById`, `GetAll`, `Find`, `SingleOrDefault`, `Add`, `Update`, `Remove`
|
||||||
|
- Asynchronous: `GetByIdAsync`, `GetAllAsync`, `FindAsync`, `SingleOrDefaultAsync`
|
||||||
|
|
||||||
|
### Entity-Specific Repositories
|
||||||
|
- `ProfileRepository : Repository<Profile>, IProfileRepository`
|
||||||
|
- `ProfileSqlJobRepository : Repository<ProfileSqlJob>, IProfileSqlJobRepository`
|
||||||
|
- `ProfileHistoryRepository : Repository<ProfileHistory>, IProfileHistoryRepository`
|
||||||
|
|
||||||
|
These can be extended with entity-specific query methods as needed.
|
||||||
|
|
||||||
|
### UnitOfWork
|
||||||
|
Implements `IUnitOfWork` interface:
|
||||||
|
- Manages DbContext lifecycle
|
||||||
|
- Provides repository instances
|
||||||
|
- Handles transaction management via `SaveChanges`/`SaveChangesAsync`
|
||||||
|
|
||||||
|
## Connection String
|
||||||
|
|
||||||
|
Default connection string (configured in consuming applications):
|
||||||
|
```
|
||||||
|
Server=SDD-VMP04-SQL17\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Schema Source
|
||||||
|
|
||||||
|
SQL scripts and schema definitions are located at:
|
||||||
|
```
|
||||||
|
M:\Datenbank\[DD_ECM]-Database\JobRunner\
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### .NET Framework 4.8 (`net480`)
|
||||||
|
- **ECMJobRunner.Domain** (project reference)
|
||||||
|
- **EntityFramework 6.5.1** (NuGet package)
|
||||||
|
|
||||||
|
### .NET 8.0 (`net8.0`)
|
||||||
|
- **ECMJobRunner.Domain** (project reference)
|
||||||
|
- **Microsoft.EntityFrameworkCore 8.0.11** (NuGet package)
|
||||||
|
- **Microsoft.EntityFrameworkCore.SqlServer 8.0.11** (NuGet package)
|
||||||
|
|
||||||
|
## Conditional Compilation
|
||||||
|
|
||||||
|
The project uses `#if NET48` / `#else` directives to handle differences between EF6 and EF Core:
|
||||||
|
|
||||||
|
**EF6 (.NET Framework 4.8):**
|
||||||
|
```csharp
|
||||||
|
#if NET48
|
||||||
|
using System.Data.Entity;
|
||||||
|
public class JobRunnerDbContext : DbContext
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
**EF Core (.NET 8.0):**
|
||||||
|
```csharp
|
||||||
|
#if !NET48
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
public class JobRunnerDbContext : DbContext
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building the Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet build ECMJobRunner.Infrastructure.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
For specific framework:
|
||||||
|
```bash
|
||||||
|
dotnet build ECMJobRunner.Infrastructure.csproj -f net8.0
|
||||||
|
dotnet build ECMJobRunner.Infrastructure.csproj -f net480
|
||||||
|
```
|
||||||
|
|
||||||
|
## Entity Framework Differences
|
||||||
|
|
||||||
|
### DbContext Constructor
|
||||||
|
- **EF6**: Accepts connection string directly
|
||||||
|
- **EF Core**: Requires `DbContextOptions<T>`
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
- **EF6**: `DbModelBuilder` in `OnModelCreating`
|
||||||
|
- **EF Core**: `ModelBuilder` in `OnModelCreating`
|
||||||
|
|
||||||
|
### Querying
|
||||||
|
- **EF6**: `DbSet<T>.AsNoTracking()` extension
|
||||||
|
- **EF Core**: Same API, built-in support
|
||||||
|
|
||||||
|
### Async Operations
|
||||||
|
- **EF6**: Limited async support
|
||||||
|
- **EF Core**: Full async/await support
|
||||||
|
|
||||||
|
## Development Notes
|
||||||
|
|
||||||
|
- **Nullable navigation properties**: `IEnumerable<T>?` to support defensive programming
|
||||||
|
- **No lazy loading**: Navigation properties loaded explicitly via `.Include()`
|
||||||
|
- **Transaction management**: Handled by Unit of Work pattern
|
||||||
|
- **Repository pattern**: Encapsulates EF-specific code
|
||||||
|
- **Clean Architecture**: Infrastructure depends on Domain, not vice versa
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Create DbContext (connection string from configuration)
|
||||||
|
var connectionString = ConfigurationManager.ConnectionStrings["JobRunner"].ConnectionString;
|
||||||
|
var context = new JobRunnerDbContext(connectionString);
|
||||||
|
|
||||||
|
// Use Unit of Work
|
||||||
|
using var unitOfWork = new UnitOfWork(context);
|
||||||
|
|
||||||
|
// Query profiles
|
||||||
|
var activeProfiles = await unitOfWork.Profiles
|
||||||
|
.FindAsync(p => p.Active);
|
||||||
|
|
||||||
|
// Add new profile
|
||||||
|
var profile = new Profile { ProfileName = "Test", Active = true };
|
||||||
|
unitOfWork.Profiles.Add(profile);
|
||||||
|
await unitOfWork.SaveChangesAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
- **ECMJobRunner.Domain**: Contains entities and repository interfaces
|
||||||
|
- **ECMJobRunner.Application**: Application layer consuming repositories
|
||||||
|
|
||||||
|
## Company Information
|
||||||
|
|
||||||
|
**Author**: Digital Data GmbH
|
||||||
|
**Copyright**: 2026
|
||||||
|
**Repository**: http://git.dd:3000/AppStd/ECMJobRunner.git
|
||||||
41
ECMJobRunner.Infrastructure/DependencyExtension.cs
Normal file
41
ECMJobRunner.Infrastructure/DependencyExtension.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#if NET48
|
||||||
|
using System.Data.Entity;
|
||||||
|
#else
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
#endif
|
||||||
|
using ECMJobRunner.Domain.Interfaces;
|
||||||
|
using ECMJobRunner.Infrastructure.Data;
|
||||||
|
using ECMJobRunner.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
namespace ECMJobRunner.Infrastructure
|
||||||
|
{
|
||||||
|
#if !NET48
|
||||||
|
/// <summary>
|
||||||
|
/// Dependency injection extension methods for Infrastructure layer
|
||||||
|
/// </summary>
|
||||||
|
public static class DependencyExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add Infrastructure services to dependency injection container
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">Service collection</param>
|
||||||
|
/// <param name="connectionString">Database connection string</param>
|
||||||
|
/// <returns>Service collection for chaining</returns>
|
||||||
|
public static IServiceCollection AddInfrastructure(this IServiceCollection services, string connectionString)
|
||||||
|
{
|
||||||
|
// Register DbContext
|
||||||
|
services.AddDbContext<JobRunnerDbContext>(options =>
|
||||||
|
options.UseSqlServer(connectionString));
|
||||||
|
|
||||||
|
// Register repositories
|
||||||
|
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
||||||
|
services.AddScoped<ICfgProfileRepository, CfgProfileRepository>();
|
||||||
|
services.AddScoped<IProfileSqlJobRepository, ProfileSqlJobRepository>();
|
||||||
|
services.AddScoped<IProfileHistoryRepository, ProfileHistoryRepository>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -4,18 +4,24 @@
|
|||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
|
||||||
<PackageId>EECMJobRunner.Infrastructure</PackageId>
|
<PackageId>ECMJobRunner.Infrastructure</PackageId>
|
||||||
<Authors>Digital Data GmbH</Authors>
|
<Authors>Digital Data GmbH</Authors>
|
||||||
<Company>Digital Data GmbH</Company>
|
<Company>Digital Data GmbH</Company>
|
||||||
<Product>ECMJobRunner.Domain</Product>
|
<Product>ECMJobRunner.Infrastructure</Product>
|
||||||
<Copyright>Copyright 2026</Copyright>
|
<Copyright>Copyright 2026</Copyright>
|
||||||
<RepositoryUrl>http://git.dd:3000/AppStd/ECMJobRunner.git</RepositoryUrl>
|
<RepositoryUrl>http://git.dd:3000/AppStd/ECMJobRunner.git</RepositoryUrl>
|
||||||
<PackageTags>digital data ecm job runner Infrastructure</PackageTags>
|
<PackageTags>digital data ecm job runner infrastructure</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ECMJobRunner.Domain\ECMJobRunner.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net480'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net480'">
|
||||||
<!-- Entity Framework 6 for .NET Framework 4.8 -->
|
<!-- Entity Framework 6 for .NET Framework 4.8 -->
|
||||||
<PackageReference Include="EntityFramework" Version="6.5.1" />
|
<PackageReference Include="EntityFramework" Version="6.5.1" />
|
||||||
|
<!-- AutoMapper for .NET Framework 4.8 -->
|
||||||
|
<PackageReference Include="AutoMapper" Version="10.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
@@ -23,5 +29,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.11" />
|
||||||
|
<!-- AutoMapper for .NET 8 -->
|
||||||
|
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using ECMJobRunner.Domain.Entities;
|
||||||
|
using ECMJobRunner.Domain.Interfaces;
|
||||||
|
using ECMJobRunner.Infrastructure.Data;
|
||||||
|
|
||||||
|
namespace ECMJobRunner.Infrastructure.Repositories
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CfgProfile repository implementation
|
||||||
|
/// </summary>
|
||||||
|
public class CfgProfileRepository : Repository<CfgProfile>, ICfgProfileRepository
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public CfgProfileRepository(JobRunnerDbContext context, IMapper mapper) : base(context, mapper)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity-specific methods can be added here in the future
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using ECMJobRunner.Domain.Entities;
|
||||||
|
using ECMJobRunner.Domain.Interfaces;
|
||||||
|
using ECMJobRunner.Infrastructure.Data;
|
||||||
|
|
||||||
|
namespace ECMJobRunner.Infrastructure.Repositories
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ProfileHistory repository implementation
|
||||||
|
/// </summary>
|
||||||
|
public class ProfileHistoryRepository : Repository<ProfileHistory>, IProfileHistoryRepository
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public ProfileHistoryRepository(JobRunnerDbContext context, IMapper mapper) : base(context, mapper)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity-specific methods can be added here in the future
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using ECMJobRunner.Domain.Entities;
|
||||||
|
using ECMJobRunner.Domain.Interfaces;
|
||||||
|
using ECMJobRunner.Infrastructure.Data;
|
||||||
|
|
||||||
|
namespace ECMJobRunner.Infrastructure.Repositories
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ProfileSqlJob repository implementation
|
||||||
|
/// </summary>
|
||||||
|
public class ProfileSqlJobRepository : Repository<ProfileSqlJob>, IProfileSqlJobRepository
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public ProfileSqlJobRepository(JobRunnerDbContext context, IMapper mapper) : base(context, mapper)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity-specific methods can be added here in the future
|
||||||
|
}
|
||||||
|
}
|
||||||
202
ECMJobRunner.Infrastructure/Repositories/Repository.cs
Normal file
202
ECMJobRunner.Infrastructure/Repositories/Repository.cs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#if NET48
|
||||||
|
using System.Data.Entity;
|
||||||
|
#else
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
#endif
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AutoMapper;
|
||||||
|
using ECMJobRunner.Domain.Interfaces;
|
||||||
|
using ECMJobRunner.Infrastructure.Data;
|
||||||
|
|
||||||
|
namespace ECMJobRunner.Infrastructure.Repositories
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic repository implementation for Entity Framework
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">Entity type</typeparam>
|
||||||
|
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
|
||||||
|
{
|
||||||
|
protected readonly JobRunnerDbContext _context;
|
||||||
|
protected readonly DbSet<TEntity> _dbSet;
|
||||||
|
protected readonly IMapper _mapper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public Repository(JobRunnerDbContext context, IMapper mapper)
|
||||||
|
{
|
||||||
|
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||||
|
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||||
|
_dbSet = context.Set<TEntity>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<TEntity?> GetByIdAsync(long id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
#if NET48
|
||||||
|
return await Task.Run(() => _dbSet.Find(id), cancellationToken);
|
||||||
|
#else
|
||||||
|
return await _dbSet.FindAsync(new object[] { id }, cancellationToken);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
#if NET48
|
||||||
|
return await Task.Run(() => _dbSet.AsNoTracking().ToList(), cancellationToken);
|
||||||
|
#else
|
||||||
|
return await _dbSet.AsNoTracking().ToListAsync(cancellationToken);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
#if NET48
|
||||||
|
return await Task.Run(() => _dbSet.Where(predicate).AsNoTracking().ToList(), cancellationToken);
|
||||||
|
#else
|
||||||
|
return await _dbSet.Where(predicate).AsNoTracking().ToListAsync(cancellationToken);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<TEntity?> SingleOrDefaultAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
#if NET48
|
||||||
|
return await Task.Run(() => _dbSet.SingleOrDefault(predicate), cancellationToken);
|
||||||
|
#else
|
||||||
|
return await _dbSet.SingleOrDefaultAsync(predicate, cancellationToken);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<TEntity> AddAsync<TDto>(TDto dto, CancellationToken cancellationToken = default) where TDto : class
|
||||||
|
{
|
||||||
|
if (dto == null) throw new ArgumentNullException(nameof(dto));
|
||||||
|
|
||||||
|
// Map DTO to new entity
|
||||||
|
var entity = _mapper.Map<TEntity>(dto);
|
||||||
|
|
||||||
|
#if NET48
|
||||||
|
await Task.Run(() => _dbSet.Add(entity), cancellationToken);
|
||||||
|
#else
|
||||||
|
await _dbSet.AddAsync(entity, cancellationToken);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
await SaveChangesAsync(cancellationToken);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<int> AddRangeAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancellationToken = default) where TDto : class
|
||||||
|
{
|
||||||
|
if (dtos == null) throw new ArgumentNullException(nameof(dtos));
|
||||||
|
|
||||||
|
var dtoList = dtos.ToList();
|
||||||
|
if (!dtoList.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Map DTOs to entities
|
||||||
|
var entities = _mapper.Map<List<TEntity>>(dtoList);
|
||||||
|
|
||||||
|
#if NET48
|
||||||
|
await Task.Run(() => _dbSet.AddRange(entities), cancellationToken);
|
||||||
|
#else
|
||||||
|
await _dbSet.AddRangeAsync(entities, cancellationToken);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return await SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<int> UpdateAsync<TDto>(Expression<Func<TEntity, bool>> predicate, TDto dto, CancellationToken cancellationToken = default) where TDto : class
|
||||||
|
{
|
||||||
|
if (dto == null) throw new ArgumentNullException(nameof(dto));
|
||||||
|
|
||||||
|
// Get entities with tracking enabled for update
|
||||||
|
#if NET48
|
||||||
|
var entities = await Task.Run(() => _dbSet.Where(predicate).ToList(), cancellationToken);
|
||||||
|
#else
|
||||||
|
var entities = await _dbSet.Where(predicate).ToListAsync(cancellationToken);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!entities.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
foreach (var entity in entities)
|
||||||
|
{
|
||||||
|
// Map DTO onto existing entity (only DTO properties are updated)
|
||||||
|
_mapper.Map(dto, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<bool> UpdateSingleAsync<TDto>(Expression<Func<TEntity, bool>> predicate, TDto dto, CancellationToken cancellationToken = default) where TDto : class
|
||||||
|
{
|
||||||
|
if (dto == null) throw new ArgumentNullException(nameof(dto));
|
||||||
|
|
||||||
|
// Get entity with tracking enabled for update
|
||||||
|
var entity = await SingleOrDefaultAsync(predicate, cancellationToken);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Map DTO onto existing entity (only DTO properties are updated)
|
||||||
|
_mapper.Map(dto, entity);
|
||||||
|
|
||||||
|
await SaveChangesAsync(cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Get entities with tracking enabled for delete
|
||||||
|
#if NET48
|
||||||
|
var entities = await Task.Run(() => _dbSet.Where(predicate).ToList(), cancellationToken);
|
||||||
|
#else
|
||||||
|
var entities = await _dbSet.Where(predicate).ToListAsync(cancellationToken);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!entities.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
_dbSet.RemoveRange(entities);
|
||||||
|
|
||||||
|
return await SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<bool> DeleteSingleAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Get entity with tracking enabled for delete
|
||||||
|
var entity = await SingleOrDefaultAsync(predicate, cancellationToken);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_dbSet.Remove(entity);
|
||||||
|
|
||||||
|
await SaveChangesAsync(cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
#if NET48
|
||||||
|
return await _context.SaveChangesAsync();
|
||||||
|
#else
|
||||||
|
return await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user