Skip to content

Commit 3768099

Browse files
authored
Merge pull request #797 from neozhu/replace-automapper-with-mapperly
Replace AutoMapper with Mapperly for Improved Performance
2 parents d51c1aa + 542a0ab commit 3768099

78 files changed

Lines changed: 491 additions & 684 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/Application/Application.csproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@
1515
<PackageReference Include="jcamp.FluentEmail.Core" Version="3.8.0" />
1616
<PackageReference Include="jcamp.FluentEmail.MailKit" Version="3.8.0" />
1717
<PackageReference Include="jcamp.FluentEmail.Razor" Version="3.8.0" />
18-
<PackageReference Include="AutoMapper" Version="13.0.1" />
1918
<PackageReference Include="FluentValidation" Version="11.10.0" />
2019
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.10.0" />
2120
<PackageReference Include="LazyCache" Version="2.4.0" />
2221
<PackageReference Include="LazyCache.AspNetCore" Version="2.4.0" />
2322
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.10" />
2423
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.10" />
25-
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.5" />
26-
<PackageReference Include="Hangfire.Core" Version="1.8.14" />
27-
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
24+
<PackageReference Include="Riok.Mapperly" Version="4.1.0" />
25+
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.8" />
26+
<PackageReference Include="Hangfire.Core" Version="1.8.15" />
27+
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.15" />
2828
<PackageReference Include="Hangfire.InMemory" Version="1.0.0" />
29-
<PackageReference Include="ZiggyCreatures.FusionCache" Version="1.4.0" />
29+
<PackageReference Include="ZiggyCreatures.FusionCache" Version="1.4.1" />
3030
<PackageReference Include="ActualLab.Fusion" Version="9.5.59" />
3131
<PackageReference Include="ActualLab.Fusion.Blazor" Version="9.5.59" />
32-
<PackageReference Include="ActualLab.Generators" Version="9.5.59"/>
32+
<PackageReference Include="ActualLab.Generators" Version="9.5.59" />
3333
</ItemGroup>
3434
<ItemGroup>
3535
<ProjectReference Include="..\Domain\Domain.csproj" />

src/Application/Common/Extensions/QueryableExtensions.cs

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,34 +30,34 @@ public static IQueryable<T> ApplySpecification<T>(this IQueryable<T> query, ISpe
3030
}
3131

3232
/// <summary>
33-
/// Extension method to provided ordered queryable data to a paginated result set.
33+
/// Projects the query to a paginated data asynchronously.
3434
/// </summary>
35-
/// <remarks>
36-
/// This method will apply the given specification to the query, paginate the results, and project them to the desired
37-
/// result type.
38-
/// </remarks>
39-
/// <typeparam name="T">Source type of the entities in the query</typeparam>
40-
/// <typeparam name="TResult">Destination type to which the entities should be projected</typeparam>
41-
/// <param name="query">The original ordered query to project and paginate</param>
42-
/// <param name="spec">The specification to apply to the query before projection and pagination</param>
43-
/// <param name="pageNumber">The desired page number of the paginated results</param>
44-
/// <param name="pageSize">The number of items per page in the paginated results</param>
45-
/// <param name="configuration">Configuration for the projection</param>
46-
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
47-
/// <returns>The paginated and projected data</returns>
35+
/// <typeparam name="T">The type of entities in the query</typeparam>
36+
/// <typeparam name="TResult">The type of the result</typeparam>
37+
/// <param name="query">The original ordered query</param>
38+
/// <param name="spec">The specification to apply to the query</param>
39+
/// <param name="pageNumber">The page number</param>
40+
/// <param name="pageSize">The size of the page</param>
41+
/// <param name="mapperFunc">The function to map entities to result</param>
42+
/// <param name="cancellationToken">The cancellation token</param>
43+
/// <returns>A task that represents the asynchronous operation. The task result contains the paginated data</returns>
4844
public static async Task<PaginatedData<TResult>> ProjectToPaginatedDataAsync<T, TResult>(
4945
this IOrderedQueryable<T> query, ISpecification<T> spec, int pageNumber, int pageSize,
50-
IConfigurationProvider configuration, CancellationToken cancellationToken = default) where T : class, IEntity
46+
Func<T, TResult> mapperFunc, CancellationToken cancellationToken = default) where T : class, IEntity
5147
{
5248
var specificationEvaluator = SpecificationEvaluator.Default;
53-
var count = await specificationEvaluator.GetQuery(query.AsNoTracking(), spec).CountAsync();
54-
var data = await specificationEvaluator.GetQuery(query.AsNoTracking(), spec).Skip((pageNumber - 1) * pageSize)
49+
var queryWithSpec = specificationEvaluator.GetQuery(query.AsNoTracking(), spec);
50+
51+
var count = await queryWithSpec.CountAsync(cancellationToken);
52+
var data = await queryWithSpec
53+
.Skip((pageNumber - 1) * pageSize)
5554
.Take(pageSize)
56-
.ProjectTo<TResult>(configuration)
5755
.ToListAsync(cancellationToken);
58-
return new PaginatedData<TResult>(data, count, pageNumber, pageSize);
59-
}
6056

57+
var items = data.Select(x => mapperFunc(x)).ToList();
58+
59+
return new PaginatedData<TResult>(items, count, pageNumber, pageSize);
60+
}
6161

6262
/// <summary>
6363
/// Filters the queryable data based on the specified keyword.
@@ -95,5 +95,4 @@ public static IQueryable<T> WhereContainsKeyword<T>(this IQueryable<T> source, s
9595
var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameter);
9696
return source.Where(lambda);
9797
}
98-
99-
}
98+
}

src/Application/Common/Mappings/MappingExtensions.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
namespace CleanArchitecture.Blazor.Application.Common.Mappings;
@@ -18,9 +18,4 @@ public static Task<PaginatedData<TDestination>> PaginatedDataAsync<TDestination>
1818
return PaginatedData<TDestination>.CreateAsync(queryable.AsNoTracking(), pageNumber, pageSize);
1919
}
2020

21-
public static Task<List<TDestination>> ProjectToListAsync<TDestination>(this IQueryable queryable,
22-
IConfigurationProvider configuration) where TDestination : class
23-
{
24-
return queryable.ProjectTo<TDestination>(configuration).AsNoTracking().ToListAsync();
25-
}
2621
}

src/Application/Common/Security/ChangeUserProfileModel.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,7 @@ public class ChangeUserProfileModel
1919
public string? TenantName { get; set; }
2020
public string? TimeZoneId { get; set; }
2121
public string? LanguageCode { get; set; }
22-
private class Mapping : Profile
23-
{
24-
public Mapping()
25-
{
26-
CreateMap<ChangeUserProfileModel, UserProfile>().ReverseMap();
27-
}
28-
}
22+
2923
}
3024

3125

src/Application/Common/Security/UserProfile.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ public class UserProfile
2323
? TimeZoneInfo.Local.BaseUtcOffset
2424
: TimeZoneInfo.FindSystemTimeZoneById(TimeZoneId).BaseUtcOffset;
2525
}
26+
27+
[Mapper]
28+
public static partial class UserProfileMapper
29+
{
30+
public static partial ChangeUserProfileModel ToChangeUserProfileModel(UserProfile entity);
31+
}

src/Application/Common/Security/UserProfileStateService.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using CleanArchitecture.Blazor.Application.Features.Identity.DTOs;
1+
using CleanArchitecture.Blazor.Application.Features.Identity.Mappers;
22
using CleanArchitecture.Blazor.Domain.Identity;
33
using Microsoft.AspNetCore.Identity;
44
using Microsoft.Extensions.DependencyInjection;
@@ -12,23 +12,21 @@ public class UserProfileStateService
1212
private UserProfile _userProfile = new UserProfile() { Email="", UserId="", UserName="" };
1313
private readonly UserManager<ApplicationUser> _userManager;
1414
private readonly IFusionCache _fusionCache;
15-
private readonly IMapper _mapper;
1615

1716
public UserProfileStateService(IServiceScopeFactory scopeFactory,
18-
IFusionCache fusionCache,
19-
IMapper mapper)
17+
IFusionCache fusionCache)
2018
{
2119
var scope = scopeFactory.CreateScope();
2220
_userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
2321
_fusionCache = fusionCache;
24-
_mapper = mapper;
22+
2523
}
2624
public async Task InitializeAsync(string userName)
2725
{
2826
var key = GetApplicationUserCacheKey(userName);
2927
var result = await _fusionCache.GetOrSetAsync(key,
3028
_ => _userManager.Users.Where(x => x.UserName == userName).Include(x => x.UserRoles)
31-
.ThenInclude(x => x.Role).ProjectTo<ApplicationUserDto>(_mapper.ConfigurationProvider)
29+
.ThenInclude(x => x.Role).ProjectTo()
3230
.FirstOrDefaultAsync(), RefreshInterval);
3331
if(result is not null)
3432
{

src/Application/DependencyInjection.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ public static class DependencyInjection
1212
{
1313
public static IServiceCollection AddApplication(this IServiceCollection services)
1414
{
15-
services.AddAutoMapper(config => { config.AddMaps(Assembly.GetExecutingAssembly()); });
1615
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
1716
services.AddTransient(typeof(IRequestExceptionHandler<,,>), typeof(DbExceptionHandler<,,>));
1817
services.AddMediatR(config =>
Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using CleanArchitecture.Blazor.Application.Common.Interfaces.Serialization;
54
using CleanArchitecture.Blazor.Application.Features.Identity.DTOs;
65

76
namespace CleanArchitecture.Blazor.Application.Features.AuditTrails.DTOs;
@@ -10,34 +9,15 @@ namespace CleanArchitecture.Blazor.Application.Features.AuditTrails.DTOs;
109
public class AuditTrailDto
1110
{
1211
[Description("Id")] public int Id { get; set; }
13-
1412
[Description("User Id")] public string? UserId { get; set; }
15-
1613
[Description("Audit Type")] public AuditType? AuditType { get; set; }
17-
1814
[Description("Table Name")] public string? TableName { get; set; }
19-
2015
[Description("Created DateTime")] public DateTime DateTime { get; set; }
21-
2216
[Description("Old Values")] public Dictionary<string, object?>? OldValues { get; set; }
23-
2417
[Description("New Values")] public Dictionary<string, object?>? NewValues { get; set; }
25-
2618
[Description("Affected Columns")] public List<string>? AffectedColumns { get; set; }
27-
2819
[Description("Primary Key")] public string PrimaryKey { get; set; } = default!;
29-
3020
[Description("Show Details")] public bool ShowDetails { get; set; }
31-
3221
[Description("Owner")] public ApplicationUserDto? Owner { get; set; }
33-
34-
private class Mapping : Profile
35-
{
36-
public Mapping()
37-
{
38-
CreateMap<AuditTrail, AuditTrailDto>(MemberList.None)
39-
.ForMember(x => x.PrimaryKey,
40-
s => s.MapFrom(y => JsonSerializer.Serialize(y.PrimaryKey, DefaultJsonSerializerOptions.Options)));
41-
}
42-
}
22+
4323
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using CleanArchitecture.Blazor.Application.Features.AuditTrails.DTOs;
2+
using CleanArchitecture.Blazor.Application.Features.Identity.Mappers;
3+
4+
namespace CleanArchitecture.Blazor.Application.Features.AuditTrails.Mappers;
5+
#pragma warning disable RMG020
6+
#pragma warning disable RMG012
7+
[Mapper]
8+
[UseStaticMapper(typeof(ApplicationUserMapper))]
9+
public static partial class AuditTrailMapper
10+
{
11+
public static partial IQueryable<AuditTrailDto> ProjectTo(this IQueryable<AuditTrail> q);
12+
[MapPropertyFromSource(nameof(AuditTrail.PrimaryKey), Use = nameof(MapPrimaryKey))]
13+
public static partial AuditTrailDto ToDto(AuditTrail auditTrail);
14+
15+
private static string MapPrimaryKey(AuditTrail source)
16+
{
17+
return JsonSerializer.Serialize(source.PrimaryKey, new JsonSerializerOptions());
18+
}
19+
}

src/Application/Features/AuditTrails/Queries/Export/ExportAuditTrailsQuery.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using CleanArchitecture.Blazor.Application.Features.AuditTrails.DTOs;
5+
using CleanArchitecture.Blazor.Application.Features.AuditTrails.Mappers;
56

67
namespace CleanArchitecture.Blazor.Application.Features.AuditTrails.Queries.Export;
78

@@ -18,17 +19,14 @@ public class ExportAuditTrailsQueryHandler :
1819
private readonly IApplicationDbContext _context;
1920
private readonly IExcelService _excelService;
2021
private readonly IStringLocalizer<ExportAuditTrailsQueryHandler> _localizer;
21-
private readonly IMapper _mapper;
2222

2323
public ExportAuditTrailsQueryHandler(
2424
IApplicationDbContext context,
25-
IMapper mapper,
2625
IExcelService excelService,
2726
IStringLocalizer<ExportAuditTrailsQueryHandler> localizer
2827
)
2928
{
3029
_context = context;
31-
_mapper = mapper;
3230
_excelService = excelService;
3331
_localizer = localizer;
3432
}
@@ -38,7 +36,7 @@ public async Task<byte[]> Handle(ExportAuditTrailsQuery request, CancellationTok
3836
var data = await _context.AuditTrails
3937
.Where(x => x.TableName!.Contains(request.Keyword))
4038
.OrderBy($"{request.OrderBy} {request.SortDirection}")
41-
.ProjectTo<AuditTrailDto>(_mapper.ConfigurationProvider)
39+
.ProjectTo()
4240
.ToListAsync(cancellationToken);
4341
var result = await _excelService.ExportAsync(data,
4442
new Dictionary<string, Func<AuditTrailDto, object?>>

0 commit comments

Comments
 (0)