Angular 2+クライアントを使用するASP.netコアアプリケーションでは、複雑なオブジェクトグラフを使用しています。オブジェクトグラフには、相互に参照するオブジェクトがいくつかあります。下の図では、MainObject
がグラフを表しています。
public class MainObject
{
public int Id { get; set; }
public ObjectProperty MyProperty { get; set; }
public List<GreenObject> GreenObjects { get; set; }
public List<YellowObject> YellowObjects { get; set; }
}
public class GreenObject
{
public int Id { get; set; }
public Guid Guid { get; set; }
public GreenObjectProperties MyProperty { get; set; }
public Guid? YellowObjectRef { get; set; }
}
public class YellowObject
{
public int Id { get; set; }
public Guid Guid { get; set; }
public YellowProperties MyProperty { get; set; }
public Guid? GreenObjectRef { get; set; }
}
ここで、MainObject
のクローンを作成して、オブジェクトグラフ内のオブジェクト間の関係を維持するだけでなく、永続化できるようにする必要があります。つまり、クローンに新しいUUID/GUIDが必要で(自分のDBに永続化できるようにするため)、クローンされたオブジェクトとの関係、したがって新しく作成されたUUID/GUIDの関係を引き続き尊重します。
ASP.netアプリケーションで既にAutomapperを使用しているので、このライブラリを使用してクローンを作成することを検討していました。しかし、他のライブラリを検討したり、クライアント側で複製を実行したりすることもできます。
質問:オブジェクトグラフを簡単に複製して、新しく作成されたオブジェクト/ GUIDへの参照を維持しながら新しいUUID/GUIDを作成するための共通のパターンまたはライブラリはありますか?
単純化すると、GreenObject
のリストを簡単に反復処理して、新しいGUIDを検索し、YellowObject
を見つけることができます。ここで、GreenObjectRef
は元の= GUIDそしてそれを新しいGUIDで置き換えます(およびYellowObject
のリストについてはその逆です)。しかし、私の実際の実装は非常に複雑で、関係に固有のカスタムクローニングメソッドを作成することも、維持するのが面倒です。
オブジェクトグラフをコピーする問題とオブジェクトグラフに新しいGUIDを割り当てる問題は非常に似ていますが、個別に処理できるため、まずコピーから始めましょう。
オブジェクトグラフが単なるツリーの場合、より簡単な解決策があるかもしれませんが、コメントでGreenおよびYellowオブジェクトへの参照を共有するいくつかのMainObject
sについて言及したので、グラフはツリーではないと仮定します。次の解決策は質問の特定の例では簡略化できますが、実際のグラフはより複雑であるため、任意のオブジェクトグラフで機能する一般的な答えを示します。
私はこれを3段階で解決します:
オブジェクトグラフ内のすべてのオブジェクトを決定します。これは、標準の グラフトラバーサル (単純な深さ優先検索のような)で、すでにアクセスしたオブジェクトが保持されている_Hashmap<object>
_を使用して実行できます。コレクションは、オブジェクトを2回訪問しないように機能し、最後に、グラフ内のすべてのオブジェクトを保持します。他のオブジェクトへの参照を保持するすべてのオブジェクトには、このためにAddReferences(Hashmap<object> alreadyVisited)
メソッドのようなものが必要になります。
見つかったオブジェクトの浅いコピーを作成します。このため、ICloneable
を使用して、各オブジェクトに MemberwiseClone
interface を実装させるのが最善です。結果をDictionary
に入れ、各「古い」オブジェクトをそのクローンにマップします。
ディクショナリがすべての「新しい」オブジェクトに渡される2番目のグラフトラバーサルを作成すると、新しいクローンに対する「古い」オブジェクトへのすべての参照を置き換えることができます。このためには、オブジェクトにReplaceReferences(Dictionary<object,object> objectMap)
のようなメソッドが必要です。
注ここでは、オブジェクトへの参照をキーとして使用できると想定しました(Equals
メソッドとGetHashCode
メソッドをオーバーライドした場合は、これを確認してください SO post )。
GUID生成/置換は同様の方法で実装できます:
すべてのGUIDを決定するためのグラフ走査
新しいGUIDの生成と、各「古い」GUIDを関連する新しいGUIDにマップする辞書への入力
GUIDを置き換えるためのグラフ走査
いいえ、これを解決またはサポートできるライブラリはありません。ライブラリは、カスタムオブジェクトグラフ内のどの参照が自分自身がグラフに属しているか、そしてグラフの「外部」にあるオブジェクトへの参照だけを知っているわけではないことに注意してください。完全に自動的に。
Protobufやavroなどのオブジェクト参照をサポートする形式にシリアル化してから逆シリアル化することもできます。ただし、GUIDではなく実際のオブジェクト参照が必要です。
Doc Brownが推奨するGUID生成/置換の手順に従います。これにSO回答すると、オブジェクトグラフのすべてのプロパティが再帰的に実行されます( https: //stackoverflow.com/a/20554262/10340388 )次のソリューションが付属しています。この関数は、GUIDを保持できるクラスだけでなくオブジェクトのコレクションでも再帰的です。おそらく、クリーンアップしてDRY-erにすることができます、しかし今のところそれは私のために働きます。
辞書の作成:
private Dictionary<Guid, Guid> CreateDictionaryOfGuids(object obj, Dictionary<Guid, Guid> guidDictionary)
{
Type objType = obj.GetType(); // Type of object
PropertyInfo[] typeProperties = objType.GetProperties(); // Properties of type
foreach (PropertyInfo property in typeProperties) // Go through all properties
{
object propValue = property.GetValue(obj, null); // Get the value of that property in Obj
Type propType = property.PropertyType;
var elems = propValue as IList; // cast the value of the property as IList
if (elems != null) // and see if it's valid if != null
{
foreach (var item in elems) // If IList become recursive into the collection
{
Type itemType = item.GetType();
if (PropertyTypeRequiresRecursive(itemType))
guidDictionary = CreateDictionaryOfGuids(item, guidDictionary);
}
}
else if (propType == typeof(Guid)) // Check if property is a Guid
{
Guid existingGuid = (Guid)property.GetValue(obj, null);
if (!guidDictionary.ContainsKey(existingGuid)) // If Guid doesn't exist in dictionary
{
Guid newGuid = Guid.NewGuid();
guidDictionary.Add(existingGuid, newGuid); // Add the old and the new Guid to the dictionary
}
}
else if (PropertyTypeRequiresRecursive(propType) && propValue != null) // Make method recursive into properties of that class
{
guidDictionary = CreateDictionaryOfGuids(propValue, guidDictionary);
}
}
return guidDictionary;
}
GUID値の置き換え:
private object ReplaceGuids(object obj, Dictionary<Guid, Guid> guidDictionary)
{
Type objType = obj.GetType(); // Type of object
PropertyInfo[] typeProperties = objType.GetProperties(); // Properties of type
foreach (PropertyInfo property in typeProperties) // Go through all properties
{
object propValue = property.GetValue(obj, null); // Get the value of that property in Obj
Type propType = property.PropertyType;
var elems = propValue as IList; // cast the value of the property as IList
if (elems != null) // and see if it's valid if != null
{
for (int i = 0; i < elems.Count; i++) // If IList become recursive into the collection
{
Type itemType = elems[i].GetType();
if (PropertyTypeRequiresRecursive(itemType))
elems[i] = ReplaceGuids(elems[i], guidDictionary);
}
} else if (propType == typeof(Guid)) // Check if property is a Guid
{
Guid existingGuid = (Guid)property.GetValue(obj, null);
if (guidDictionary.ContainsKey(existingGuid))
{
Guid newGuid = guidDictionary[existingGuid];
property.SetValue(obj, newGuid); // set to the new GUID from dictionary
}
} else if (PropertyTypeRequiresRecursive(propType) && propValue != null) // Make method recursive into properties of that class
{
object newPropValue = ReplaceGuids(propValue, guidDictionary);
property.SetValue(obj, newPropValue);
}
}
return obj;
}
private bool PropertyTypeRequiresRecursive(Type type)
{
bool propIsPrimitive = type.IsPrimitive;
if (propIsPrimitive == true || type == typeof(string) || type == typeof(DateTime)) return false;
return true;
}
次に、これを次のように呼びます。ここで、MainObject mainObject
はすでにディープコピー/クローンであると想定されています:
Dictionary<Guid, Guid> guidDictionary = new Dictionary<Guid, Guid>();
guidDictionary = CreateDictionaryOfGuids(mainObject, guidDictionary);
mainObject = (MainObject)ReplaceGuids(mainObject, guidDictionary);