C#Automapper 6.0.2继续抛出StackOverflowException

时间:2017-05-22 23:58:16

标签: c# automapper

初始化映射器

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; }
}

如您所见,该实体继承自包含一堆默认集合的SocialEntitySocialEntity然后继承基础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);
    }

结果
Unit test memory usage

如您所见,内存在~20秒后泛滥 看起来像某个地方的automapper被配置错误导致无限循环并导致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>();
}

3 个答案:

答案 0 :(得分:0)

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

我可以看到MediaItem有一个&#34;文章&#34; property,Article有一个MediaItems集合。

如果您的域模型也具有这些导航属性的等价物,那么您是否需要它们两种方式?

答案 1 :(得分:0)

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

实体框架没有任何问题,只有AutoMapper。

我删除了大部分导航属性,似乎我并不需要大部分导航属性。这解决了我的问题快速&amp;简单的方法!

答案 2 :(得分:-1)

就我而言,我的系统中有一个这样的结构(DTO和实体模型都相同)。有计划列表,只有其中一些计划会引发StackOverflowException。实体框架在具有多个嵌套级别的情况下表现不错,但是AutoMapper在大约3个级别上会失败。幸运的是,不需要PreviousPlan属性,因此我将其删除,并且工作正常。如果您无力删除它,我建议您使用Automapper的MaxDepth。

我知道这并不是真正的答案,但也许可以帮助人们找到原因。

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