Skip to content

Commit 61d22a3

Browse files
committed
improvements
1 parent 79de220 commit 61d22a3

File tree

7 files changed

+254
-9
lines changed

7 files changed

+254
-9
lines changed

RoyalCode.EnterprisePatterns/RoyalCode.Repositories.EntityFramework/Configurations/RepositoriesBuilder.cs

+40-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using Microsoft.EntityFrameworkCore;
1+
using System.Diagnostics.CodeAnalysis;
2+
using Microsoft.EntityFrameworkCore;
23
using Microsoft.Extensions.DependencyInjection;
4+
using RoyalCode.Entities;
35
using RoyalCode.OperationHint.Abstractions;
46
using RoyalCode.Repositories.Abstractions;
57

@@ -31,6 +33,7 @@ public RepositoriesBuilder(
3133
/// <inheritdoc />
3234
public IRepositoriesBuilder<TDbContext> Add<TEntity>() where TEntity : class
3335
{
36+
// register the repository
3437
var repoType = typeof(IRepository<>).MakeGenericType(typeof(TEntity));
3538
var dbRepoType = typeof(IRepository<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity));
3639
var repoImplType = typeof(InternalRepository<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity));
@@ -41,6 +44,24 @@ public IRepositoriesBuilder<TDbContext> Add<TEntity>() where TEntity : class
4144
foreach (var dataService in repoType.GetInterfaces())
4245
services.Add(ServiceDescriptor.Describe(dataService, sp => sp.GetService(dbRepoType)!, lifetime));
4346

47+
// if the entity implements IHasGuid interface, register the FinderByGuid
48+
if (typeof(IHasGuid).IsAssignableFrom(typeof(TEntity)))
49+
{
50+
var finderType = typeof(IFinderByGuid<>).MakeGenericType(typeof(TEntity));
51+
var finderImplType = typeof(FinderByGuid<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity));
52+
53+
services.Add(ServiceDescriptor.Describe(finderType, finderImplType, lifetime));
54+
}
55+
56+
// if the entity implements IHasCode interface, register the FinderByCode
57+
if (IsGenericAssinableFrom(typeof(IFinderByCode<,>), typeof(TEntity), out var finderByCodeType))
58+
{
59+
var finderImplType = typeof(FinderByCode<,,>)
60+
.MakeGenericType(typeof(TDbContext), typeof(TEntity), finderByCodeType.GetGenericArguments()[1]);
61+
62+
services.Add(ServiceDescriptor.Describe(finderByCodeType, finderImplType, lifetime));
63+
}
64+
4465
return this;
4566
}
4667

@@ -60,4 +81,22 @@ public IRepositoriesBuilder<TDbContext> ConfigureOperationHints(Action<IHintHand
6081

6182
return this;
6283
}
84+
85+
private bool IsGenericAssinableFrom(Type genericType, Type type, [NotNullWhen(true)] out Type? closedType)
86+
{
87+
if (genericType.IsGenericTypeDefinition)
88+
{
89+
foreach (var interfaceType in type.GetInterfaces())
90+
{
91+
if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == genericType)
92+
{
93+
closedType = interfaceType;
94+
return true;
95+
}
96+
}
97+
}
98+
99+
closedType = null;
100+
return false;
101+
}
63102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using RoyalCode.Entities;
3+
using RoyalCode.Repositories.Abstractions;
4+
5+
namespace RoyalCode.Repositories.EntityFramework;
6+
7+
/// <summary>
8+
/// <para>
9+
/// Default implementation of <see cref="IFinderByCode{TEntity,TCode}"/> using EF.
10+
/// </para>
11+
/// </summary>
12+
/// <typeparam name="TDbContext">The EF DbContext type related to the entity.</typeparam>
13+
/// <typeparam name="TEntity">The entity type.</typeparam>
14+
/// <typeparam name="TCode">The type of the code.</typeparam>
15+
public class FinderByCode<TDbContext, TEntity, TCode> : IFinderByCode<TEntity, TCode>
16+
where TDbContext : DbContext
17+
where TEntity : class, IHasCode<TCode>
18+
{
19+
private readonly TDbContext db;
20+
21+
/// <summary>
22+
/// Creates a new instance of the <see cref="FinderByCode{TDbContext,TEntity,TCode}"/>
23+
/// </summary>
24+
/// <param name="dbContext">The DbContext for work.</param>
25+
public FinderByCode(TDbContext dbContext)
26+
{
27+
db = dbContext;
28+
}
29+
30+
/// <inheritdoc/>
31+
public TEntity? FindByCode(TCode code)
32+
{
33+
return db.Set<TEntity>().FirstOrDefault(e => e.Code.Equals(code));
34+
}
35+
36+
/// <inheritdoc/>
37+
public Task<TEntity?> FindByCodeAsync(TCode code, CancellationToken token = default)
38+
{
39+
return db.Set<TEntity>().FirstOrDefaultAsync(e => e.Code.Equals(code), token);
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using RoyalCode.Entities;
3+
using RoyalCode.Repositories.Abstractions;
4+
5+
namespace RoyalCode.Repositories.EntityFramework;
6+
7+
/// <summary>
8+
/// <para>
9+
/// Implementation of <see cref="IFinderByGuid{TEntity}"/> using EF.
10+
/// </para>
11+
/// </summary>
12+
/// <typeparam name="TDbContext">The EF DbContext type related to the entity.</typeparam>
13+
/// <typeparam name="TEntity">The entity type.</typeparam>
14+
public class FinderByGuid<TDbContext, TEntity> : IFinderByGuid<TEntity>
15+
where TDbContext : DbContext
16+
where TEntity : class, IHasGuid
17+
{
18+
private readonly TDbContext db;
19+
20+
/// <summary>
21+
/// Creates a new instance of the finder.
22+
/// </summary>
23+
/// <param name="dbContext">The DbContext for work.</param>
24+
public FinderByGuid(TDbContext dbContext)
25+
{
26+
db = dbContext;
27+
}
28+
29+
/// <inheritdoc/>
30+
public TEntity? FindByGuid(Guid guid)
31+
{
32+
return db.Set<TEntity>().FirstOrDefault(e => e.Guid == guid);
33+
}
34+
35+
/// <inheritdoc/>
36+
public Task<TEntity?> FindByGuidAsync(Guid guid, CancellationToken token = default)
37+
{
38+
return db.Set<TEntity>().FirstOrDefaultAsync(e => e.Guid == guid, token);
39+
}
40+
}

RoyalCode.EnterprisePatterns/RoyalCode.UnitOfWork.EntityFramework/Extensions/PersistenceServiceCollectionExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Extensions.DependencyInjection.Extensions;
44
using RoyalCode.UnitOfWork.EntityFramework;
55
using RoyalCode.UnitOfWork.Abstractions;
6+
using RoyalCode.UnitOfWork.EntityFramework.Internals;
67

78
namespace Microsoft.Extensions.DependencyInjection;
89

RoyalCode.EnterprisePatterns/RoyalCode.UnitOfWork.EntityFramework/IUnitOfWorkBuilder.cs

+35
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using RoyalCode.Repositories.EntityFramework.Configurations;
44
using RoyalCode.Searches.Persistence.EntityFramework.Configurations;
5+
using RoyalCode.UnitOfWork.EntityFramework.Services;
56

67
namespace RoyalCode.UnitOfWork.EntityFramework;
78

@@ -38,6 +39,40 @@ public interface IUnitOfWorkBuilder
3839
public interface IUnitOfWorkBuilder<out TDbContext> : IUnitOfWorkBuilder
3940
where TDbContext : DbContext
4041
{
42+
/// <summary>
43+
/// <para>
44+
/// Configure the <see cref="DbContext"/> for the unit of work.
45+
/// </para>
46+
/// <para>
47+
/// The configuration is done by the <see cref="IConfigureDbContextService{TDbContext}"/>
48+
/// registered in the services.
49+
/// </para>
50+
/// <para>
51+
/// When the <see cref="IConfigureDbContextService{TDbContext}"/> is not registered, an
52+
/// <see cref="InvalidOperationException"/> is thrown.
53+
/// </para>
54+
/// </summary>
55+
/// <returns>The same instance.</returns>
56+
/// <exception cref="InvalidOperationException">
57+
/// The <see cref="IConfigureDbContextService{TDbContext}"/> is not registered.
58+
/// </exception>
59+
IUnitOfWorkBuilder<TDbContext> ConfigureWithService()
60+
{
61+
Services.AddDbContext<TDbContext>((sp, builder) =>
62+
{
63+
var configurator = sp.GetService<IConfigureDbContextService<TDbContext>>();
64+
65+
if (configurator is null)
66+
throw new InvalidOperationException(
67+
"The IConfigureDbContextService is not registered. " +
68+
"When using the ConfigureWithService method, it is necessary to register the " +
69+
"IConfigureDbContextService<TDbContext>.");
70+
71+
configurator.ConfigureDbContext(builder);
72+
}, Lifetime);
73+
return this;
74+
}
75+
4176
/// <summary>
4277
/// Configure the <see cref="DbContext"/> for the unit of work as pooled.
4378
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace RoyalCode.UnitOfWork.EntityFramework.Services;
4+
5+
/// <summary>
6+
/// A service that configures a <see cref="DbContext"/>.
7+
/// </summary>
8+
/// <typeparam name="TDbContext">The type of the <see cref="DbContext"/>.</typeparam>
9+
public interface IConfigureDbContextService<TDbContext>
10+
where TDbContext : DbContext
11+
{
12+
/// <summary>
13+
/// Applies the configuration to the <see cref="DbContextOptionsBuilder"/>.
14+
/// </summary>
15+
/// <param name="builder">The <see cref="DbContextOptionsBuilder"/>.</param>
16+
public void ConfigureDbContext(DbContextOptionsBuilder builder);
17+
}

RoyalCode.EnterprisePatterns/RoyalCode.WorkContext.Abstractions/WorkContextExtensions.cs

+80-8
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public static void Add<TEntity>(this IWorkContext context, TEntity entity)
2929
/// <param name="context">The work context to get the repository.</param>
3030
/// <param name="entity">The new entity instance.</param>
3131
/// <param name="token">Cancellation token.</param>
32-
public static ValueTask AddAsync<TEntity>(this IWorkContext context, TEntity entity, CancellationToken token = default)
32+
public static ValueTask AddAsync<TEntity>(this IWorkContext context, TEntity entity,
33+
CancellationToken token = default)
3334
where TEntity : class
3435
=> context.Repository<TEntity>().AddAsync(entity, token);
3536

@@ -43,7 +44,7 @@ public static ValueTask AddAsync<TEntity>(this IWorkContext context, TEntity ent
4344
public static void AddRange<TEntity>(this IWorkContext context, IEnumerable<TEntity> entities)
4445
where TEntity : class
4546
=> context.Repository<TEntity>().AddRange(entities);
46-
47+
4748
/// <summary>
4849
/// <para>
4950
/// Finds an existing entity through its unique identity (id).
@@ -73,7 +74,8 @@ public static void AddRange<TEntity>(this IWorkContext context, IEnumerable<TEnt
7374
/// Existing instance, or null/default if it does not exist.
7475
/// </para>
7576
/// </returns>
76-
public static ValueTask<TEntity?> FindAsync<TEntity>(this IWorkContext context, object id, CancellationToken token = default)
77+
public static ValueTask<TEntity?> FindAsync<TEntity>(this IWorkContext context, object id,
78+
CancellationToken token = default)
7779
where TEntity : class
7880
=> context.Repository<TEntity>().FindAsync(id, token);
7981

@@ -92,10 +94,78 @@ public static void AddRange<TEntity>(this IWorkContext context, IEnumerable<TEnt
9294
/// An entry representing the entity record obtained from the database.
9395
/// </para>
9496
/// </returns>
95-
public static ValueTask<Entry<TEntity, TId>> FindAsync<TEntity, TId>(this IWorkContext context, Id<TEntity, TId> id, CancellationToken token = default)
97+
public static ValueTask<Entry<TEntity, TId>> FindAsync<TEntity, TId>(this IWorkContext context, Id<TEntity, TId> id,
98+
CancellationToken token = default)
9699
where TEntity : class
97100
=> context.Repository<TEntity>().FindAsync(id, token);
98101

102+
/// <summary>
103+
/// <para>
104+
/// Finds an existing entity through its Guid.
105+
/// </para>
106+
/// </summary>
107+
/// <param name="context">The work context to get the repository.</param>
108+
/// <param name="guid">The entity Guid.</param>
109+
/// <returns>
110+
/// <para>
111+
/// Existing instance, or null/default if it does not exist.
112+
/// </para>
113+
/// </returns>
114+
public static TEntity? FindByGuid<TEntity>(this IWorkContext context, Guid guid)
115+
where TEntity : class, IHasGuid
116+
=> context.GetService<IFinderByGuid<TEntity>>().FindByGuid(guid);
117+
118+
/// <summary>
119+
/// <para>
120+
/// Finds an existing entity through its Guid.
121+
/// </para>
122+
/// </summary>
123+
/// <param name="context">The work context to get the repository.</param>
124+
/// <param name="guid">The entity Guid.</param>
125+
/// <param name="token">Token for cancelling tasks.</param>
126+
/// <returns>
127+
/// <para>
128+
/// Existing instance, or null/default if it does not exist.
129+
/// </para>
130+
/// </returns>
131+
public static Task<TEntity?> FindByGuidAsync<TEntity>(this IWorkContext context, Guid guid,
132+
CancellationToken token = default)
133+
where TEntity : class, IHasGuid
134+
=> context.GetService<IFinderByGuid<TEntity>>().FindByGuidAsync(guid, token);
135+
136+
/// <summary>
137+
/// <para>
138+
/// Finds an existing entity through its Code.
139+
/// </para>
140+
/// </summary>
141+
/// <param name="context">The work context to get the repository.</param>
142+
/// <param name="code">The entity code.</param>
143+
/// <returns>
144+
/// <para>
145+
/// Existing instance, or null/default if it does not exist.
146+
/// </para>
147+
/// </returns>
148+
public static TEntity? FindByCode<TEntity, TCode>(this IWorkContext context, TCode code)
149+
where TEntity : class, IHasCode<TCode>
150+
=> context.GetService<IFinderByCode<TEntity, TCode>>().FindByCode(code);
151+
152+
/// <summary>
153+
/// <para>
154+
/// Finds an existing entity through its Code.
155+
/// </para>
156+
/// </summary>
157+
/// <param name="context">The work context to get the repository.</param>
158+
/// <param name="code">The entity code.</param>
159+
/// <param name="token">Token for cancelling tasks.</param>
160+
/// <returns>
161+
/// <para>
162+
/// Existing instance, or null/default if it does not exist.
163+
/// </para>
164+
/// </returns>
165+
public static Task<TEntity?> FindByCodeAsync<TEntity, TCode>(this IWorkContext context, TCode code, CancellationToken token = default)
166+
where TEntity : class, IHasCode<TCode>
167+
=> context.GetService<IFinderByCode<TEntity, TCode>>().FindByCodeAsync(code, token);
168+
99169
/// <summary>
100170
/// <para>
101171
/// Operation to merge a data model to an existing entity.
@@ -143,7 +213,8 @@ public static IEnumerable<bool> MergeRange<TEntity, TId>(this IWorkContext conte
143213
/// True if the entity exists and has been updated, false otherwise.
144214
/// </para>
145215
/// </returns>
146-
public static Task<bool> MergeAsync<TEntity, TId>(this IWorkContext context, IHasId<TId> model, CancellationToken token = default)
216+
public static Task<bool> MergeAsync<TEntity, TId>(this IWorkContext context, IHasId<TId> model,
217+
CancellationToken token = default)
147218
where TEntity : class
148219
=> context.Repository<TEntity>().MergeAsync(model, token);
149220

@@ -164,7 +235,7 @@ public static Task<bool> MergeAsync<TEntity, TId>(this IWorkContext context, IHa
164235
/// True if the entity exists and has been updated, false otherwise.
165236
/// </para>
166237
/// </returns>
167-
public static Task<bool> MergeAsync<TEntity, TId, TModel>(this IWorkContext context,
238+
public static Task<bool> MergeAsync<TEntity, TId, TModel>(this IWorkContext context,
168239
Id<TEntity, TId> id, TModel model, CancellationToken token = default)
169240
where TEntity : class
170241
where TModel : class
@@ -193,7 +264,7 @@ public static Task<IEnumerable<bool>> MergeRangeAsync<TEntity, TId>(
193264
/// </summary>
194265
/// <param name="context">The work context to get the repository.</param>
195266
/// <param name="entity">The entity.</param>
196-
public static void Remove<TEntity>(this IWorkContext context,TEntity entity)
267+
public static void Remove<TEntity>(this IWorkContext context, TEntity entity)
197268
where TEntity : class
198269
=> context.Repository<TEntity>().Remove(entity);
199270

@@ -256,7 +327,8 @@ public static void RemoveRange<TEntity>(this IWorkContext context, IEnumerable<T
256327
/// The entity excluded, or null if the entity is not found.
257328
/// </para>
258329
/// </returns>
259-
public static Task<TEntity?> DeleteAsync<TEntity>(this IWorkContext context, object id, CancellationToken token = default)
330+
public static Task<TEntity?> DeleteAsync<TEntity>(this IWorkContext context, object id,
331+
CancellationToken token = default)
260332
where TEntity : class
261333
=> context.Repository<TEntity>().DeleteAsync(id, token);
262334

0 commit comments

Comments
 (0)