Circular reference To a model but needs to Parent in child for display

labjac

Member
Joined
May 26, 2022
Messages
9
Programming Experience
1-3
Hallo

We have a complex nested model which consist of numerous parents and child. eg..

MESSAGE --> HEAD ---> WAVE --> ODH[ARRAY] --> ODL[ARRAY]
--> SHUFF[ARRAY]

etc, and more nested.

The issue is I want to be able to add the parameters of WAVE into the ODH model. but it complains about circular reference when posting to the model.
Model example:
 public class WAVE_WCSWAVE : WAVE
    {
        public int ODHId { get; set; }
        public List<ODH_WCSWAVE>? ODH { get; set; }
    }

    public class INVDET_WCSWAVE : INVDET
    {
        public SKUCON_WCSWAVE? SKUCON { get; set; }
    }

    public class SKUCON_WCSWAVE : SKUCON
    {
    }

    public class ODH_WCSWAVE : ODH
    {
        //[Column("WAVE_WCSWAVEId")]
        //public int WaveId { get; set; }
        //public WAVE_WCSWAVE? Wave { get; set; }
        public List<ODL_WCSWAVE>? ODL { get; set; }
        public ODHUDF_WCSWAVE? ODHUDF { get; set; }
    }

The error when posting message below.. (I don't want to use json Preserve because is creates $ref and $id which is visible in the json string.

Error Message:
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.DSV_WCSWAVE0430.HEAD.WAVE.ODH.Wave.ODH.Wave.ODH.Wave.ODH.Wave.ODH.Wave.ODH.Wave.ODH.Wave.ODH.Wave.ODH.Wave.ODH.
   at System.Text.Json.ThrowHelper.ThrowJsonException_SerializerCycleDetected(Int32 maxDepth)

I have also tried adding the below code to the program.cs file with the converter class..

Configuration:
builder.Services.Configure<JsonSerializerOptions>(options =>
{
    options.ReferenceHandler = ReferenceHandler.Preserve;
    options.Converters.Add(new ObjectCycleConverter<DSVWCSWAVE0430_Inbound>());
    options.Converters.Add(new ObjectCycleConverter<HEAD_WCSWAVE>());
    options.Converters.Add(new ObjectCycleConverter<WAVE_WCSWAVE>());
    options.Converters.Add(new ObjectCycleConverter<ODH_WCSWAVE>());
    options.Converters.Add(new ObjectCycleConverter<ODL_WCSWAVE>());
});

Converter:
 public class ObjectCycleConverter<T> : JsonConverter<T>
    {
        private readonly Dictionary<string, T> _referenceCache = new Dictionary<string, T>();

        public override bool CanConvert(Type typeToConvert)
        {
            return typeof(T).IsAssignableFrom(typeToConvert);
        }

        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return JsonSerializer.Deserialize<T>(ref reader, options);
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            var referenceId = GetReferenceId(value);
            if (!string.IsNullOrEmpty(referenceId))
            {
                writer.WriteStringValue(referenceId);
                return;
            }

            referenceId = Guid.NewGuid().ToString();
            _referenceCache.Add(referenceId, value);

            writer.WriteStartObject();

            var properties = typeof(T).GetProperties();
            foreach (var property in properties)
            {
                writer.WritePropertyName(property.Name);
                var propertyValue = property.GetValue(value);
                JsonSerializer.Serialize(writer, propertyValue, property.PropertyType, options);
            }

            writer.WriteEndObject();
        }

        private string GetReferenceId(T value)
        {
            foreach (var kvp in _referenceCache)
            {
                if (ReferenceEquals(kvp.Value, value))
                {
                    return kvp.Key;
                }
            }
            return null;
        }
    }
 
If that circular reference is only used for display, then only have it in the View Model, and not part of the actual data Model. That way you can load and save your Model without issues, and only have to relink the circular references when you are populating your View Model in preparation for display.
 
Thanks, sorry might be stupid question, but I only have the one model, or rather nested models. Do I create another model based on the data model for view purposes? (maybe derive from the data model and create a viewModel with the added properties required?
 
A view model is a model that is created specifically for the use of the view. The data model is for how data is stored in a database, or transported over a network. The view model can simply map to members of the data model, or it can synthesize data derived from the data model.

For example, you could have a data model that looks like this:
C#:
record Car(string Model, int Year);
record Driver(string Name, DateTime BirthDate);
record Team(string Name, string Owner);
record TeamDriver(Team Team, Car Car, Driver Driver);

and a view model that looks like this:
C#:
record TeamDriverViewModel(TeamDriver TeamDriver)
{
   // Pulled from TeamDrive
    public string Name => TeamDriver.Driver.Name;
    public string TeamName => TeamDriver.Team.Name;
    public string Model => TeamDriver.Car.Model;
    public string Year => TeamDriver.Car.Year

   // Synthesized from TeamDrive
    public bool IsSenior { TeamDriver.Driver.BirthDate - DateTime.Now > TimeSpan.FromDays(365 * 65); }
}
 
Back
Top Bottom