【问题标题】:C# Automapper 6.0.2 Keeps throwing StackOverflowExceptionC# Automapper 6.0.2 不断抛出 StackOverflowException
【发布时间】:2017-05-22 23:58:16
【问题描述】:

初始化映射器

public static class MapperConfig
{
    public const int MAX_MAPPING_DEPTH = 2;

    private static MapperConfiguration _config;
    private static IMapper _mapper;
    public static IMapper Mapper => _mapper ?? (_mapper = GetConfig().CreateMapper());

    public static MapperConfiguration GetConfig()
    {
        var assembly = Assembly.GetExecutingAssembly();
        if (_config != null)
            return _config;

        _config = new MapperConfiguration(cfg =>
        {
            cfg.AddProfiles(assembly);
            cfg.ForAllMaps(ConfigTypeMapping);
        });

        _config.AssertConfigurationIsValid();
        return _config;
    }


    public static void ConfigTypeMapping(TypeMap map, IMappingExpression expression)
    {
        map.ShouldCheckForValid();

        expression.PreserveReferences();
        expression.MaxDepth(MAX_MAPPING_DEPTH);
        //expression.ForAllMembers(m => m.UseDestinationValue());
    }
}

主要实体

[ResourceKey("MEDIA_ITEM")]
public class MediaItem : SocialEntity
{
    /// <summary>
    ///     User-defined media item <see cref="Title"/> or its original file name.
    /// </summary>
    public string Title { get; set; }

    /// <summary>
    ///     The original <see cref="FileName"/>.
    /// </summary>
    public string FileName { get; set; }

    /// <summary>
    ///     The <see cref="FileGuid"/> that refers to the CDN file entry.
    /// </summary>
    public Guid FileGuid { get; set; }

    /// <summary>
    ///     The purpose / group (aka <see cref="Type"/>) for this <see cref="MediaItem"/>.
    /// </summary>
    public FileType Type { get; set; }

    /// <summary>
    ///     Allows limiting retrieval of this <see cref="MediaItem"/> depending on a specific <see cref="PrivacyLevel"/>.
    /// </summary>
    public PrivacyLevel PrivacyLevel { get; set; }

    /// <summary>
    ///     The <see cref="Source"/> for the Content / File of this <see cref="MediaItem"/>.
    /// </summary>
    public virtual Url Source { get; set; }
    public Guid? SourceGuid { get; set; }

    /// <summary>
    ///     The <see cref="Profile"/> entity that this media file is bound to.
    /// </summary>
    public virtual Profile Profile { get; set; }
    public Guid? ProfileGuid { get; set; }

    /// <summary>
    ///     The <see cref="Article"/> entity that this media file is bound to.
    /// </summary>
    public virtual Article Article { get; set; }
    public Guid? ArticleGuid { get; set; }

    /// <summary>
    ///     The <see cref="Communication.Comment"/> entity that this media file is bound to.
    /// </summary>
    public virtual Comment Comment { get; set; }
    public Guid? CommentGuid { get; set; }

    /// <summary>
    ///     The <see cref="Theme"/> entity that this media file is bound to.
    /// </summary>
    public virtual Theme Theme { get; set; }
    public Guid? ThemeGuid { get; set; }
}

如您所见,该实体继承自 SocialEntity,该 SocialEntity 包含一堆默认集合。然后,SocialEntity 继承基类 Entity 以及 Id、用户、创建日期等。

基类映射

CreateMap<SocialEntity, SocialDomainModel>()
       .IncludeBase<Entity, DomainModel>()
       .ReverseMap()
       .IncludeBase<DomainModel, Entity>();

CreateMap<Entity, DomainModel>()
       .IncludeBase<global::Data.Pattern.Entity.Entity, DomainModel>()
       .ReverseMap()
       .IncludeBase<DomainModel, global::Data.Pattern.Entity.Entity>();

CreateMap<global::Data.Pattern.Entity.Entity, DomainModel>()
       .ForMember(dm => dm.Creator, mo => mo.Ignore())
       .ForMember(dm => dm.CreatorGuid, mo => mo.Ignore())
       .ReverseMap();

CreateMap<MediaItem, Domain.Models.Storage.MediaItem>()
       .IncludeBase<SocialEntity, SocialDomainModel>()
       .ReverseMap()
       .IncludeBase<SocialDomainModel, SocialEntity>();


测试方法

[TestMethod]
    public void MapMediaTest()
    {
        var m = MapperConfig.Mapper;

        var entity = new MediaItem();

        var entityToDomain = m.Map<Domain.Models.Storage.MediaItem>(entity);
        Assert.IsTrue(entityToDomain != null);
    }

结果

如您所见,大约 20 秒后内存泛滥。
看起来自动映射器在某处配置错误,导致无限循环并导致StackOverflowException

我的尝试

  • 我尝试了每一个实体,结果总是一样。
  • 在 5.1.1 和 6.0.2 之间切换了很多次。
  • 尝试了很多不同的配置。
  • 已将HashSet 集合切换为正常ICollections

我在实体和域模型之间进行映射,没有使用投影/查询。 在映射之前检索每个实体,没有延迟加载。 请注意,我确实有 virtual 关键字与其他实体和类,并且默认情况下初始化每个集合。

我别无选择,除了放弃(不是这样)AutoMapper 以换取其他东西之外,我想不出还能做什么。我想要一些额外的见解,谢谢!

编辑
Article 类。

/// <summary>
///     Used to store <see cref="Article"/> data.
/// </summary>
[ResourceKey("ARTICLE")]
public class Article : SocialEntity
{
    /// <summary>
    ///     The main <see cref="Title"/> of this <see cref="Article"/>.
    /// </summary>
    public string Title { get; set; } = string.Empty;

    /// <summary>
    ///     The content <see cref="Body"/> of this <see cref="Article"/>.
    /// </summary>
    public string Body { get; set; } = string.Empty;

    /// <summary>
    ///     The <see cref="ShortDescription"/> of this <see cref="Article"/>.
    /// </summary>
    public string ShortDescription { get; set; } = string.Empty;

    /// <summary>
    ///     The long <see cref="Description"/> of this <see cref="Article"/>.
    /// </summary>
    public string Description { get; set; } = string.Empty;

    /// <summary>
    ///     The <see cref="DateTime"/> that the <see cref="Entity"/> will be available to use.
    /// </summary>
    public DateTime ExpiresOn { get; set; }

    public virtual HashSet<Url> Sources { get; set; } = new HashSet<Url>();
    public virtual HashSet<MediaItem> Media { get; set; } = new HashSet<MediaItem>();
    public virtual HashSet<Tag> Tags { get; set; } = new HashSet<Tag>();
    public virtual HashSet<Hobby> Hobbies { get; set; } = new HashSet<Hobby>();
}

【问题讨论】:

  • 为什么需要IncludeBase 电话?我很确定这是默认设置。既然你打电话给ReverseMap,你为什么还要明确地创建反向地图
  • IncludeBase 电话是我解决此问题的众多尝试之一。据我了解,ReverseMap 调用确保双向映射已正确初始化,如果这些现在是多余的,我很乐意删除它们。
  • 删除那些并且请确保也没有循环依赖。同样要进行故障排除,请从一个映射开始,然后继续添加更多映射,直到您发现问题的原因是哪个映射。
  • 关于它,我很快就会得到结果。感谢您的帮助!
  • 好吧,我已经隔离了大部分 CreateMapping 方法,当我为 Article 类添加映射时,StackOverflowException 开始重新出现。由于没有看到任何循环引用,因此我不知道如何继续。

标签: c# automapper


【解决方案1】:

@Richard 的回答让我走上了正轨!导航属性太多... AutoMapper 无法弄清楚如何将它们全部映射,可能缺少配置。

Entity Framework 对它们没有任何问题,只是 AutoMapper。

我删除了大部分导航属性,似乎我不需要其中的大部分。这以快速简便的方式解决了我的问题!

【讨论】:

    【解决方案2】:

    根据我使用 Automapper 的经验,在映射循环引用时会遇到 StackOverflowException。

    我可以看到 MediaItem 有一个“Article”属性,而 Article 有一个 MediaItem 的集合。

    如果您的域模型也具有这些导航属性的等价物,您是否同时需要它们?

    【讨论】:

    • 是的,应该可以使用 automapper。好像MaxDepth(2) 工作不正常。
    【解决方案3】:

    就我而言,我的系统中有这样的结构(DTO 和实体模型都是相同的)。有计划列表,只有其中一些会抛出 StackOverflowException。 Entity Framework 在这样的多层嵌套中做得很好,但 AutoMapper 在大约 3 层时会失败。幸运的是,不需要 PreviousPlan 属性,所以我只是删除了它,它工作正常。如果您负担不起移除它的费用,我建议您使用 Automapper 的 MaxDepth。

    我知道这不是一个真正的答案,但也许会帮助某人找到原因。

    public class Plan
    {
        public int Id { get; set; }
        public int? PreviousPlanId { get; set; }
        public Plan PreviousPlan { get; set; }
        ...
    }
    

    【讨论】:

      猜你喜欢
      • 2016-09-12
      • 2015-03-25
      • 2021-01-25
      • 1970-01-01
      • 1970-01-01
      • 2011-05-18
      • 1970-01-01
      • 2015-11-29
      • 2021-03-25
      相关资源
      最近更新 更多