この投稿に出くわしたので、これをミックスに追加すると思いました-そして、いくつかの変更を加えて、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);