树形结构中通用的深度限制深度克隆

3
我正在使用Entity Framework的Code First开发。问题是:在克隆延迟加载列表时,列表元素类型为:System.Data.Entity.DynamicProxies.Node_CB2936E7A8389F56009639CD3D732E4B509C4467531A6AFB3A143429D77A07DF,而我的通用函数将其视为System.Object。有没有办法在将它们传递给Clone函数之前将此对象转换为其父类?或者有其他想法吗?
因为我只需要复制特定深度的内容,所以我不能序列化整个树形结构,然后再反序列化它。
我的模型是:
public class Node
{
    public int Id { get; set; }

    public String Name { get; set; } 

    public virtual IList<Node> Nodes { get; set; } 

    public int? ParentId { get; set; }

    [ForeignKey("ParentId")]
    public virtual Node Parent { get; set; }
}

使用函数进行克隆:

protected T Clone<T>(T entity, int depth) where T : new()
{
    var cloned = new T();
    foreach (var property in cloned.GetType().GetProperties())
    {
        if (property.PropertyType.Namespace == "System" && property.CanWrite)
        {
            property.SetValue(cloned, property.GetValue(entity));
        }
        else if (depth > 0 && property.CanWrite)
        {
            if (property.PropertyType.Namespace == "System.Collections.Generic")
            {
                var type = property.PropertyType.GetGenericArguments()[0];
                Type genericListType = typeof(List<>).MakeGenericType(type);
                var collection = (IList)Activator.CreateInstance(genericListType);
                var value = property.GetValue(entity);
                foreach (var element in value as IEnumerable)
                {
                    collection.Add(Clone(element, depth - 1));  // here is Error:
                        //The value “System.Object” is not of type “Sandbox.Models.Node” and cannot be used in this generic collection. Parameter name: value
                        //I should cast element to its parent class but how?
                }
                property.SetValue(cloned, collection);
            }
        }
    }
    return cloned;
}

这个函数在非实体框架对象上运作得很好。 Clone 函数的使用方法如下:

var cloned = Clone(context.Nodes.Find(10), 2);

需要帮助请告知。

1个回答

1
你遇到的问题是,在foreach循环中,var的类型将会是Object,因此在对Clone进行内部调用时,new T();将执行new Object()而不是new WhateverTheTypeTheListHad()。你需要使用反射来进行新的调用。
protected T Clone<T>(T entity, int depth) where T : new()
{
    return (T)CloneInternal(entity, depth);
}

private object CloneInternal(object entity, int depth)
{
    var cloned = Activator.CreateInstance(entity.GetType());

    foreach (var property in cloned.GetType().GetProperties())
    {
        if (property.PropertyType.Namespace == "System" && property.CanWrite)
        {
            property.SetValue(cloned, property.GetValue(entity));
        }
        else if (depth > 0 && property.CanWrite)
        {
            if (property.PropertyType.Namespace == "System.Collections.Generic")
            {
                var type = property.PropertyType.GetGenericArguments()[0];
                Type genericListType = typeof(List<>).MakeGenericType(type);
                var collection = (IList)Activator.CreateInstance(genericListType);
                var value = property.GetValue(entity);
                foreach (var element in value as IEnumerable)
                {
                    collection.Add(CloneInternal(element, depth - 1));
                }
                property.SetValue(cloned, collection);
            }
        }
    }
    return cloned;
}

由于递归调用不依赖于传递的类型,我更喜欢将逻辑拆分并使递归内部版本非通用化,只需向下传递Object。这使您更容易发现错误的假设(例如在Object上调用new)。
然而,您的代码还有其他问题。例如,您对任何在System.Collections.Generic中的类型进行内部递归,但始终创建List,如果集合是其他内容(例如EF中常见的HashSet),则会在property.SetValue(cloned,collection)调用中失败。
这是一个快速重写,以处理实现IList、IList和ISet(并具有默认构造函数)的任何内容,这应该涵盖您遇到的90%的所有集合。
    private object CloneInternal(object entity, int depth)
    {
        var cloned = Activator.CreateInstance(entity.GetType());

        foreach (var property in cloned.GetType().GetProperties())
        {
            Type propertyType = property.PropertyType;
            if (propertyType.Namespace == "System" && property.CanWrite)
            {
                property.SetValue(cloned, property.GetValue(entity));
            }
            else if (depth > 0 && property.CanWrite && typeof(IEnumerable).IsAssignableFrom(propertyType))
            {
                if (typeof(IList).IsAssignableFrom(propertyType))
                {
                    var collection = (IList)Activator.CreateInstance(propertyType);
                    var value = property.GetValue(entity);
                    foreach (var element in value as IEnumerable)
                    {
                        collection.Add(CloneInternal(element, depth - 1));
                    }
                    property.SetValue(cloned, collection);
                }
                else if (propertyType.IsGenericType)
                {
                    var type = propertyType.GetGenericArguments().Single();
                    if (typeof(IList<>).MakeGenericType(type).IsAssignableFrom(propertyType) ||
                        typeof(ISet<>).MakeGenericType(type).IsAssignableFrom(propertyType))
                    {
                        var collection = Activator.CreateInstance(propertyType);
                        var addMethod = collection.GetType().GetMethod("Add");
                        var value = property.GetValue(entity);
                        foreach (var element in value as IEnumerable)
                        {
                            addMethod.Invoke(collection, new[] {CloneInternal(element, depth - 1)});
                        }
                        property.SetValue(cloned, collection);
                    }
                }
            }
        }
        return cloned;
    }

感谢您清晰而有帮助的解释。它非常有效。还要感谢您指出其他问题。 - py3r3str
@py3r3str 我添加了一个更新,展示如何修复集合问题,它可以更加健壮,但是它给你一个开始。 - Scott Chamberlain
好的。再次感谢。 - py3r3str

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接