web-dev-qa-db-ja.com

オブジェクトグラフを複製し、オブジェクトの関係を維持する方法

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のリストについてはその逆です)。しかし、私の実際の実装は非常に複雑で、関係に固有のカスタムクローニングメソッドを作成することも、維持するのが面倒です。

3
Superman.Lopez

オブジェクトグラフをコピーする問題とオブジェクトグラフに新しいGUIDを割り当てる問題は非常に似ていますが、個別に処理できるため、まずコピーから始めましょう。

オブジェクトグラフが単なるツリーの場合、より簡単な解決策があるかもしれませんが、コメントでGreenおよびYellowオブジェクトへの参照を共有するいくつかのMainObjectsについて言及したので、グラフはツリーではないと仮定します。次の解決策は質問の特定の例では簡略化できますが、実際のグラフはより複雑であるため、任意のオブジェクトグラフで機能する一般的な答えを示します。

私はこれを3段階で解決します:

  1. オブジェクトグラフ内のすべてのオブジェクトを決定します。これは、標準の グラフトラバーサル (単純な深さ優先検索のような)で、すでにアクセスしたオブジェクトが保持されている_Hashmap<object>_を使用して実行できます。コレクションは、オブジェクトを2回訪問しないように機能し、最後に、グラフ内のすべてのオブジェクトを保持します。他のオブジェクトへの参照を保持するすべてのオブジェクトには、このためにAddReferences(Hashmap<object> alreadyVisited)メソッドのようなものが必要になります。

  2. 見つかったオブジェクトの浅いコピーを作成します。このため、ICloneableを使用して、各オブジェクトに MemberwiseClone interface を実装させるのが最善です。結果をDictionaryに入れ、各「古い」オブジェクトをそのクローンにマップします。

  3. ディクショナリがすべての「新しい」オブジェクトに渡される2番目のグラフトラバーサルを作成すると、新しいクローンに対する「古い」オブジェクトへのすべての参照を置き換えることができます。このためには、オブジェクトにReplaceReferences(Dictionary<object,object> objectMap)のようなメソッドが必要です。

注ここでは、オブジェクトへの参照をキーとして使用できると想定しました(EqualsメソッドとGetHashCodeメソッドをオーバーライドした場合は、これを確認してください SO post )。

GUID生成/置換は同様の方法で実装できます:

  1. すべてのGUIDを決定するためのグラフ走査

  2. 新しいGUIDの生成と、各「古い」GUIDを関連する新しいGUIDにマップする辞書への入力

  3. GUIDを置き換えるためのグラフ走査

いいえ、これを解決またはサポートできるライブラリはありません。ライブラリは、カスタムオブジェクトグラフ内のどの参照が自分自身がグラフに属しているか、そしてグラフの「外部」にあるオブジェクトへの参照だけを知っているわけではないことに注意してください。完全に自動的に。

1
Doc Brown
  1. メンバーごとにすべての緑と黄色のオブジェクトを複製する
  2. 緑と黄色のオブジェクトのリストを調べ、それらのガイドを更新する
  3. 辞書に新旧のGUIDペアを追加する
  4. 緑と黄色のオブジェクトのリストをもう一度歩き、前の手順で作成したディクショナリを使用して参照を交換します

Protobufやavroなどのオブジェクト参照をサポートする形式にシリアル化してから逆シリアル化することもできます。ただし、GUIDではなく実際のオブジェクト参照が必要です。

0
Martin K

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);
0
Superman.Lopez