Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforcing generic repository to return aggregate root #606

Merged
merged 4 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Dfe.Academies.Application.Common.Interfaces;
using Dfe.Academies.Domain.Constituencies;
using Dfe.Academies.Infrastructure;
using Dfe.Academies.Infrastructure.Caching;
using Dfe.Academies.Infrastructure.Repositories;
Expand All @@ -8,6 +9,7 @@
using Dfe.Academies.Domain.Interfaces.Repositories;
using Dfe.Academies.Domain.Interfaces.Caching;
using Dfe.Academies.Infrastructure.QueryServices;
using Dfe.Academies.Domain.ValueObjects;

namespace Microsoft.Extensions.DependencyInjection
{
Expand Down Expand Up @@ -41,8 +43,8 @@ public static IServiceCollection AddPersonsApiInfrastructureDependencyGroup(
services.AddScoped<ITrustRepository, TrustRepository>();
services.AddScoped<IEstablishmentRepository, EstablishmentRepository>();
services.AddScoped<IConstituencyRepository, ConstituencyRepository>();
services.AddScoped(typeof(IMstrRepository<>), typeof(MstrRepository<>));
services.AddScoped(typeof(IMopRepository<>), typeof(MopRepository<>));
services.AddScoped(typeof(IMstrRepository<,>), typeof(MstrRepository<,>));
services.AddScoped(typeof(IMopRepository<,>), typeof(MopRepository<,>));

// Query Services
services.AddScoped<IEstablishmentQueryService, EstablishmentQueryService>();
Expand Down
8 changes: 4 additions & 4 deletions Dfe.Academies.Api.Infrastructure/MopContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

private static void ConfigureMemberContactDetails(EntityTypeBuilder<MemberContactDetails> memberContactDetailsConfiguration)
{
memberContactDetailsConfiguration.HasKey(e => e.MemberId);
memberContactDetailsConfiguration.HasKey(e => e.Id);

memberContactDetailsConfiguration.ToTable("MemberContactDetails", DEFAULT_SCHEMA);
memberContactDetailsConfiguration.Property(e => e.MemberId).HasColumnName("memberID")
memberContactDetailsConfiguration.Property(e => e.Id).HasColumnName("memberID")
.HasConversion(
v => v.Value,
v => new MemberId(v));
Expand All @@ -55,7 +55,7 @@ private static void ConfigureMemberContactDetails(EntityTypeBuilder<MemberContac
private void ConfigureConstituency(EntityTypeBuilder<Constituency> constituencyConfiguration)
{
constituencyConfiguration.ToTable("Constituencies", DEFAULT_SCHEMA);
constituencyConfiguration.Property(e => e.ConstituencyId).HasColumnName("constituencyId")
constituencyConfiguration.Property(e => e.Id).HasColumnName("constituencyId")
.HasConversion(
v => v.Value,
v => new ConstituencyId(v));
Expand All @@ -78,7 +78,7 @@ private void ConfigureConstituency(EntityTypeBuilder<Constituency> constituencyC
.HasOne(c => c.MemberContactDetails)
.WithOne()
.HasForeignKey<Constituency>(c => c.MemberId)
.HasPrincipalKey<MemberContactDetails>(m => m.MemberId);
.HasPrincipalKey<MemberContactDetails>(m => m.Id);
}


Expand Down
13 changes: 11 additions & 2 deletions Dfe.Academies.Api.Infrastructure/MopRepository.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
using Dfe.Academies.Domain.Interfaces.Repositories;
using System.Diagnostics.CodeAnalysis;
using Dfe.Academies.Domain.Common;
using Dfe.Academies.Domain.Interfaces.Repositories;
using Dfe.Academies.Infrastructure.Repositories;

namespace Dfe.Academies.Infrastructure
{
public class MopRepository<TEntity>(MopContext dbContext) : Repository<TEntity, MopContext>(dbContext), IMopRepository<TEntity> where TEntity : class, new();
[ExcludeFromCodeCoverage]
public class MopRepository<TAggregate, TId>(MopContext dbContext)
: Repository<TAggregate, TId, MopContext>(dbContext), IMopRepository<TAggregate, TId>
where TAggregate : class, IAggregateRoot<TId>
where TId : ValueObject
{

}
}
10 changes: 9 additions & 1 deletion Dfe.Academies.Api.Infrastructure/MstrRepository.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
using Dfe.Academies.Infrastructure.Repositories;
using Dfe.Academies.Domain.Interfaces.Repositories;
using Dfe.Academies.Domain.Common;
using System.Diagnostics.CodeAnalysis;

namespace Dfe.Academies.Infrastructure
{
public class MstrRepository<TEntity>(MstrContext dbContext) : Repository<TEntity, MstrContext>(dbContext), IMstrRepository<TEntity> where TEntity : class, new();
[ExcludeFromCodeCoverage]
public class MstrRepository<TAggregate, TId>(MstrContext dbContext)
: Repository<TAggregate, TId, MstrContext>(dbContext), IMstrRepository<TAggregate, TId>
where TAggregate : class, IAggregateRoot<TId>
where TId : ValueObject
{
}
}
113 changes: 58 additions & 55 deletions Dfe.Academies.Api.Infrastructure/Repositories/Repository.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
using Dfe.Academies.Domain.Interfaces.Repositories;
using Dfe.Academies.Domain.Common;
using Dfe.Academies.Domain.Interfaces.Repositories;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;

namespace Dfe.Academies.Infrastructure.Repositories
{
#pragma warning disable CS8603 // Possible null reference return, behaviour expected
#pragma warning disable CS8603, S2436

[ExcludeFromCodeCoverage]
public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity>
where TEntity : class, new()
public abstract class Repository<TAggregate, TId, TDbContext> : IRepository<TAggregate, TId>
where TAggregate : class, IAggregateRoot<TId>
where TId : ValueObject
where TDbContext : DbContext
{
/// <summary>
Expand All @@ -22,170 +25,170 @@ public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity>

/// <summary>Shorthand for _dbContext.Set</summary>
/// <returns></returns>
protected virtual DbSet<TEntity> DbSet()
protected virtual DbSet<TAggregate> DbSet()
{
return this.DbContext.Set<TEntity>();
return this.DbContext.Set<TAggregate>();
}

/// <inheritdoc />
public virtual IQueryable<TEntity> Query() => (IQueryable<TEntity>)this.DbSet();
public virtual IQueryable<TAggregate> Query() => (IQueryable<TAggregate>)this.DbSet();

/// <inheritdoc />
public virtual ICollection<TEntity> Fetch(Expression<Func<TEntity, bool>> predicate)
public virtual ICollection<TAggregate> Fetch(Expression<Func<TAggregate, bool>> predicate)
{
return (ICollection<TEntity>)((IQueryable<TEntity>)this.DbSet()).Where<TEntity>(predicate).ToList<TEntity>();
return (ICollection<TAggregate>)((IQueryable<TAggregate>)this.DbSet()).Where<TAggregate>(predicate).ToList<TAggregate>();
}

/// <inheritdoc />
public virtual async Task<ICollection<TEntity>> FetchAsync(
Expression<Func<TEntity, bool>> predicate,
public virtual async Task<ICollection<TAggregate>> FetchAsync(
Expression<Func<TAggregate, bool>> predicate,
CancellationToken cancellationToken = default(CancellationToken))
{
return (ICollection<TEntity>)await EntityFrameworkQueryableExtensions.ToListAsync<TEntity>(((IQueryable<TEntity>)this.DbSet()).Where<TEntity>(predicate), cancellationToken);
return (ICollection<TAggregate>)await EntityFrameworkQueryableExtensions.ToListAsync<TAggregate>(((IQueryable<TAggregate>)this.DbSet()).Where<TAggregate>(predicate), cancellationToken);
}

/// <inheritdoc />
public virtual TEntity Find(params object[] keyValues) => this.DbSet().Find(keyValues);
public virtual TAggregate Find(params TId[] keyValues) => this.DbSet().Find(keyValues);

/// <inheritdoc />
public virtual TEntity Find(Expression<Func<TEntity, bool>> predicate)
public virtual TAggregate Find(Expression<Func<TAggregate, bool>> predicate)
{
return ((IQueryable<TEntity>)this.DbSet()).FirstOrDefault<TEntity>(predicate);
return ((IQueryable<TAggregate>)this.DbSet()).FirstOrDefault<TAggregate>(predicate);
}

/// <inheritdoc />
public virtual async Task<TEntity> FindAsync(params object[] keyValues)
public virtual async Task<TAggregate> FindAsync(params TId[] keyValues)
{
return await this.DbSet().FindAsync(keyValues);
}

/// <inheritdoc />
public virtual async Task<TEntity> FindAsync(
Expression<Func<TEntity, bool>> predicate,
public virtual async Task<TAggregate> FindAsync(
Expression<Func<TAggregate, bool>> predicate,
CancellationToken cancellationToken = default(CancellationToken))
{
return await EntityFrameworkQueryableExtensions.FirstOrDefaultAsync<TEntity>((IQueryable<TEntity>)this.DbSet(), predicate, cancellationToken);
return await EntityFrameworkQueryableExtensions.FirstOrDefaultAsync<TAggregate>((IQueryable<TAggregate>)this.DbSet(), predicate, cancellationToken);
}

/// <inheritdoc />
public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate)
public virtual TAggregate Get(Expression<Func<TAggregate, bool>> predicate)
{
return ((IQueryable<TEntity>)this.DbSet()).Single<TEntity>(predicate);
return ((IQueryable<TAggregate>)this.DbSet()).Single<TAggregate>(predicate);
}

/// <inheritdoc />
public virtual TEntity Get(params object[] keyValues)
public virtual TAggregate Get(params TId[] keyValues)
{
return this.Find(keyValues) ?? throw new InvalidOperationException(
$"Entity type {(object)typeof(TEntity)} is null for primary key {(object)keyValues}");
$"Entity type {(object)typeof(TAggregate)} is null for primary key {(object)keyValues}");
}

/// <inheritdoc />
public virtual async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate)
public virtual async Task<TAggregate> GetAsync(Expression<Func<TAggregate, bool>> predicate)
{
return await EntityFrameworkQueryableExtensions.SingleAsync<TEntity>((IQueryable<TEntity>)this.DbSet(), predicate, new CancellationToken());
return await EntityFrameworkQueryableExtensions.SingleAsync<TAggregate>((IQueryable<TAggregate>)this.DbSet(), predicate, new CancellationToken());
}

/// <inheritdoc />
public virtual async Task<TEntity> GetAsync(params object[] keyValues)
public virtual async Task<TAggregate> GetAsync(params TId[] keyValues)
{
return await this.FindAsync(keyValues) ?? throw new InvalidOperationException(
$"Entity type {(object)typeof(TEntity)} is null for primary key {(object)keyValues}");
$"Entity type {(object)typeof(TAggregate)} is null for primary key {(object)keyValues}");
}

/// <inheritdoc />
public virtual TEntity Add(TEntity entity)
public virtual TAggregate Add(TAggregate entity)
{
this.DbContext.Add<TEntity>(entity);
this.DbContext.Add<TAggregate>(entity);
this.DbContext.SaveChanges();
return entity;
}

/// <inheritdoc />
public virtual async Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default(CancellationToken))
public virtual async Task<TAggregate> AddAsync(TAggregate entity, CancellationToken cancellationToken = default(CancellationToken))
{
await this.DbContext.AddAsync<TEntity>(entity, cancellationToken);
await this.DbContext.AddAsync<TAggregate>(entity, cancellationToken);
await this.DbContext.SaveChangesAsync(cancellationToken);
return entity;
}

/// <inheritdoc />
public virtual IEnumerable<TEntity> AddRange(ICollection<TEntity> entities)
public virtual IEnumerable<TAggregate> AddRange(ICollection<TAggregate> entities)
{
this.DbContext.AddRange((IEnumerable<object>)entities);
this.DbContext.SaveChanges();
return (IEnumerable<TEntity>)entities;
return (IEnumerable<TAggregate>)entities;
}

/// <inheritdoc />
public virtual async Task<IEnumerable<TEntity>> AddRangeAsync(
ICollection<TEntity> entities,
public virtual async Task<IEnumerable<TAggregate>> AddRangeAsync(
ICollection<TAggregate> entities,
CancellationToken cancellationToken = default(CancellationToken))
{
await this.DbContext.AddRangeAsync((IEnumerable<object>)entities, cancellationToken);
await this.DbContext.SaveChangesAsync(cancellationToken);
return (IEnumerable<TEntity>)entities;
return (IEnumerable<TAggregate>)entities;
}

/// <inheritdoc />
public virtual TEntity Remove(TEntity entity)
public virtual TAggregate Remove(TAggregate entity)
{
this.DbContext.Remove<TEntity>(entity);
this.DbContext.Remove<TAggregate>(entity);
this.DbContext.SaveChanges();
return entity;
}

/// <inheritdoc />
public virtual async Task<TEntity> RemoveAsync(
TEntity entity,
public virtual async Task<TAggregate> RemoveAsync(
TAggregate entity,
CancellationToken cancellationToken = default(CancellationToken))
{
this.DbContext.Remove<TEntity>(entity);
this.DbContext.Remove<TAggregate>(entity);
await this.DbContext.SaveChangesAsync(cancellationToken);
return entity;
}

/// <inheritdoc />
public virtual int Delete(Expression<Func<TEntity, bool>> predicate)
public virtual int Delete(Expression<Func<TAggregate, bool>> predicate)
{
return DbSet().Where(predicate).ExecuteDelete();
}

/// <inheritdoc />
public virtual IEnumerable<TEntity> RemoveRange(ICollection<TEntity> entities)
public virtual IEnumerable<TAggregate> RemoveRange(ICollection<TAggregate> entities)
{
this.DbSet().RemoveRange((IEnumerable<TEntity>)entities);
this.DbSet().RemoveRange((IEnumerable<TAggregate>)entities);
this.DbContext.SaveChanges();
return (IEnumerable<TEntity>)entities;
return (IEnumerable<TAggregate>)entities;
}

/// <inheritdoc />
public virtual async Task<IEnumerable<TEntity>> RemoveRangeAsync(
ICollection<TEntity> entities,
public virtual async Task<IEnumerable<TAggregate>> RemoveRangeAsync(
ICollection<TAggregate> entities,
CancellationToken cancellationToken = default(CancellationToken))
{
this.DbSet().RemoveRange((IEnumerable<TEntity>)entities);
this.DbSet().RemoveRange((IEnumerable<TAggregate>)entities);
await this.DbContext.SaveChangesAsync(cancellationToken);
return (IEnumerable<TEntity>)entities;
return (IEnumerable<TAggregate>)entities;
}

/// <inheritdoc />
public virtual TEntity Update(TEntity entity)
public virtual TAggregate Update(TAggregate entity)
{
this.DbContext.Update<TEntity>(entity);
this.DbContext.Update<TAggregate>(entity);
this.DbContext.SaveChanges();
return entity;
}

/// <inheritdoc />
public virtual async Task<TEntity> UpdateAsync(
TEntity entity,
public virtual async Task<TAggregate> UpdateAsync(
TAggregate entity,
CancellationToken cancellationToken = default(CancellationToken))
{
this.DbContext.Update<TEntity>(entity);
this.DbContext.Update<TAggregate>(entity);
await this.DbContext.SaveChangesAsync(cancellationToken);
return entity;
}
}
#pragma warning restore CS8603 // Possible null reference return
#pragma warning restore CS8603, S2436
}
6 changes: 6 additions & 0 deletions Dfe.Academies.Domain/Common/IAggregateRoot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Dfe.Academies.Domain.Common
{
public interface IAggregateRoot<out TId> : IEntity<TId> where TId : ValueObject
{
}
}
7 changes: 7 additions & 0 deletions Dfe.Academies.Domain/Common/IEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Dfe.Academies.Domain.Common
{
public interface IEntity<out TId> where TId : ValueObject
{
TId Id { get; }
}
}
9 changes: 5 additions & 4 deletions Dfe.Academies.Domain/Constituencies/Constituency.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using Dfe.Academies.Domain.ValueObjects;
using Dfe.Academies.Domain.Common;
using Dfe.Academies.Domain.ValueObjects;

namespace Dfe.Academies.Domain.Constituencies
{
#pragma warning disable CS8618
public class Constituency
public class Constituency : IAggregateRoot<ConstituencyId>
{
public ConstituencyId ConstituencyId { get; private set; }
public ConstituencyId Id { get; }
public MemberId MemberId { get; private set; }
public string ConstituencyName { get; private set; }
public NameDetails NameDetails { get; private set; }
Expand All @@ -24,7 +25,7 @@ public Constituency(
DateOnly? endDate,
MemberContactDetails memberContactDetails)
{
ConstituencyId = constituencyId ?? throw new ArgumentNullException(nameof(constituencyId));
Id = constituencyId ?? throw new ArgumentNullException(nameof(constituencyId));
MemberId = memberId ?? throw new ArgumentNullException(nameof(memberId));
ConstituencyName = constituencyName;
NameDetails = nameDetails ?? throw new ArgumentNullException(nameof(nameDetails));
Expand Down
Loading
Loading