ASP.NET 具有作用域依赖性的 CORE Automapper 配置文件
ASP.NET CORE Automapper Profile with Scoped Dependency
在我的用例中,我需要根据上下文映射 属性。
请记住,我通过调用 ProjectTo
函数将 Automapper 与 Entity Framework 一起使用。所以很遗憾,自定义值解析器是没有选择的。
简单示例:
public class Comment
{
public int Id { get;set; }
public int UserId { get;set; }
...
}
public class Source
{
public int Id { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
public class Destination
{
public int Id { get; set; }
public int NumOwnComments { get; set; }
}
基本上Destination应该包含自己评论的数量。
使用 ICurrentUserService
和 属性 UserId
.
动态解析当前用户
我已经通过以下方式解决了这个问题:
在 Startup.cs
我添加了一个 Transient
映射器/配置。
services.AddTransient(provider => new MapperConfiguration(cfg => { cfg.AddProfile(new MappingProfile(provider.GetService<ICurrentUserService>())); })
.CreateMapper());
然后在 MappingProfile 中,我按以下方式创建了映射:
public class MappingProfile : Profile {
public MappingProfile(ICurrentUserService currentUserService) {
CreateMap<Source, Destination>()
.ForMember(vm => vm.NumOwnComments, opts => opts.MapFrom(s => s.Comments.Count(c => c.UserId == currentUserService.UserId))
;
}
}
虽然这可行,但将映射器配置为瞬态/作用域依赖性并不是很好。每个请求都会创建这个映射器,这会消耗大量内存和 cpu 个周期。
是否有更优雅的方法,例如将映射配置文件创建为单例,然后在作用域/瞬态映射器中执行它?
我会回答我自己的问题以备将来参考:
感谢@lucian-bargaoanu 在评论中提供link:https://docs.automapper.org/en/latest/Queryable-Extensions.html#parameterization
可以通过 ProjectTo 方法传递动态参数。
我最终为所有 DTO 投影创建了扩展方法
public static class DestinationProjection
{
public static IQueryable<Destination> ProjectToDestination(IQueryable source, IConfiguration configuration, int currentUserId) {
return source.ProjectTo<Destination>(configuration, new { currentUserId });
}
}
并且在映射中我使用了这个参数
public class MappingProfile : Profile
{
public MappingProfile() {
int? currentUserId = null;
CreateMap<Source, Destination>()
.ForMember(vm => vm.NumOwnComments, opts => opts.MapFrom(s => s.Comments.Count(c => c.UserId == currentUserId.GetValueOrDefault()))
;
}
}
这样我就可以在处理程序 class 中注入我的 ICurrentUserService
。
public class DestinationListQueryHandler
{
public DestinationListQueryHandler(IMapper mapper, IDbContext dbContext, ICurrentUserService currentUserService)
{
// field initialization logic
}
public async Task<IEnumerable<Destination>> Handle(CancellationToken cancellationToken)
{
return await dbContext.Sources.ProjectToDestination(mapper.ConfigurationProvider, currentUserId).ToListAsync(cancellationToken);
}
}
在我的用例中,我需要根据上下文映射 属性。
请记住,我通过调用 ProjectTo
函数将 Automapper 与 Entity Framework 一起使用。所以很遗憾,自定义值解析器是没有选择的。
简单示例:
public class Comment
{
public int Id { get;set; }
public int UserId { get;set; }
...
}
public class Source
{
public int Id { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
public class Destination
{
public int Id { get; set; }
public int NumOwnComments { get; set; }
}
基本上Destination应该包含自己评论的数量。
使用 ICurrentUserService
和 属性 UserId
.
我已经通过以下方式解决了这个问题:
在 Startup.cs
我添加了一个 Transient
映射器/配置。
services.AddTransient(provider => new MapperConfiguration(cfg => { cfg.AddProfile(new MappingProfile(provider.GetService<ICurrentUserService>())); })
.CreateMapper());
然后在 MappingProfile 中,我按以下方式创建了映射:
public class MappingProfile : Profile {
public MappingProfile(ICurrentUserService currentUserService) {
CreateMap<Source, Destination>()
.ForMember(vm => vm.NumOwnComments, opts => opts.MapFrom(s => s.Comments.Count(c => c.UserId == currentUserService.UserId))
;
}
}
虽然这可行,但将映射器配置为瞬态/作用域依赖性并不是很好。每个请求都会创建这个映射器,这会消耗大量内存和 cpu 个周期。
是否有更优雅的方法,例如将映射配置文件创建为单例,然后在作用域/瞬态映射器中执行它?
我会回答我自己的问题以备将来参考:
感谢@lucian-bargaoanu 在评论中提供link:https://docs.automapper.org/en/latest/Queryable-Extensions.html#parameterization
可以通过 ProjectTo 方法传递动态参数。
我最终为所有 DTO 投影创建了扩展方法
public static class DestinationProjection
{
public static IQueryable<Destination> ProjectToDestination(IQueryable source, IConfiguration configuration, int currentUserId) {
return source.ProjectTo<Destination>(configuration, new { currentUserId });
}
}
并且在映射中我使用了这个参数
public class MappingProfile : Profile
{
public MappingProfile() {
int? currentUserId = null;
CreateMap<Source, Destination>()
.ForMember(vm => vm.NumOwnComments, opts => opts.MapFrom(s => s.Comments.Count(c => c.UserId == currentUserId.GetValueOrDefault()))
;
}
}
这样我就可以在处理程序 class 中注入我的 ICurrentUserService
。
public class DestinationListQueryHandler
{
public DestinationListQueryHandler(IMapper mapper, IDbContext dbContext, ICurrentUserService currentUserService)
{
// field initialization logic
}
public async Task<IEnumerable<Destination>> Handle(CancellationToken cancellationToken)
{
return await dbContext.Sources.ProjectToDestination(mapper.ConfigurationProvider, currentUserId).ToListAsync(cancellationToken);
}
}