David Pantea
David Pantea

Reputation: 101

Automapper Circular Reference Infinite loops

I have some problems with AutoMapper, the object that I mapped makes circular reference, and because of this I can't return it JSON to View using ActionResult.

I've made an DTO's object linked with another two.

 public class MasterJobsDTO
{
    public int function_id { get; set; }
    public string function_name { get; set; }
    public bool is_active { get; set; }
    public job_family job_family
    {
        get; set;

    }
    public functional_area functional_area
    {
        get; set;

    }
}

Function mode:

 public partial class function
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public function()
    {
        this.t_actual_organization = new HashSet<t_actual_organization>();
        this.t_actual_organization_split_position = new HashSet<t_actual_organization_split_position>();
    }

    public int function_id { get; set; }
    public string function_name { get; set; }
    public bool is_active { get; set; }
    public Nullable<int> job_family_id { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<t_actual_organization> t_actual_organization { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<t_actual_organization_split_position> t_actual_organization_split_position { get; set; }
    public virtual job_family job_family { get; set; }
}

Job_Family model:

public partial class job_family
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public job_family()
    {
        this.t_actual_organization = new HashSet<t_actual_organization>();
        this.t_actual_organization_split_position = new HashSet<t_actual_organization_split_position>();
        this.functions = new HashSet<function>();
    }

    public int job_family_id { get; set; }
    public string job_family_name { get; set; }
    public Nullable<int> functional_area_id { get; set; }

    public virtual functional_area functional_area { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<t_actual_organization> t_actual_organization { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<t_actual_organization_split_position> t_actual_organization_split_position { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<function> functions { get; set; }
}

Automapper config:

cfg.CreateMap<function, MasterJobsDTO>().MaxDepth(1).PreserveReferences()
        .ForMember(x => x.functional_area_id, opts => opts.MapFrom(source => source.job_family.functional_area.functional_area_id))
        .ForMember(x => x.functional_area_extended_name, opts => opts.MapFrom(source => source.job_family.functional_area.functional_area_extended_name))
        .ForMember(x => x.job_family_name, opts => opts.MapFrom(source => source.job_family.job_family_name))
        .ForMember(x => x.functional_area, opts => opts.MapFrom(source => source.job_family.functional_area))
        ;

function_area class:

 public partial class functional_area
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public functional_area()
    {
        this.job_family = new HashSet<job_family>();
        this.t_actual_organization = new HashSet<t_actual_organization>();
        this.t_actual_organization_split_position = new HashSet<t_actual_organization_split_position>();
    }

    public int functional_area_id { get; set; }
    public string functional_area_name { get; set; }
    public string functional_area_extended_name { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<job_family> job_family { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<t_actual_organization> t_actual_organization { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<t_actual_organization_split_position> t_actual_organization_split_position { get; set; }
}

And the call:

List<MasterJobsDTO> mjd = Mapper.Map<List<function>, List<MasterJobsDTO>>(data);

The error that I get in browser is:

A circular reference was detected while serializing an object of type 'System.Data.Entity.DynamicProxies.job_family_D3FE2013BDB6002B7BE94915E73AEA531401...

Upvotes: 0

Views: 4188

Answers (2)

Kenny Lucero
Kenny Lucero

Reputation: 134

Wait, so is the problem with automapper or is it that the json formatter can't handle the circular reference (CR)?

If it is the json you can configure your api to handle the CR. Here is a link to an overly academic example of how to have it ignore the CR. Here are the options for the setting. I was able to resolve the issue globally in my WebApiConfig.cs

Personally I'd rather be able to have the json be able to represent the data correctly than change my coding practices because I can only go X levels deep.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
        //this will ignore
        json.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        //this will serialize them to objects.
        json.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
        json.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
     }
}

I ran into a similar issue with new versions of automapper. Automapper is supposed to be able to statically figure out the CRs in 6.1+ but I had a very complex dto model with many CRs. I am waiting for the automapper team to resolve my issue. In the interim I reverted to 4.2.1.0 and everything worked. After I resolved the automapper exceptions I got a exception from the json formatter and the ignore configuration above solved my issue.

Here is what led me on the right track for the json issue.

Upvotes: 0

Nikola Radevic
Nikola Radevic

Reputation: 26

In your automapper config you can exclude the offending circular reference pointback.

.ForMember(dest => dest.OffendingVariable, source=> source.Ignore());

The resulting object you get after the automapper finishes will be "smaller" than the "entity" one and can be serialised to JSON without issue.

EDIT: If your true error lies in that you ultimately want to be able to serialise your "infinite" object into JSON, an you don't care about fixing it by fiddling with automapper i can propose "cropping" down the circular point backs of your object with something like this:

List<MasterJobsDTO> mjd = Mapper.Map<List<function>, List<MasterJobsDTO>>(data);

var jsonPrepMJD = new List<MasterJobsDTO>(from m in mjd
                            select new MasterJobsDTO()
                            {
                              id = m.id,
                              ...,
                              pointBackMember = new PointBackMember(){set all but the virtual pointback}
                            }.Cast<MasterJobsDTO>();

If the pointBackMember is a list then select from it and cast it too as deep as you need to go

jsonPrepMJD would then be serialisable.

Upvotes: 1

Related Questions