I have an entity that has nested references down to 8th level of depth.
I have tried using System.Text.Json.Serializer with option `ReferenceHandler'
Also JsonConvert.SerializeObject with its option ReferenceHandler.Ignore
Both of the above mentioned options could not able to clone; i was never getting back to the callee function. Max i waited was 10 minutes.
I guess I will have to re-invent the clone functionality.
Meanwhile my question would be : are there other optimal alternative options i can work with? that supports cloning of DB entities and handles reference circulation.
UPDATE
Below is the recursive functionality i have created using the Reflection technology of C#. It handles the reference circular dependency as well as has the feature to skip entities that are not needed to be cloned; in my case these were the 'reference tables' and with it i am able to clone entities to Nth level in microseconds/seconds however i am certain it can be more optimized and concised.
public object Clone(string[] skipEntites, Type circularType = null)
{
var newEntity = Activator.CreateInstance(this.GetType());
PropertyInfo[] entityProperties = newEntity.GetType().GetProperties();
foreach (PropertyInfo entityProperty in entityProperties)
{
var value = this.GetType().GetProperty(entityProperty.Name).GetValue(this);
if (TryGetArgumentTypesFromHashSet(value, out Type[] argumentTypes))
{
if (circularType != null && circularType.Name.Contains(argumentTypes[0].Name))
{
continue;
}
Type hashSetType = typeof(HashSet<>).MakeGenericType(argumentTypes[0]);
var hashSet = Activator.CreateInstance(hashSetType);
MethodInfo addMethod = hashSetType.GetMethod("Add");
var entities = (IEnumerable)value;
foreach (IEntity entity in entities)
{
addMethod.Invoke(hashSet, new object[] { entity.Clone(skipEntites, GetType()) });
}
SetValue(entityProperty, hashSet, newEntity, skipEntites);
continue;
}
if (value != null && circularType != null && circularType.Name.Contains(value.GetType().Name))
{
continue;
}
if (circularType != null
&& circularType.Name.Contains(entityProperty.Name.Substring(0, entityProperty.Name.Length-2))
&& entityProperty.Name.Contains("Id")
&& (entityProperty.PropertyType == typeof(int)
|| entityProperty.PropertyType == typeof(long)
|| entityProperty.PropertyType == typeof(int?)
|| entityProperty.PropertyType == typeof(long?))
)
{
entityProperty.SetValue(newEntity, 0);
continue;
}
SetValue(entityProperty, value, newEntity, skipEntites);
}
return newEntity;
}
private bool TryGetArgumentTypesFromHashSet(object value, out Type[] argumentTypes)
{
argumentTypes = null;
if (value == null)
{
return false;
}
Type type = value.GetType();
argumentTypes = type.GetGenericArguments();
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>) && argumentTypes.Length > 0;
}
private void SetValue(PropertyInfo entityProperty, object value, object newEntity, string[] skipEntites)
{
if (value != null && skipEntites.Any(x => value.GetType().Name.Contains(x)))
{
return;
}
if (value is IEntity referenceObject)
{
var newReference = referenceObject.Clone(skipEntites, GetType());
entityProperty.SetValue(newEntity, newReference);
return;
}
if (entityProperty.Name == "Id" && (entityProperty.PropertyType == typeof(int) || entityProperty.PropertyType == typeof(long)))
{
entityProperty.SetValue(newEntity, 0);
return;
}
entityProperty.SetValue(newEntity, value);
}