【问题标题】:AutoMapper conditional map for recursive model递归模型的 AutoMapper 条件映射
【发布时间】:2015-09-19 00:20:39
【问题描述】:

我有一个具有以下定义的递归模型类:

public class ItemFilterBlockGroup
{
    public ItemFilterBlockGroup(string groupName, ItemFilterBlockGroup parent, bool advanced = false)
    {
        GroupName = groupName;
        ParentGroup = parent;
        Advanced = advanced;
        ChildGroups = new List<ItemFilterBlockGroup>();
    }

    public string GroupName { get; private set; }
    public bool Advanced { get; private set; }
    public ItemFilterBlockGroup ParentGroup { get; private set; }
    public List<ItemFilterBlockGroup> ChildGroups { get; private set; }
}

它有一个名为 ChildGroups 的属性,它是一个自身的列表 - 用于构建分层模型。我要做的是将此模型映射到视图模型,但有条件。有时(取决于 UI 设置)我只想包含 Advanced = false 的子对象,有时我想包含所有模型。

目前,我正在通过涉及 Mapper.Reset() 和运行时重新定义地图的令人讨厌的 hack 来实现这一点 - 这显然不好并且存在多个问题:

Mapper.Reset();
if (showAdvanced)
{
    Mapper.CreateMap<ItemFilterBlockGroup, ItemFilterBlockGroupViewModel>();
}
else
{
    Mapper.CreateMap<ItemFilterBlockGroup, ItemFilterBlockGroupViewModel>()
        .ForMember(dest => dest.ChildGroups,
            opts => opts.MapFrom(from => from.ChildGroups.Where(c => c.Advanced == false)));
}

var mappedViewModels = Mapper.Map<ObservableCollection<ItemFilterBlockGroupViewModel>>(blockGroups);

给定模型的输入层次结构示例:

Root (Advanced = False)
+-Child 1 (Advanced = True)
+-Child 2 (Advanced = False)
+-Child 3 (Advanced = False)
  +-Child 3 Sub Child 1 (Advanced = False)
  +-Child 3 Sub Child 2 (Advanced = True)

第一个 CreateMap 定义返回此层次结构不变,而第二个 CreateMap 定义(使用 Advanced 参数)返回此修改后的层次结构(所有 Advanced = true 模型及其子模型都被排除在映射之外):

Root (Advanced = False)
+-Child 2 (Advanced = False)
+-Child 3 (Advanced = False)
  +-Child 3 Sub Child 1 (Advanced = False)

如何参数化 showAdvanced 条件并使用单个 CreateMap 定义实现相同的结果?我已经搜索了很多正确的解决方案,尝试了 ResolveUsing、CustomResolvers,但无济于事。

【问题讨论】:

    标签: c# automapper


    【解决方案1】:

    您可以使用如下所示的自定义转换器,您可以自定义映射设置。

    创建一个转换类

      internal class AccountConverter : TypeConverter<PD.IAccount, OD.IAccount>
    {
        protected override OD.IAccount ConvertCore(PD.IAccount source)
        {
            var result = new Account()
            {
                CustomerNumber = source.CustomerNumber,
                EAccount = source.EAccount,
                EmailAddress = source.EmailAddress
            };
    
            return result;
        }
    }
    

    然后像这样添加映射。

    Mapper.CreateMap<PD.IAccount, OD.IAccount>()
      .ConvertUsing(new AccountConverter());
    

    【讨论】:

    • 这如何帮助我在运行时有条件地映射?我需要一个可以根据输入参数(我的问题示例中的 showAdvanced 布尔值)表现不同的地图。
    • 你能做什么你可以在自动映射器中的源对象“ItemFilterBlockGroup”类中添加一个这个节目,然后你可以在类型转换器的“ConvertCore”方法中放置一个if条件并检查如果它是真的这样做,否则这样做。那有意义吗。内部类 AccountConverter : TypeConverter { protected override OD.IAccount ConvertCore(PD.IAccount source) { if(source.showAdvanced) .....
    • 所以你是在建议我用只与我的观点相关的数据污染我的对象模型?这不是我愿意做的事情。
    • 在 automapper 中还有另一种方法可以进行更改,但这也不是很优雅,如下所示。如果这对您也不起作用,那么在 automapper 中没有更简洁的方法可以做到这一点,您需要有一个条件映射,如上面“XtremeBytes”发布的那样。 --- bool showAdvanced = false; var tt = Mapper.Map(a1, e => e.AfterMap((a, b) => b.Name =showAdvanced ? "test" : a.Name)); ---
    【解决方案2】:

    您想使用不属于对象的外部参数来控制逻辑。我相信最好的方法是使用一个映射,并根据您的标志过滤输入对象。像这样的

                var blockGroupsTemp;
                if (showAdvanced) 
                    blockGroupsTemp = blockGroups;
                else
                {
                    blockGroupsTemp = blockGroups.Where(x => x.Advanced == false).ToList();
                    blockGroupsTemp.ForEach(s => s.ChildGroups.RemoveAll(y => y.Advanced == true));
                }
    
                var mappedViewModels = Mapper.Map<ObservableCollection<ItemFilterBlockGroupViewModel>>(blockGroupsTemp);

    【讨论】:

    • 那只过滤根对象的子对象 - 我需要过滤每个级别的子对象,这就是我的问题中的第二个示例 CreateMap 所做的。
    • 我的意思是对源对象进行过滤,而不是使用映射器。我用过滤子组的选项更新了答案。
    • 您的答案仍然只过滤一级子组 - 层次结构可能有无限深的层次。我希望做的是避免手动进行这种模型操作,因为 AutoMapper 显然能够做到这一点,因为我的问题中的第二个 CreateMap 语句就是这样做的。
    • Okie 我看到了您尝试使用 Automapper 进行递归的方式。您是否尝试过像这里的配置映射stackoverflow.com/questions/14266108/…
    【解决方案3】:

    您可以使用上下文选项Items 集合在运行时将值传递给映射函数:

    var showAdvanced = true;
    var mappedViewModels = Mapper.Map<ObservableCollection<ItemFilterBlockGroupViewModel>>(
        blockGroups, 
        options => options.Items["includeAdvanced"] = showAdvanced);
    

    并在上下文可用于构造目标对象的任何地方使用它们。 在ResolveUsingConstructUsing 方法中例如:

    Mapper.CreateMap<ItemFilterBlockGroup, ItemFilterBlockGroupViewModel>()
        .ForMember(destination => destination.ChildGroups, options => options.ResolveUsing(
            (resolution) =>
            {
                var includeAdvanced = (bool)resolution.Context.Options.Items["includeAdvanced"];
                var source = (ItemFilterBlockGroup)resolution.Context.SourceValue;
                if(includeAdvanced)
                    return source.ChildGroups;
                else
                    return source.ChildGroups.Where(c => c.Advanced == false);               
             }));
    


    如果使用弱类型的字典值来传递标志参数对您来说看起来不那么漂亮,我建议将此逻辑封装在两个单独的方法中,如 Martin Fowler article 示例。

    【讨论】:

    • 这看起来更符合我的想法 - 我在理解如何构建视图模型时遇到了一些麻烦。因为视图模型与被映射的类型相同,所以我不能在 ConstructUsing 调用中使用 Mapper.Map。 AutoMapper 似乎可以正常处理这种递归,但似乎通过使用 ConstructUsing 我会失去这个功能。在这一点上,我可能根本不使用 AutoMapper,而只是编写一个递归方法来进行映射。
    • 我真正想做的是这样的:Mapper.CreateMap&lt;ItemFilterBlockGroup, ItemFilterBlockGroupViewModel&gt;().ForMember(dest =&gt; dest.ChildGroups, opts =&gt; opts.MapFrom(from =&gt; (bool)context.Options.Items["showAdvanced"] ? from.ChildGroups : from.ChildGroups.Where(c =&gt; c.Advanced == false))) 但我认为不可能从 ForMember 中访问 Options.Items。
    • @XVar,是的,我正在考虑。尝试更新版本的地图创建代码
    猜你喜欢
    • 1970-01-01
    • 2017-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-17
    • 1970-01-01
    • 2021-12-23
    相关资源
    最近更新 更多