2

EF DbContext に既にアタッチされているオブジェクトを更新 (置換) するにはどうすればよいですか?

DAL には Update() メソッドがあります。

public int Update<TEntity>(TEntity entity)
{
    this._context.Set<TEntity>().Attach(entity);
    this._context.Entry<TEntity>(entity).State = EntityState.Modified;
}

ある時点で、同じキー値を持つ TEntity の 2 つ以上の異なるインスタンスを受け取ることができます (単純な例であり、実際のプロジェクトからのものではありません)。

var e1 = new SomeEntity() { Id = 1; }
dal.Update(e1);
...    
var e2 = new SomeEntity() { Id = 1; }
dal.Update(e2); 
// Exception: An object with the same key already exists in the ObjectStateManager. 
...
dal.Commit(); 

古い最後の値 (e2) を保存する必要があります。どうすればできますか(2回目の更新用に新しいコンテキストを作成せずに)?

4

2 に答える 2

5

この投稿に出くわしたので、これをミックスに追加すると思いました-そして、いくつかの変更を加えて、Ladislavが提案したソリューションを使用しました:

public T Save<T>(T entity) where T : class {
    try {
        this.Set<T>().Attach(entity);
    } catch { 
      // You may wish to add logging here, instead of throwing away the exception
    }

    try {
        this.Entry<T>(entity).State = EntityState.Modified;
        this.SaveChanges();
        this.Entry<T>(entity).Reload(); // Update any server-generated fields
        this.Detach<T>(entity); // Detach the object again, to avoid collisions
    } catch { return null; }

    return entity; // Return the updated version of the object.
}
internal void Detach<T>(T entity) where T : class {
    ObjectContext.Detach(entity);
}

public ObjectContext ObjectContext {
    get { return ((IObjectContextAdapter)this).ObjectContext; }
}

変更点と、変更した理由を明確に指摘させてください。

まず、2 つtry/catchのブロックがあります。1 つ目は、オブジェクトが既にアタッチされている場合にエラーが発生しないようにします。エラーに対して実際に何もする必要はありません。これは重要ではなく、情報提供を目的としています。そのため、それが発生した場合は無視して、続行してください。

2 番目のtry/catchブロックは、その他の種類の保存エラーを適切に処理します。たとえば、変更が外部キー制約に違反する場合、このブロックは失敗をキャッチします。

3 つ目は、メソッドを追加したDetach<T>()ことです。これも衝突を防ぐため.Attach()です。基本的に、私の仮定は、.Attach()そもそもオブジェクトを必要とするこのような汎用メソッドを作成している場合、変更追跡なしで POCO を使用しているためです。そのため、保存/更新が完了したら、再度デタッチする必要があります。

InvalidOperationException完全を期すために、「オブジェクトは既にアタッチされています」というエラーを実際に明示的にキャッチしたい場合は、最初のcatchブロックでキャッチする必要があることも指摘しておく必要があります。

さらに優れたアプローチ

基本クラスまたはインターフェイスを追加できる場合 (生成されたオブジェクトを使用している場合は追加する必要があります).tt、最初の行を次のように置き換えることで、このソリューションの型安全性を向上させることができます。

public void Save<T>(T entity) where T : BaseClass {

私のアプリケーションの EF5POCO.ttファイルでは、すべてのオブジェクトの基本クラスを出力するように変更し、いくつかのシリアル化サポートを追加し、その他の微調整を行いました。その要点は次の行から来ています。

/* THIS SECTION ADDED IN ORDER TO CREATE A GLOBAL BASE CLASS */

fileManager.StartNewFile("EntityBase.cs");
#>
using System.Runtime.Serialization;

<# BeginNamespace(code); #>
<# foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) { #>
[KnownType(typeof(<#=code.Escape(entity)#>))]
<# } #>
public abstract class EntityBase { }
<#
EndNamespace(code);

/* END INJECTED BASE CLASS */

.tt次に、のEntityClassOpening()関数を次のように更新します。

public string EntityClassOpening(EntityType entity) {
    String baseType = _typeMapper.GetTypeName(entity.BaseType);
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", String.IsNullOrWhiteSpace(baseType) ? "EntityBase" : baseType)
    );
}

その結果、まだ継承されていないすべての POCO オブジェクトが派生元になりました。これにより、署名を次EntityBaseのように変更できます。Save<T>

public T Save<T>(T entity) where T : EntityBase {

...そして今、私のコードはコンパイル時にタイプセーフです。

では、どのように効果的に使用しますか?

失敗した場合は.Save<T>()メソッドが返さnullれ、成功した場合はエンティティの更新されたバージョンが返されることに注意してください。私は一般的に次のように呼んでいます。

if (null == (entity = db.Save(entity))
    throw new Exception( ... );

// If we got this far, it's because the save succeeded.
DoSomething().With(entity);
于 2013-01-11T08:48:17.333 に答える
2

次のようなものを使用できます。

public void Update<TEntity>(TEntity entity) where TEntity : IEntityWithId {
    DbSet<TEntity> set = _context.Set<TEntity>();
    TEntity original = set.Local.SingleOrDefault(e => e.Id == entity.Id);
    if (original != null) {
        _context.Entry<TEntity>(original).CurrentValues.SetValues(entity);
    } else {
        set.Attach(entity);
        _context.Entry<TEntity>(entity).State = EntityState.Modified;
    }
}

この操作をサポートするには、エンティティに実装してインターフェースするだけです。

public interface IEntityWithId {
    int Id { get; }
}
于 2012-09-06T12:15:09.097 に答える