20

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?

Charleh
  • 13,749
  • 3
  • 37
  • 57
  • 4
    Hang on - damn keyboard nipple posted it before I finished, stupid laptop! – Charleh Jul 16 '12 at 13:16
  • exception? stack? ....?? –  Jul 16 '12 at 13:17
  • No context, so hard to give a full answer... maybe just `[IgnoreMap]` the property that causes the circle? – Marc Gravell Jul 16 '12 at 13:22
  • Soz my laptop has one of those blue nipples and the mouse happened to be hovering over the 'Ask Question' button - any activity near the centre of the keyboard can throw a random 'click'! Wasn't aware of the `[IgnoreMap]` attribute. I code gen some of the classes so I'll see if I can plug this into the gen if it works – Charleh Jul 16 '12 at 13:25
  • Actually looks like `[IgnoreMap]` always ignores mapping to a property - I want to be able to map to an `Order` property on my `OrderLines` - but this `Order` property contains a reference to the parent of the lines, therefore there is circular referencing – Charleh Jul 16 '12 at 13:27
  • As far as I can see this won't work - I'm calling Map() more than once (child objects are taken from an IList to populate the CSLA collection types). Automapper tracks references when a single call to Map is made, but won't work when multiple calls to Map are made in the same mapping run. I'll see if I can re-jig the code – Charleh Jul 16 '12 at 13:48
  • 1
    At this time (AM 6.1.1) the right answer is [this](https://stackoverflow.com/questions/45298110/how-to-ignore-property-of-property-in-automapper-mapping/45300519#45300519). – Lucian Bargaoanu Jul 27 '17 at 09:10

6 Answers6

23

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;
         });
Steve Czetty
  • 6,147
  • 9
  • 39
  • 48
  • 3
    Thanks for this Steve, but I was hoping to stay away from being specific to the type since I'm trying to map via convention through a generic type and keep the amount of mapping code to virtually nil. My generics won't be aware of any property names on the derived types so it might have to be a case of creating a virtual method for the derived type to maintain this association. – Charleh Jul 16 '12 at 14:13
  • I'll take this as the answer - I used this approach but managed to avoid having to write this manually by using code-gen to create these relationships. – Charleh Jul 16 '12 at 16:20
  • Could you elaborate? What exactly is needed for what? I want a Invoice to include InvoiceLines, but don't include Invoice again. However when I load a invoiceLine, it should still be abble to also get a Invoice within it, but not including the invoiceLines of that invoice again.. I have lazy loading (EF) turned off so EF should be making sure to only load what I have Included. These kind of things makes me consider switching back to manually doing things. – CularBytes Apr 16 '19 at 17:35
11

I was having the same issue using EF 6 and AutoMapper 6. Apparently what Kenny Lucero posted led me to the solution. Here's an extract from AM site:

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

Adding PreserveReferences() to both models made it work.

kurdemol94
  • 358
  • 5
  • 12
  • As it says in the docs, in newer versions this works by default without any settings. – Lucian Bargaoanu Jul 21 '19 at 15:01
  • 1
    Since this is the top search result on Google for the issue I've moved the accepted answer to this since it's more correct at this current moment in time and I wouldn't want people jumping directly to the original answer that worked at the time without considering the new info (in this ever evolving landscape of technology!) – Charleh Oct 23 '19 at 11:09
3

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.

Kenny Lucero
  • 134
  • 7
  • 1
    As it says in the docs, in newer versions this works by default without any settings. – Lucian Bargaoanu Nov 03 '18 at 06:18
  • 2
    Yes and if it doesn't they encourage developers to submit a ticket because software isn't perfect and it might not work for your data model by default. But if you are on a deadline, and the automapper team doesn't respond in time reverting might help. – Kenny Lucero Nov 03 '18 at 15:48
  • There is no known AM issue about circular references. A usage error is much more likely. – Lucian Bargaoanu Nov 04 '18 at 07:18
3

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.

CularBytes
  • 9,924
  • 8
  • 76
  • 101
2

If anyone using Mapster (a mapping library for C# same as AutoMapper)

TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .PreserveReference(true);

need to be used for preventing stack overflow error.

e03050
  • 1,392
  • 15
  • 12
0

Not sure if I should post it here:

I had the same error after doing an automapper.map in a method. The answer of CularBytes got me thinking that the issue was not automapper related but json related.

I did:

Return ok(_service.getDataById(id));

instead of

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

(I forgot to await an asyc call... rookie mistake I know)

Enrico
  • 2,734
  • 1
  • 27
  • 40