web-dev-qa-db-ja.com

Entity Framework 5エンティティのディープコピー/クローン

Entity Framework 5(DBContext)を使用しており、エンティティをディープコピー(つまり、エンティティとすべての関連オブジェクトをコピー)してデータベースに新しいエンティティを保存する最良の方法を見つけようとしています。これどうやってするの? CloneHelperなどの拡張メソッドの使用を検討しましたが、DBContextに適用されるかどうかはわかりません。

69
kypk

エンティティを複製する安価で簡単な方法の1つは、次のようなことです。

var originalEntity = Context.MySet.AsNoTracking()
                             .FirstOrDefault(e => e.Id == 1);
Context.MySet.Add(originalEntity);
Context.SaveChanges();

ここでのコツはAsNoTracking()-このようなエンティティをロードすると、コンテキストはそれを認識せず、SaveChangesを呼び出すと、それを新しいエンティティのように扱います。

MySetMyPropertyへの参照があり、そのコピーも必要な場合は、Includeを使用します。

var originalEntity = Context.MySet.Include("MyProperty")
                            .AsNoTracking()
                            .FirstOrDefault(e => e.Id == 1);
122
Leo

別のオプションがあります。

クローン化するデータを取得するためにクエリを実行する必要がないため、場合によってはこの方法をお勧めします。このメソッドを使用して、データベースから既に取得したエンティティのクローンを作成できます。

//Get entity to be cloned
var source = Context.ExampleRows.FirstOrDefault();

//Create and add clone object to context before setting its values
var clone = new ExampleRow();
Context.ExampleRows.Add(clone);

//Copy values from source to clone
var sourceValues = Context.Entry(source).CurrentValues;
Context.Entry(clone).CurrentValues.SetValues(sourceValues);

//Change values of the copied entity
clone.ExampleProperty = "New Value";

//Insert clone with changes into database
Context.SaveChanges();

このメソッドは、ソースから現在の値を、追加された新しい行にコピーします。

21
Jas Laferriere

これは、一般的な複製を可能にする一般的な拡張方法です。

Nugetから_System.Linq.Dynamic_を取得する必要があります。

_    public TEntity Clone<TEntity>(this DbContext context, TEntity entity) where TEntity : class
    {

        var keyName = GetKeyName<TEntity>();
        var keyValue = context.Entry(entity).Property(keyName).CurrentValue;
        var keyType = typeof(TEntity).GetProperty(keyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).PropertyType;

        var dbSet = context.Set<TEntity>();
        var newEntity =  dbSet
            .Where(keyName + " = @0", keyValue)
            .AsNoTracking()
            .Single();

        context.Entry(newEntity).Property(keyName).CurrentValue = keyType.GetDefault();

        context.Add(newEntity);

        return newEntity;
    }
_

自分で実装する必要があるのは、GetKeyNameメソッドだけです。これは、return typeof(TEntity).Name + "Id"から_return the first guid property_までのいずれかであるか、DatabaseGenerated(DatabaseGeneratedOption.Identity)]でマークされた最初のプロパティを返します。

私の場合、すでにクラスに[DataServiceKeyAttribute("EntityId")]のマークを付けています

_    private string GetKeyName<TEntity>() where TEntity : class
    {
        return ((DataServiceKeyAttribute)typeof(TEntity)
           .GetCustomAttributes(typeof(DataServiceKeyAttribute), true).First())
           .KeyNames.Single();
    }
_
1

Entity Framework Coreでも同じ問題があり、子エンティティが遅延ロードされる場合、ディープクローンには複数の手順が含まれます。構造全体を複製する1つの方法は次のとおりです。

   var clonedItem = Context.Parent.AsNoTracking()
        .Include(u => u.Child1)
        .Include(u => u.Child2)
        // deep includes might go here (see ThenInclude)
        .FirstOrDefault(u => u.ParentId == parentId);

    // remove old id from parent
    clonedItem.ParentId = 0;

    // remove old ids from children
    clonedItem.Parent1.ForEach(x =>
    {
        x.Child1Id = 0;
        x.ParentId= 0;
    });
    clonedItem.Parent2.ForEach(x =>
    {
        x.Child2Id = 0;
        x.ParentId= 0;
    });

    // customize entities before inserting it

    // mark everything for insert
    Context.Parent.Add(clonedItem);

    // save everything in one single transaction
    Context.SaveChanges();

もちろん、すべてを積極的にロードしたり、すべてのキーの値をリセットしたりするための汎用関数を作成する方法がありますが、これにより、すべてのステップがより明確でカスタマイズ可能になります(たとえば、一部の子は、含める)。

1
Alexei