Circular reference causing stack overflow with Automapper

I'm using Automapper to map my NHibernate proxy objects (DTO) to my CSLA business objects

I'm using Fluent NHibernate to create the mappings - this is working fine

The problem I have is that the Order has a collection of OrderLines and each of these has a reference to 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
}

and:

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
}

These DTO objects map to Order and OrderLines CSLA objects respectively

When auto-mapping to OrderLines a list of OrderLinesDTO is mapped. Auto mapper is then mapping the "Order" property on of the lines, which maps back to Order which then circularly maps back to OrderLine, then to Order and so on

Does anyone know if Automapper can avoid this circular reference?

Answers


In your Automapper configuration:

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

Since this is the #1 google search result, I think there might be some people, like me, coming here who don't get a stackoverflow exception, but find trouble when sending the object (via ASP.NET) to the client, and thus it being JSON serialized.

So I had the same structure in place, Invoices has multiple InvoiceLines, when I load an Invoice and use the Linq-to-SQL .Include(x => x.InvoiceLines) I get errors when I try to load the object from the Api because each InvoiceLine contains the same Invoice again.

To solve this, do the following in ASP.NET Core Startup class:

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

So include o.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects; in your JsonConfiguration when adding MVC to your application.

JSON.Net is taking the extra step to setup each reference with an additional meta-property called “$id”. When JSON.Net encounters the same instance in another place in the object graph, it simply drops a reference to the original instance, instead of duplicating the data, and thus not causing circular reference issues!

Source: https://johnnycode.com/2012/04/10/serializing-circular-references-with-json-net-and-entity-framework/

So now I don't have to further edit my AutoMapper configuration.


I was having the same issue and solved it by downgrading to version 4.2.1. apparently the checks for circular references was expensive so they made it default to not check. Migrating to AutoMapper 5 - Circular references

Supposedly these are supposed to be the settings methods for v 5+ but it didn't work for my data model because we opt'd for complex dto relationships instead of single use dtos for each action.

// 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 is supposed to be able to statically determine if the circular reference settings in v6.1+, So if it doesn't work for you automatically in version v6.1+ contact the automapper team.


Need Your Help

How to change the Font Size in a whole Application programmatically, Android?

android font-size android-fonts

I have created Spinner with the list of Font Sizes from "8" to "46" . I could be able to click the font Size and in a spinner it has shown me .

WPF share column width between separate grids

wpf grid user-controls styling

I have the following setup on my WPF UserControl: