2016-07-07 5 views
1

私のような性質を持つテーブルがあります。EF:サブサブ子どもを含むすべての子を持つ重複したオブジェクト(ディープコピー)

Id Name ParentId

ParentIdがプライマリ列Idへの外部キーです。

NULL 
/ \ 
    1  2 
     /\ 
     3 4 

は今、私たちはそのParentId、それはすべてのサブオブジェクトだとNULLである行オブジェクトをコピーしたいと言うことができます(行だけからParentIdを示す):今、私は次のように数行を持って言うことができます。

var row = db.FirstOrDefault(x=> x.Id == 1); 
    var new_row = new Table1(); 
    var subrows = row.Table1.ToArray(); 
     foreach(var row in subrows) 
     { 
      db.Entry(row).State = System.Data.Entity.EntityState.Detached; 
     } 
     new_row.Table1 = subrows; 
db.Table.Add(new_row); 
db.saveChanges(); 

結果:私は唯一の下位レベルがコピーされていることを想定しています

NULL 
/ \ 
    1  2 

:新規のような構造を挿入します。すべてのサブレベルをコピー/挿入するには?

EDIT:デタッチは1つのレベルまでのコピーの作成を支援しているので、これは私が試したものです:

private void RecursiveDetach(Table1 parent) 
     { 
      var subrows = parent.Table1.ToArray(); 
      foreach (var row in subrows) 
      { 
       if(row.Table1.Count() > 0) 
       { 
        RecursiveDetach(row); 
       } 
       db.Entry(row).State = System.Data.Entity.EntityState.Detached; 
      } 
     } 

しかし、今私はエラーを取得しています:

Collection was modified; enumeration operation may not execute.

+0

自己参照関係で作業しているか、2つの異なるエンティティが関係していますか? – octavioccl

+0

私はあなたがここでより多くのコンテキスト 'Entities db = new Entities();'と尋ねたいとは思っていませんでした。ここでは外部キーがどのように定義されています。 [テーブル]([Id]) ' –

答えて

0

私は」これを前にしなければならなかった。私は純粋にコードで行い、再帰的にオブジェクトをコピーし、必要に応じてユニークなIDをサニタイズしましたが、私が作成した最もクリーンなアプローチはオブジェクトをXMLにシリアライズし、新しいオブジェクトにデシリアライズすることです。この方法はあまり効率的ではありませんが、非常に柔軟で実装が簡単です。

//Save object to XML file. Returns filename. 
public string SaveObjectAsXML(int id) 
{ 
    //however you get your EF context and disable proxy creation 
    var db = GetContext(); 
    bool currentProxySetting = db.Configuration.ProxyCreationEnabled; 
    db.Configuration.ProxyCreationEnabled = false; 

    //get the data 
    var item = db.GetItem(id); //retrieval be unique to your setup, but I have 
           //a more generic solution if you need it. Make 
           //sure you have all the sub items included 
           //in your object or they won't be saved. 
    db.Configuration.ProxyCreationEnabled = currentProxySetting; 

    //if no item is found, do whatever needs to be done 
    if (item == null) 
    {     
     return string.Empty; 
    }    

    //I actually write my data to a file so I can save states if needed, but you could 
    //modify the method to just spit out the XML instead 
    Directory.CreateDirectory(DATA_PATH); //make sure path exists to prevent write errors 
    string path = $"{DATA_PATH}{id}{DATA_EXT}"; 
    var bf = new BinaryFormatter(); 
    using (FileStream fs = new FileStream(path, FileMode.Create)) 
    { 
     bf.Serialize(fs, repair); 
    } 

    return path; 
} 

//Load object from XML file. Returns ID. 
public int LoadXMLData(string path) 
{ 
    //make sure the file exists 
    if (!File.Exists(path)) 
    { 
     throw new Exception("File not found."); 
    } 

    //load data from file 
    try 
    { 
     using (FileStream fs = new FileStream(path, FileMode.Open)) 
     { 
      var item = (YourItemType)new BinaryFormatter().Deserialize(fs); 
      db.YourItemTypes.Add(item); 
      db.SaveChanges(); 
      return item.Id; 
     } 
    } 
    catch (Exception ex) { 
     //Exceptions here are common when copying between databases where differences in config entries result in mis-matches 
     throw; 
    } 
} 

使い方は簡単です。

//save object 
var savedObjectFilename = SaveObjectAsXML(myObjID); 

//loading the item will create a copy 
var newID = LoadXMLData(savedObjectFilename); 

最高の運!

0

ここでは全く別の答えがあります。親オブジェクトだけではなく、オブジェクト全体を再帰的に切り離します。あなたのコンテキストオブジェクトに拡張メソッドとして記述され、以下:

/// <summary> 
    /// Recursively detaches item and sub-items from EF. Assumes that all sub-objects are properties (not fields). 
    /// </summary> 
    /// <param name="item">The item to detach</param> 
    /// <param name="recursionDepth">Number of levels to go before stopping. object.Property is 1, object.Property.SubProperty is 2, and so on.</param> 
    public static void DetachAll(this DbContext db, object item, int recursionDepth = 3) 
    { 
     //Exit if no remaining recursion depth 
     if (recursionDepth <= 0) return; 

     //detach this object 
     db.Entry(item).State = EntityState.Detached; 

     //get reflection data for all the properties we mean to detach 
     Type t = item.GetType(); 
     var properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance) 
          .Where(p => p.GetSetMethod()?.IsPublic == true) //get only properties we can set        
          .Where(p => p.PropertyType.IsClass)    //only classes can be EF objects 
          .Where(p => p.PropertyType != typeof(string)) //oh, strings. What a pain. 
          .Where(p => p.GetValue(item) != null);   //only get set properties 

     //if we're recursing, we'll check here to make sure we should keep going 
     if (properties.Count() == 0) return; 

     foreach (var p in properties) 
     { 
      //handle generics 
      if (p.PropertyType.IsGenericType) 
      { 
       //assume its Enumerable. More logic can be built here if that's not true. 
       IEnumerable collection = (IEnumerable)p.GetValue(item); 
       foreach (var obj in collection) 
       { 
        db.Entry(obj).State = EntityState.Detached; 
        DetachAll(db, obj, recursionDepth - 1); 
       } 
      } 
      else 
      { 
       var obj = p.GetValue(item); 
       db.Entry(obj).State = EntityState.Detached; 
       DetachAll(db, obj, recursionDepth - 1); 
      } 
     } 
    } 

設定型のプロパティになります外を見るために最大のもの - 直接オブジェクトに関連していないデータを表すオブジェクトを。これらは競合を引き起こす可能性があるため、オブジェクトにオブジェクトが含まれていないことを確認することをお勧めします。

注:

このアプローチは、コピーしたいすべてのサブオブジェクトは遅延ロードを避け、事前に移入されている必要があります。これを確実にするために、私は私のEFの問い合わせについては、以下の拡張子を使用:

//Given a custom context object such that CustomContext inherits from DbContext AND contains an arbitrary number of DbSet collections 
//which represent the data in the database (i.e. DbSet<MyObject>), this method fetches a queryable collection of object type T which 
//will preload sub-objects specified by the array of expressions (includeExpressions) in the form o => o.SubObject. 
public static IQueryable<T> GetQueryable<T>(this CustomContext context, params Expression<Func<T, object>>[] includeExpressions) where T : class 
{ 
    //look through the context for a dbset of the specified type 
    var property = typeof(CustomContext).GetProperties().Where(p => p.PropertyType.IsGenericType && 
                    p.PropertyType.GetGenericArguments()[0] == typeof(T)).FirstOrDefault(); 

    //if the property wasn't found, we don't have the queryable object. Throw exception 
    if (property == null) throw new Exception("No queryable context object found for Type " + typeof(T).Name); 

    //create a result of that type, then assign it to the dataset 
    IQueryable<T> source = (IQueryable<T>)property.GetValue(context); 

    //return 
    return includeExpressions.Aggregate(source, (current, expression) => current.Include(expression)); 
} 

この方法は、あなたがDbContextから継承して、オブジェクトのDbSet<>コレクションを含むカスタムコンテキストオブジェクトを持っていることを前提としています。適切なものが見つかるでしょうDbSet<T>そしてあなたのオブジェクトの指定されたサブクラスをあらかじめロードするクエリ可能なコレクションを返します。これらは式の配列として指定されます。たとえば:

//example for object type 'Order' 
var includes = new Expression<Func<Order, object>>[] { 
    o => o.SalesItems.Select(p => p.Discounts), //load the 'SalesItems' collection AND the `Discounts` collection for each SalesItem 
    o => o.Config.PriceList,     //load the Config object AND the PriceList sub-object 
    o => o.Tenders,        //load the 'Tenders' collection 
    o => o.Customer        //load the 'Customer' object 
}; 

は私の照会可能なコレクションを取得するには、私は今のようなそれを呼び出す:

var queryableOrders = context.GetQueryable(includes); 

を繰り返しますが、ここでの目的は、熱心にのみサブオブジェクトをロードします照会可能オブジェクトを作成することですあなたが実際に望むサブオブジェクト(とサブサブオブジェクト)を作成します。

、特定のアイテムを取得し、他の照会可能源のようにこれを使用するには:あなたはまた、表現をインラインで提供することができます

var order = context.GetQueryable(includes).FirstOrDefault(o => o.OrderNumber == myOrderNumber); 

注意。ただし、汎用を指定する必要があります。

//you can provide includes inline if you just have a couple 
var order = context.GetQueryable<Order>(o => o.Tenders, o => o.SalesItems).FirstOrDefault(o => o.OrderNumber == myOrderNumber); 
+0

再帰の深さが不明な場合はどうなりますか? –

+0

私は何かを試して質問を修正しました。 'コレクション変更'エラーを解決する簡単な方法があるかどうかを提案してください。 –

+0

循環参照がない限り、再帰チェックは不要です(つまり、 'Person.Address.State.Person')。その場合、再帰の深さを削除することができます。また、コレクションの変更されたエラーも取得されません。 – Daniel

関連する問題