将 AutoMapper 的 ProjectTo 与扩展选项一起使用时,内存会随着每个请求而增加
Memory increases with each request when using AutoMapper's ProjectTo with expand option
我正在构建一个基于 CQRS/MediatR 的 .NET 5 REST API,并且我注意到在对我的应用程序进行压力测试时线性内存增加。我做了一些分析,发现来自名称 space System.Linq.Expression
的大量对象实例占用了所有 space。所有这些实例都与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>>>();
}
我正在构建一个基于 CQRS/MediatR 的 .NET 5 REST API,并且我注意到在对我的应用程序进行压力测试时线性内存增加。我做了一些分析,发现来自名称 space System.Linq.Expression
的大量对象实例占用了所有 space。所有这些实例都与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>>>();
}