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>
|
||||
<Nullable>enable</Nullable>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
|
||||
<PackageId>EECMJobRunner.Infrastructure</PackageId>
|
||||
<PackageId>ECMJobRunner.Infrastructure</PackageId>
|
||||
<Authors>Digital Data GmbH</Authors>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>ECMJobRunner.Domain</Product>
|
||||
<Product>ECMJobRunner.Infrastructure</Product>
|
||||
<Copyright>Copyright 2026</Copyright>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ECMJobRunner.Domain\ECMJobRunner.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net480'">
|
||||
<!-- Entity Framework 6 for .NET Framework 4.8 -->
|
||||
<PackageReference Include="EntityFramework" Version="6.5.1" />
|
||||
<!-- AutoMapper for .NET Framework 4.8 -->
|
||||
<PackageReference Include="AutoMapper" Version="10.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
@@ -23,5 +29,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" 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>
|
||||
</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