web-dev-qa-db-ja.com

Entity Framework 6:ID以外のオブジェクトを複製

私のMVVMプログラムには、Modelクラス(たとえばMyModel)があり、そこから(Entity Frameworkを使用して)データベースからの読み取りのインスタンスがあります。オブジェクトを取得するとき、すべてのデータをユーザーに提示しています。後でユーザーはいくつかのフィールドを変更します。
私が望むのは、IDID主キーおよび自動インクリメントであるため)以外は同じオブジェクトを作成することです)。
では、どうすればこれにアプローチできますか?すべてのフィールドを1つずつコピーしたくありません。これは堅牢なアプローチではありません。おそらく将来的にはモデルが変更される可能性があるため、この方法でクローン作成方法を考慮する必要があります。

オブジェクトをコピーするエレガントな方法はありますか?データベースに保存するときに、そのIDは再び自動インクリメントされますか? (IDをnullに設定すると、int型であるため、コンパイラエラーが発生します)。

46
QuantumHive

コピーする必要がないことに気付きました。どうやらモデルのインスタンスをデータベースに追加するとき(IDが既にデータベースに存在するものに設定されている場合でも)、Entity Frameworkはデータベースに新しい行を挿入し、その主キーを自動インクリメントします。そのため、この機能はすでにEFに組み込まれています。知りませんでした、ごめんなさい。
わかりやすくするために、例を示します。

using(var database = new MyDbContext()) {
    MyModel myModel = database.FirstOrDefault(m => m.SomeProperty == someValue);
    myModel.SomeOtherProperty = someOtherValue; //user changed a value
    database.MyModels.Add(myModel); //even though the ID of myModel exists in the database, it gets added as a new row and the ID gets auto-incremented 
    database.SaveChanges();
}
59
QuantumHive

Lori Peterson は、.AsNoTracking()を使用してEF6でクローンを実行することを提案しています。私はこの方法を使用していますが、動作することを確認できます。子オブジェクトを含めることもできます。

var entity = context.Entities
                    .AsNoTracking()
                    .Include(x => x.ChildEntities)
                    .FirstOrDefault(x => x.EntityId == entityId);

entity.SomeProperty = DateTime.Now;

context.Entities.Add(entity);
context.SaveChanges();

データセットから1つまたは複数のエンティティを取得するとき、Entity Frameworkにそのオブジェクトに加えた変更を追跡しないように指示してから、そのエンティティをデータセットに新しいエンティティとして追加できます。 .AsNoTrackingを使用すると、コンテキストは既存のエンティティについて何も知りません。

38
jaycer

ObjectContextを使用する場合、QuantumHiveが提供する答えは機能しません。

その状況で返されるエラーは次のとおりです。

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
System.InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
   at System.Data.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded)
   at System.Data.Objects.ObjectContext.AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, String argumentName)
   at System.Data.Objects.DataClasses.RelatedEnd.AddEntityToObjectStateManager(IEntityWrapper wrappedEntity, Boolean doAttach)
   at System.Data.Objects.DataClasses.RelatedEnd.AddGraphToObjectStateManager(IEntityWrapper wrappedEntity, Boolean relationshipAlreadyExists, Boolean addRelationshipAsUnchanged, Boolean doAttach)
   at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges)
   at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedEntity, Boolean applyConstraints)
   at System.Data.Objects.DataClasses.EntityReference`1.set_ReferenceValue(IEntityWrapper value)
   at System.Data.Objects.DataClasses.EntityReference`1.set_Value(TEntity value)

エンティティフレームワークオブジェクトを正しく複製するには(少なくともEF6.0では):

/// <summary>
/// Clone a replica of this item in the database
/// </summary>
/// <returns>The cloned item</returns>
public Item CloneDeep()
{
    using (var context = new EntityObjectContext())
    {
        var item = context.Items
            .Where(i => i.ItemID == this.ItemID)
            .Single();
        context.Detach(item);
        item.EntityKey = null;
        item.ItemID = 0;
        return item;
    }
}
4
sweetfa

私はこれを見て、現在使用しているよりもオブジェクトをクローンする良い方法があるかどうかを確認し、複数のクローンを作成しようとしている場合、少なくともあなたがしたい場合、受け入れられた答えに潜在的な問題があることに気付きましたコンテキストを何度も作成しないでください...

これがクローニングへの最善のアプローチであるかどうかはわかりません。そのため、別の方法を探していました。しかし、それは機能します。エンティティを複数回クローンする必要がある場合、JSONシリアル化を使用してクローンを作成できます...このようなもの(Newtonsoft JSONを使用)。

using( var context = new Context() ) {
    Link link    = context.Links.Where(x => x.Id == someId);
    bool isFirst = true;
    foreach( var id in userIds ) {
        if( isFirst ) {
            link.UserId = id;
            isFirst     = false;
        }
        else {
            string cloneString = JsonConvert.SerializeObject(link);
            Link clone = JsonConvert.DeserializeObject<Link>(cloneString);
            clone.UserId = id;
            context.Links.Add(clone);
        }
    }
    context.SaveChanges();
}
4
Kevin Nelson

私はpostgresデータベースを使用します:

CREATE TABLE public."Table" ( 
    "Id" integer NOT NULL DEFAULT nextval('"Table_Id_seq"'::regclass),
    ...

私の場合、前述の方法のいずれも機能しません。私は二番目に使用します:

Table table = _context.Table.AsNoTracking().Select(s => new Table {
 // some properties, exept id
    }).FirstOrDefault();
_context.Table.Add(table);
await _context.SaveChangesAsync();
0
user1855805