0

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...

Thank you!

David Pantea
  • 101
  • 2
  • 9

2 Answers2

1

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.

  • If I ignore the job_family property from MasterJobDTO, it will be null. I want to access from JavaScript job_family atribute (ex. masterJobDTO.job_family.job_family_name), then i want to send masterjob's object back to server maybe with new values. – David Pantea Apr 20 '18 at 12:36
  • Edited again to suggest the manual cropping out of the virtual pointback although it feels dirty :) – Nikola Radevic Apr 20 '18 at 12:45
  • the public virtual ICollection functions { get; set; } links to the class Function and tries to add it's tree to the node when serialising. class Function has public virtual job_family job_family { get; set; } which is where your loop begins – Nikola Radevic Apr 20 '18 at 13:01
  • You say to replace pointBackMember = new PointBackMember() with function_area = new function_area(){} ? – David Pantea Apr 20 '18 at 13:04
  • The problem with pointbacks is that they are used to drive organising an object from it's own perspective point. In circular pointbacks the resulting "true-map" tree you get differs based on which perspective you start at. I think you may have to "hard" define that yourself no two ways about it – Nikola Radevic Apr 20 '18 at 13:05
  • So for start point as MasterJobsDTO you traverse : MasterJobsDTO -> Job_Family -> Function -> Job_Family -> Function -> ... You want to stop it yourself at function since you are defining your serial tree start point at MasterJobsDTO – Nikola Radevic Apr 20 '18 at 13:11
  • Can there be no limit on how deep the tree is? – David Pantea Apr 20 '18 at 13:12
  • so pointBackMember that is causing problems is Function having virtual Job_family – Nikola Radevic Apr 20 '18 at 13:12
  • And how can i get new function_area? With another select from db? – David Pantea Apr 20 '18 at 13:13
  • There is a "limit" in that it cannot be infinity and the object variable is stored in a finite data size. – Nikola Radevic Apr 20 '18 at 13:14
  • functional_area = new functional_area() { functional_area_id=?? } }).Cast(); – David Pantea Apr 20 '18 at 13:15
  • If your functional_area class has no pointback issues (i cant see that class) you can just set it straight: functional_area = m.functional_area. If it has issues like job_family then you will do something like: functional_area = new functional_area() { functional_area_id= m.functional_area.id, ..., dont set the offending virtual } }).Cast(); – Nikola Radevic Apr 20 '18 at 13:18
  • Basically for each complex object: Set everything you need, discard anything you don't. If complex object has a complex property: Dig in; Set everything you need, discard anything you don't. You are manually excluding the particular parts which "restart the tree" – Nikola Radevic Apr 20 '18 at 13:21
  • I edited question, i putted function_area class code – David Pantea Apr 20 '18 at 13:29
  • If i want to remove public string job_family_name {get; set;} and others properties from DTO that are related to job_family... It's possible to apply your proposed solution? – David Pantea Apr 20 '18 at 13:36
  • Please take a look over MasterJobDTO object now – David Pantea Apr 20 '18 at 13:39
  • after looking at function_area class code is see that this too has a virtual pointer to job_family -> this indicates that the model design could have been optimised. I can present this in the form of two Json tree diagrams which both contain the "same" data in different ways. e.g. you can dig in to the job_family from MasterJobDTO->job_family or MasterJobDTO->functinal-area->job_family and get the same data redundantly. – Nikola Radevic Apr 20 '18 at 13:43
  • Avoiding design aspects however, the Json tree i suspect you want looks like: MasterJobDTO:{[stuff], job_family:{[stuff, function:{[stuff, NO job_family]} , NO functional_area]}, functional_area:{[stuff], NO job_family}} I recommend drawing out the tree on paper yourself and "humanly step-in" to see where the tree resets. I can spot 2 places: [functional_area : virtual job_family linked with job_family : virtual functional_area] [job_family : virtual function linked with function : virtual job_family] Up to you but one has to go, it changes the shape of the tree (same data) – Nikola Radevic Apr 20 '18 at 13:52
  • The new MasterJobDTO object helps visually simplify what we see. It removes some of the [stuff] but the same problem remains. – Nikola Radevic Apr 20 '18 at 13:58
  • sorry last json tree was terribad typod: try expand this: {[stuff],job_family:{[stuff],function:{[stuff],NO job_family},NO functional_area}}functional_area:{[stuff],NO job_family}} – Nikola Radevic Apr 20 '18 at 14:05
  • You are right about how i want to looks the object. – David Pantea Apr 20 '18 at 14:05
  • The MasterJobDTO is function object at the base... with job_family and functional_area as properties... – David Pantea Apr 20 '18 at 14:07
  • yes ideally (with some others your string function_name) but each of them have properties too. And job_family has a virtual to functional_area and visa versa... but only 1 can exist (yourchoice) and job_family has a property function which has a property that points back to job_family – Nikola Radevic Apr 20 '18 at 14:11
0

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.

Kenny Lucero
  • 134
  • 7
  • There is no known AM issue about circular references. A usage error is much more likely. – Lucian Bargaoanu Nov 04 '18 at 07:15
  • If you read the OP and my post instead of copy pasting the same comment you put on my other post. You'd see that my solution here is for resolving circular references in JSON not AM. but thanks for the downvoting both. – Kenny Lucero Nov 05 '18 at 16:05
  • I specifically downvoted for the AM content. Remove that and I'll undo. – Lucian Bargaoanu Nov 05 '18 at 16:44
  • Why are you downvoting the reverting option? does it not offer another solution? does it not solve the problem? I specifically say that it should work in the latest even give links to the documentation saying how to implement the AM approved solution and if it doesn't... Even the AM team make allowances for the cases where it might not work. – Kenny Lucero Nov 05 '18 at 17:09