树层次结构中具有深度限制的通用深度克隆

时间:2014-12-09 16:03:51

标签: c# entity-framework generics

我正在使用Code First开发与Entity Framework。问题是: 克隆延迟加载列表时,列表元素的类型为:

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 个答案:

答案 0 :(得分:1)

您的foreach循环中var所遇到的问题属于Object类型,因此new T();内部调用Clone将执行{{1}而不是new Object()。你需要做的是使用反射进行新的调用。

new WhateverTheTypeTheListHad()

因为递归调用不依赖于知道传递的类型,所以我更喜欢将逻辑拆分并使递归内部版本非泛型并且只传递Object。当你有错误的假设时(例如在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; } 上调用new),它会更加明显。

但是,您的代码还有其他问题。例如,你对Object中的任何类型进行内部递归,但是你总是创建一个System.Collections.Generic,该集合是其他的(例如List<genericListType>,在EF中非常常见)您的代码将在HashSet<genericListType>来电时失败。

这是一个快速重写来处理任何包含property.SetValue(cloned, collection);IListIList<T>(并且有一个默认构造函数)的内容,它应该覆盖您将遇到的所有集合中的90%

ISet<T>