簡體   English   中英

樹層次結構中具有深度限制的通用深度克隆

[英]Generic deep cloning with depth limitation in tree hierarchy

我正在使用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);

任何幫助,將不勝感激。

你遇到的問題是你的foreach循環中的var將是Object類型所以new T(); 在你Clone的內部調用將執行一個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<genericListType> ,如果該集合是其他東西(例如, HashSet<genericListType> ,在EF中非常常見)您的代碼將失敗的property.SetValue(cloned, collection); 呼叫。

這是一個快速重寫來處理任何暗示IListIList<T>ISet<T> (並且有一個默認構造函數)的東西,它應該覆蓋你將遇到的所有集合的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;
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM