0

I have a circular reference exception when serializing a c# model object (I'm using Entity Framework, and the object is the model passed to the view that comes from a LINQ query).

(It does not matter whether I use System.Web.Script.Serialization.JavaScriptSerializer() or Newtonsoft.Json.JsonConvert.SerializeObject(Model)).

I found which properties are causing the circular reference and come to the conclusion that setting them to null (as I don't need them in the view) could be a solution, but for any reason I cannot set them to null successfully.

@section scripts{
    @Html.Partial("_GoogleMap", "")
    @Scripts.Render("~/bundles/google_maps_plugins")
    var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
    <script type="text/javascript" defer>
        $(function () {
            my_app.finca.InicializarFormulario(@Html.Raw(serializer.Serialize(Model)));
        });
    </script>
}

...

var q =
    from
        finca in db.finca
    join consecion in db.concesion
        on finca.concesion_id equals consecion.concesion_id
    join oficina in db.oficina
        on finca.oficina_mas_cercana_id equals oficina.oficina_id
    join planificacion in db.planificacion
        on finca.planificacion_defecto_id equals planificacion.planificacion_id
    join ruta in db.ruta
        on finca.ruta_defecto_id equals ruta.ruta_id
    join acometida in db.acometida
        on finca.acometida_defecto_id equals acometida.acometida_id
    where
        finca.finca_id == finca_id
    select new FincaViewModel
    {
        descripcion = finca.descripcion,
        finca_id = finca.finca_id,
        oficina_mas_cercana_id = finca.oficina_mas_cercana_id,
        oficina = finca.oficina.nombre,
        planificacion_id = finca.planificacion_defecto_id,
        planificacion = finca.planificacion.nombre_planificacion,
        ruta_id = finca.ruta_defecto_id,
        ruta = ruta.descripcion,
        acometida_id = finca.acometida_defecto_id,
        acometida = acometida.nombre,
        direccion_id = finca.direccion_id,
        codigo_gis = finca.codigo_gis,
        concesion_id = finca.concesion_id,
        concesion = finca.concesion.nombre_concesion,
        disponible_contratacion_bit = finca.disponible_contratacion,
        disponible_contratacion = finca.disponible_contratacion ? General.Si : General.No,
        numero_alternativo = finca.numero_alternativo,
        fecha_ultima_inspeccion = finca.fecha_ultima_inspeccion,
        latitud = finca.latitud,
        longitud = finca.longitud,
        ps = finca.ps,
        orden_trabajo = finca.orden_trabajo
    };

var f = await q.FirstOrDefaultAsync();
f.ps = RemoveCircularReference(f.ps);

...

private ICollection<ps> RemoveCircularReference(ICollection<ps> ps) {
    ps.ToList().ForEach(p => p.contrato.ToList().ForEach(c => 
    {
        c.cliente.tipo_cliente.cliente = null;
        c.cliente.cliente_historial = null;
    }));

    return ps;
}

The fact is that after RemoveCircularReference the properties are still not null, which is nonsense. Weirdly, if I set a breakpoint after the method and launch it again the properties are indeed set to null, but not the first time :s

Edit 1: Adding "finca" class.

public class FincaViewModel
{
    public int finca_id { get; set; }
    public string descripcion { get; set; }
    public DateTime fecha_creacion { get; set; }
    public string disponible_contratacion { get; set; }
    public bool disponible_contratacion_bit { get; set; }
    public int concesion_id { get; set; }
    public string concesion { get; set; }
    public string codigo_gis { get; set; }
    public string numero_alternativo { get; set; }
    public DateTime? fecha_ultima_inspeccion { get; set; }
    public decimal? latitud { get; set; }
    public decimal? longitud { get; set; }
    public int oficina_mas_cercana_id { get; set; }
    public int direccion_id { get; set; }
    public string direccion { get; set; }
    public short ambito_id { get; set; }
    public string ambito { get; set; }
    public ICollection<ps> ps { get; set; }
    public ICollection<PSFincaViewModel> psvm { get; set; }
    public int pscount { get; set; }
    public int ctcount { get; set; }
    public string oficina { get; set; }
    public int planificacion_id { get; set; }
    public int planificacion_defecto_id { get; set; }
    public string planificacion { get; set; }
    public int ruta_id { get; set; }
    public int ruta_defecto_id { get; set; }
    public string ruta { get; set; }
    public ICollection<orden_trabajo> orden_trabajo { get; set; }
    public ICollection<FichaOtViewModel> orden_trabajo_vm { get; set; }
    public string orden_trabajo_tipo { get; set; }
    public int acometida_id { get; set; }
    public int acometida_defecto_id { get; set; }
    public string acometida { get; set; }
    public ICollection<AcometidaViewModel> acometidas { get; set; }
}

Edit 2: Lazy loading.

I don't know if it may have something to do but I have:

db.Configuration.LazyLoadingEnabled = false;

I've tried to set it to true just to test, but in that case f.ps.contrato is not fetch.

Edit 3: Lazy loading (part 2).

I'm quite sure the issue has to do with lazy, but I don't know yet how to limit recursion in serializer.

Edit 4: App ends unexpectedly when trying the link suggested by @GSerg.

The link seems to be useful, but when using: var settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }; my app just "gets crazy" and ends unexpectedly

Edit 5: New attempt with JsonConvert.

I've also tried with JsonConvert using

var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; 

to no avail (the same result), the app ends unexpectedly, what makes me suspect I'm running into memory problems or whatever. Well, I'm stuck, and I'll have to change direction in finding a solution.

Diego Perez
  • 2,188
  • 2
  • 30
  • 58
  • What is `finca.ps`? Is it delay loaded? Is your `RemoveCircularReference` in fact attempting to do `foreach (var c in ps.SelectMany(p => p.contrato)) { c.cliente... = null; }`? – GSerg Jan 23 '23 at 15:45
  • Thanks for your reply @GSerg. finca.ps: public ICollection ps { get; set; } (ps is a table handled by entity framework). And I have LazyLoading enabled (db.Configuration.LazyLoadingEnabled = true). Lastly, I'm trying to avoid the circular reference when serializing the way you can see in my code in the RemoveCircularReference method, don't know if there is another better way. BTW, I've added the finca -another table- class, in case it helps. – Diego Perez Jan 24 '23 at 07:31
  • Does this answer your question? [How Do You "Really" Serialize Circular Referencing Objects With Newtonsoft.Json?](https://stackoverflow.com/questions/26434738/how-do-you-really-serialize-circular-referencing-objects-with-newtonsoft-json) – GSerg Jan 24 '23 at 08:45
  • Hi and thanks for pointing this @GSerg. The link seems to be useful, but when using: var settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }; my app just "gets crazy" and ends unexpectedly. – Diego Perez Jan 24 '23 at 09:29
  • I've also tried with JsonConvert using var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; to no avail (the same result), the app ends unexpectedly, what makes me suspect I'm running into memory problems or whatever. Well, I'm stuck, and I'll have to change direction in finding a solution. – Diego Perez Jan 24 '23 at 09:42

1 Answers1

0

After many different unsuccessful attempts on avoiding/skipping the circular reference when serializing I decided to change the direction on finding a solution.

The problem regarding not being able to set null to the culprit properties was the lazy loading, but disabling it would prevent "contratos" in "ps" to be fetched.

What I ended doing is to disable lazy loading and loading the properties in question manually (instead of the Entity Framework auto asigning).

db.Configuration.LazyLoadingEnabled = false;

var q =
        from
            finca in db.finca
        join ...
        where ...
        select new FincaViewModel
        {
            ...
            ps = finca.ps,
            orden_trabajo = finca.orden_trabajo
        };

    var f = await q.FirstOrDefaultAsync();
    f.acometidas = acometidasFinca;
    f.psvm = ObtenerPS(f.ps);
    f.orden_trabajo_vm = await ObtenerOT(f.orden_trabajo);

    f.ps = ContratosPorPs(f.ps);

    return f;

And

public ICollection<ps> ContratosPorPs(ICollection<ps> ps)
{
    foreach (var p in ps)
    {
        p.contrato = new ContratoOperations().ContratosPorPs(p.ps_id);
    }

    return ps;
}

Now I can serialize with no circular reference exception.

Diego Perez
  • 2,188
  • 2
  • 30
  • 58