使用Automapper时出现循环引用导致堆栈溢出问题

20

我正在使用Automapper将我的NHibernate代理对象(DTO)映射到我的CSLA业务对象。

我正在使用Fluent NHibernate创建映射 - 这部分工作正常。

我的问题是,Order有一组OrderLines,每个OrderLine都引用了Order

public class OrderMapping : ClassMap<OrderDTO>
{
    public OrderMapping()
    {
        // Standard properties
        Id(x => x.OrderId);
        Map(x => x.OrderDate);
        Map(x => x.Address);

        HasMany<OrderLineDTO>(x => x.OrderLines).KeyColumn("OrderId").Inverse();

        Table("`Order`");
    }
}

public class OrderDTO
{
    // Standard properties
    public virtual int OrderId { get; set; }
    public virtual DateTime OrderDate { get; set; }
    public virtual string Address { get; set; }

    // Child collection properties
    public virtual IList<OrderLineDTO> OrderLines { get; set; } <-- this refs the lines
}

并且:

public class OrderLineMapping : ClassMap<OrderLineDTO>
{
    public OrderLineMapping()
    {
        // Standard properties
        Id(x => x.OrderLineId);
        References<OrderDTO>(x => x.Order).Column("OrderId");
        Map(x => x.Description);
        Map(x => x.Amount);

        Table("`OrderLine`");
    }
}

public class OrderLineDTO
{
    // Standard properties
    public virtual int OrderLineId { get; set; }
    public virtual string Description { get; set; }
    public virtual decimal Amount { get; set; }

    public virtual OrderDTO Order { get; set; } // <-- this refs the order
}

这些DTO对象分别映射到OrderOrderLines CSLA对象。

当自动映射到OrderLines时,会映射一个OrderLinesDTO列表。然后,自动映射将在行的"Order"属性上映射,该属性映射回Order,然后循环地映射回OrderLine,再映射回Order,如此循环。

是否有人知道Automapper是否可以避免这种循环引用?


4
稍等一下——该死的笔记本电脑触点在我还没完成之前就发布了,真是个愚蠢的设计! - Charleh
没有上下文,很难给出完整的答案...也许只需将导致圆形的属性标记为[IgnoreMap] - Marc Gravell
抱歉,我的笔记本电脑有一个那种蓝色的小点,而鼠标碰巧悬停在“提问”按钮上——键盘中心附近的任何活动都可能引发随机“点击”!我不知道[IgnoreMap]属性。我会生成一些类,所以如果它有效,我会尝试将其插入到生成器中。 - Charleh
实际上,[IgnoreMap] 似乎总是忽略对属性的映射 - 我想要能够将其映射到 OrderLines 上的 Order 属性 - 但是这个 Order 属性包含了对行的父级的引用,因此存在循环引用。 - Charleh
1
目前(AM 6.1.1),正确的答案是这个 - Lucian Bargaoanu
显示剩余2条评论
6个回答

23

在Automapper配置中:

Mapper.Map<OrderLine, OrderLineDTO>()
    .ForMember(m => m.Order, opt => opt.Ignore());

Mapper.Map<Order, OrderDTO>()
    .AfterMap((src, dest) => { 
         foreach(var i in dest.OrderLines) 
             i.Order = dest;
         });

3
谢谢你的回复,Steve。但我希望避免特定类型,因为我正在尝试通过通用类型按约定映射,并将映射代码的数量几乎降至零。我的泛型不会意识到派生类型上的任何属性名称,所以可能需要创建一个虚拟方法来维护这种关联。 - Charleh
我会把这个作为答案 - 我使用了这种方法,但通过使用代码生成来创建这些关系,成功避免了手动编写的麻烦。 - Charleh
你能详细说明一下吗?需要什么具体内容呢?我想要一个发票包括发票行,但不包括发票本身。然而,当我加载一个发票行时,它仍然应该能够在其中获取一个发票,但不包括该发票的发票行。我已经关闭了延迟加载(EF),所以EF应该确保只加载我已经包含的内容。这些问题让我考虑是否要切换回手动操作。 - CularBytes

11

我使用EF 6和AutoMapper 6时遇到了相同的问题,显然Kenny Lucero发布的内容让我找到了解决方案。以下是AM网站摘录:

// Circular references between users and groups
cfg.CreateMap<User, UserDto>().PreserveReferences();

在两个模型中添加PreserveReferences()使其正常工作。


正如文档所述,在较新的版本中,这将默认工作而无需任何设置。 - Lucian Bargaoanu
1
由于这是谷歌上与该问题相关的最高搜索结果,因此我已将被接受的答案移至此处,因为它在当前时刻更加正确,我不希望人们直接跳转到当时有效的原始答案而忽略了新信息(在这个不断发展的技术领域中!) - Charleh

3
由于这是#1谷歌搜索结果,我想可能有像我一样的一些人来到这里,在将对象(通过ASP.NET)发送到客户端时遇到麻烦,因此它被JSON序列化,而不是得到stackoverflow异常。
所以当我加载一个发票并使用Linq-to-SQL .Include(x=>x.InvoiceLines)时,我得到了错误,因为每个InvoiceLine再次包含相同的发票,这就是我拥有的相同结构。
为解决此问题,请在ASP.NET Core Startup类中执行以下操作:
services.AddMvc().AddJsonOptions(o =>
{
    o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    o.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
    o.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
    // ^^ IMPORTANT PART ^^
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

当您向应用程序添加MVC时,请在您的JsonConfiguration中包含o.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;。JSON.Net采取额外步骤,为每个引用设置一个名为“$id”的元属性。当JSON.Net在对象图的另一个位置遇到同一实例时,它只需放置对原始实例的引用,而不是复制数据,因此不会导致循环引用问题!这样,我就不必进一步编辑我的AutoMapper配置了。来源:https://johnnycode.com/2012/04/10/serializing-circular-references-with-json-net-and-entity-framework/

谢谢您。您的回答让我意识到错误并不与automapper有关! - Enrico

3
我遇到了同样的问题,通过降级到4.2.1版本解决了它。显然检查循环引用是很耗费资源的,所以他们默认不检查。Migrating to AutoMapper 5 - Circular references 据说这些是v5+的设置方法,但对于我的数据模型来说并没有起作用,因为我们选择了复杂的DTO关系而不是每个操作都使用单一的DTO。
// Self-referential mapping
cfg.CreateMap<Category, CategoryDto>().MaxDepth(3);

// Circular references between users and groups
cfg.CreateMap<User, UserDto>().PreserveReferences();

http://docs.automapper.org/en/stable/5.0-Upgrade-Guide.html#circular-references

Automapper应该能够在v6.1+中静态确定循环引用设置,所以如果在版本v6.1+中无法自动工作,请联系Automapper团队。


1
正如文档中所述,在更新的版本中,这将默认工作,无需任何设置。 - Lucian Bargaoanu
2
是的,如果不行,他们鼓励开发人员提交工单,因为软件并不完美,可能默认情况下无法适用于您的数据模型。但是,如果您有紧急期限,而自动映射团队没有及时回复,那么还原可能会有所帮助。 - Kenny Lucero
目前没有关于循环引用的已知问题。更有可能是使用错误。 - Lucian Bargaoanu

2
如果有人使用 Mapster(一款与AutoMapper相同的C#映射库),则:
TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .PreserveReference(true);

需要用于防止堆栈溢出错误。

0

不确定我应该在这里发布:

在一个方法中进行automapper.map后,我遇到了相同的错误。 CularBytes的答案让我思考问题并非与automapper有关,而是与json有关。

我做了以下操作:

Return ok(_service.getDataById(id));

而不是

Return ok(await _service.getDataById(id));

(我忘记等待异步调用...这是新手的错误,我知道)


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