使用 AutoMapper 的 ProjectTo 和扩展选项时,每个请求都会增加内存
Posted
技术标签:
【中文标题】使用 AutoMapper 的 ProjectTo 和扩展选项时,每个请求都会增加内存【英文标题】:Memory increases with each request when using AutoMapper's ProjectTo with expand option 【发布时间】:2021-12-20 17:31:58 【问题描述】:我正在构建一个基于 CQRS/MediatR 的 .NET 5 REST API,我注意到在对我的应用程序进行压力测试时内存呈线性增加。我做了一些分析,发现来自命名空间System.Linq.Expression
的大量对象实例占用了所有空间。所有这些实例都与AM的MapperConfiguration
有关。
我使用 AutoMapper 将我的实体映射到 DTO,为此我主要使用以下 ProjectTo
扩展方法:
public static IQueryable<TDestination> ProjectTo<TDestination>(this IQueryable source, IConfigurationProvider configuration, object parameters, params Expression<Func<TDestination, object>>[] membersToExpand);
经过一些测试,我注意到只有在ProjectTo
方法中提供membersToExpand
时才会出现内存问题。
当提供“扩展”并调用相关端点时,就像每次创建映射的表达式但从未释放过一样。当它们停留时,它们会在内存中累积并在调用查询时添加。
这是比较两个快照的内存状态截图:
对端点的一次调用后的第一个 五次后的第二次调用同一个端点(不做任何更改)我不介意内存使用情况,认为它可能是 AM 使用的某种缓存以获得更好的响应时间,但问题是使用的内存越多,我的 API 响应请求的速度就越慢(开始时的 4ms 到压力测试后的 90ms)。我想 AM 很难在这么多存储的表达式中进行搜索。一旦我停止使用“扩展”系统,响应又是瞬时的(4 毫秒)。
最后一点是,当应用程序池被回收(并且内存被清除)时,API 以 4ms 的速度再次全速响应(带有扩展)。
我在网上搜索了几个小时,看看我是否错过了与缓存或其他东西相关的特定 AM 配置,但一无所获。
是否有人对此行为有任何想法、类似经历或更多信息?
PS : 如您所见,我将 AM 与 DI 一起使用(使用 DI 包和.AddAutoMapper
方法)
下面是我的应用程序中的一些代码示例:
RoleByProfileDto
(RoleDto
没有什么特别之处,ReverseMap
仅用于“翻译”目的):
public class RoleByProfileDto : RoleDto
public ProfileRoleDto ProfileRole get; set;
public void Mapping(AM.Profile profile)
int profileId = default;
profile.CreateMap<Role, RoleByProfileDto>()
.ForMember(dto =>
dto.ProfileRole, opts =>
opts.MapFrom(r => r.ProfileRoles.FirstOrDefault(pr => pr.ProfileId == profileId))
)
.IncludeBase<Role, RoleDto>()
.ReverseMap();
上述请求的处理程序:
public class GetRoleByProfileAndIdQueryHandler : IRequestHandler<GetRoleByProfileAndIdQuery, RoleByProfileDto>
private readonly IApplicationDbContext _context;
private readonly AM.IMapper _mapper;
public GetRoleByProfileAndIdQueryHandler(IApplicationDbContext context, AM.IMapper mapper)
_context = context;
_mapper = mapper;
public async Task<RoleByProfileDto> Handle(GetRoleByProfileAndIdQuery request, CancellationToken cancellationToken)
var dto = await _context.Roles
.Where(r =>
r.Id == request.RoleId
&& r.ProfileRoles.Any(pr =>
pr.ProfileId == request.ProfileId
)
)
.ProjectTo(
_mapper.ConfigurationProvider,
new profileId = request.ProfileId ,
request.Expand.GetExpandMemberList(ExpandMappings)
)
.FirstOrDefaultAsync(cancellationToken);
if (dto == null)
throw new NotFoundException(
nameof(Role),
new List<string>() nameof(Profile), nameof(Role) ,
new List<object>() request.ProfileId, request.RoleId
);
return dto;
private static readonly IReadOnlyDictionary<RoleByProfileAndIdExpand, Expression<Func<RoleByProfileDto, object>>> ExpandMappings =
new Dictionary<RoleByProfileAndIdExpand, Expression<Func<RoleByProfileDto, object>>>
RoleByProfileAndIdExpand.ProfileRole, d => d.ProfileRole
;
用于构建提供给ProjectTo
的扩展数组的方法:
public static Expression<Func<TDto, object>>[] GetExpandMemberList<TEnum, TDto>(
this IList<TEnum> selectedExpands,
IReadOnlyDictionary<TEnum, Expression<Func<TDto, object>>> mappings
)
where TEnum : Enum
where TDto : BaseDto
if (selectedExpands != null && selectedExpands.Any())
return selectedExpands.Select(e => mappings[e]).ToArray();
else
return Array.Empty<Expression<Func<TDto, object>>>();
【问题讨论】:
何时调用RoleByProfileDto::Mapping
?
试试MyGet 构建。
@Stefan 映射是由 AM 直接在 ProjectTo
方法内完成的。这里类型是推断出来的,因为我们在展开部分使用它。
@Lucian Bargaoanu 我会尽快尝试并给您反馈。
是的,很久以前就修复了。我预计 11.0 之前不会有重大变化。检查the upgrade guide。
【参考方案1】:
此漏洞已在 MyGet 构建中修复。详情here.
【讨论】:
关于 AutoMapper 11.0 版何时可用的任何想法?如果可能的话,我想避免使用预发布,但内存泄漏是不可取的。最后一个问题,您认为在生产中使用最后一个预发布版本是否安全?我猜每个构建都经过测试。 与任何版本一样安全。:) 我当然在使用它。没有针对某个版本的特殊测试。以上是关于使用 AutoMapper 的 ProjectTo 和扩展选项时,每个请求都会增加内存的主要内容,如果未能解决你的问题,请参考以下文章
EF Core 相关的千倍性能之差: AutoMapper ProjectTo VS Mapster ProjectToType
“ProjectTo”和 Nullable 类型之后的“Where”子句?