[英]Deep cloning via Automapper ignoring specific property from the hierarchy
[英]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);
呼叫。
這是一個快速重寫來處理任何暗示IList
, IList<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.