如何将值传递给AutoMapper Profile投影?

5

我们有一个支持票列表的视图。

现在我们需要显示一个额外的图标,如果当前登录的用户已回复支持票。这意味着我们必须向我们的投影添加一个附加属性,我们不能仅仅在当前用户的列表上进行过滤。

现在我们的想法是将此附加属性与个人资料映射。但我们不知道如何将当前用户ID传递给个人资料。

最终它可能看起来像这样

public class SupportTicketProjectionProfile : AutoMapper.Profile
{
    public SupportTicketProjectionProfile()
    {
        CreateMap<SupportTicketEntity, SupportTicketProjection>()
            .ForMember(p => p.CurrentUserHasReplied, 
               opt => opt.MapFrom(e=>e.Editors.Any(editor=> editor.Id == **passedIdValue**))

但是如何传递userId呢?
我们发现可以将Dictionary<string, object>传递给ProjectTo方法。但是我们找不到任何关于如何在AutoMapper配置文件中访问此字典的文档。
谢谢任何提示。
2个回答

6
我终于找到了诀窍:创建一个临时的映射模型。
public class SupportTicketUserContextExpressionModel
{
    public SupportTicketEntity SupportTicket { get; set; }
    public bool UserHasReplied { get; set; }
}

借助于(我们称之为)表达式模型,您可以注入额外的子查询结果:

var query = _dbContext.SupportTickets
    .Select(ticket => new
    {
        ticket,
        hasReplied = ticket.Editors.Any(e => e.UserId == userId)
    })
    .Select(temp => new SupportTicketUserContextExpressionModel
    {
        SupportTicket = temp.ticket,
        HasReplied = temp.hasReplied
    }));

现在您需要一个新的附加配置文件:

CreateMap<SupportTicketUserContextExpressionModel, SupportTicketUserView>()
     .ForMember(p => p.TicketId,
         opt => opt.MapFrom(e => e.SupportTicket.Id))

     // more here

     .ForMember(p => p.UserHasReplied,
         opt => opt.MapFrom(e => e.HasReplied));

最后,您可以使用该个人资料来进行ProjectTo操作:

.ProjectTo<SupportTicketUserView>(_mapper.ConfigurationProvider);

谢谢,本!


总是很高兴收到一个有效的解决方案,却没有任何评论。♥ - undefined
1
我得到了-1作为我的答案,看来有人只是生气了。 - undefined
+1给你们两个。聪明地使用了一个中间的dto,当我无法使用IQueryable projectto扩展来处理多个参数时。 - undefined

4

映射

您可以使用在映射对象时传递的解析上下文字典。这要比在映射后仅分配一个字段要难以维护和调试。如果源类之外有新字段被分配,您还可以考虑为两个源类创建两个映射。话虽如此,以下是代码:

映射类:

Mapper.CreateMap<Src, Dest>()
    .ForMember(dest => dest.Param
        , opt => opt.ResolveUsing((src, dest, destMember, resContext)
            => src.Param = resContext.Items["item"]));

您还可以将字典传递给嵌套对象映射,作为映射器对象,并且可以通过解析上下文访问字典。

Mapper.CreateMap<Src, Dest>()
    .ForMember(dest => dest.Param
        , opt => opt.ResolveUsing((src, dest, destMember, resContext)
            => resContext.Mapper.Map<NestedDestClass>(src, opts =>
                {
                    opts.Items["item"] = resContext.Items["item"];
                }
            )));

映射对象

var dto = Mapper.Map<Dest>(item, 
    opts => opts.Items["item"] = "somevalue");

这里是一个fiddle

ProjectTo

在使用 ProjectTo 映射时,您可以使用字典。为此,您必须指定映射定义,在该定义中创建运行时参数,然后在执行时提供它。

要使用运行时参数创建映射定义,我们需要“伪造”一个闭包,其中包含一个命名的局部变量。我们无法在配置中访问执行期间使用的“真实”值,因此我们创建一个替代品,仍然为我们创建闭包。构建的基础表达式树会识别此外部输入,并创建占位符参数以在运行时提供。

var configuration = new MapperConfiguration(cfg => 
    string name = null;
    cfg.CreateMap<Dest, Source>()
        .ForMember(m => m.CurrentUserName, opt => opt.MapFrom(src => name))
    ));

执行投影操作时,我们可以使用字典来提供参数值(关于使用匿名对象的示例,请参见文档)。
data.ProjectTo<OrderLineDTO>(configuration, new Dictionary<string, object> {["name"] = "test"}).ToList();

当底层的LINQ提供程序执行投影时,将正确的运行时值替换在表达式中,使您能够在投影中使用每个映射的运行时值。您可以在任何基于表达式的配置选项中使用这些运行时值。

谢谢你的回复。这正是我发现的(而且不喜欢,因为很难维护)。难道没有其他方法可以将动态表达式(在我的情况下是ID比较)传递给投影吗? - undefined
我不这么认为,我建议只需执行var dto = Mapper.Map<Dest>(item); dto.Image = image;。这样更容易调试,并且单元测试应该确保字段始终被赋值。 - undefined
我使用投影。如果我使用孤立的赋值,这意味着我的查询将以O(n)的时间复杂度执行。 - undefined
1
你有没有看过parametrization?它基本上和Map的使用方式一样。 - undefined
让我用一段你可以使用的代码片段和一些解释来更新我的答案。 - undefined
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接