EF5を汎用リポジトリパターンおよびninjectとともに使用して依存関係を削除し、edmxでストアドプロシージャを使用してデータベースにエンティティを更新しようとすると問題が発生します。
dbContextRepository.csの私の更新は次のとおりです。
public override void Update(T entity)
{
if (entity == null)
throw new ArgumentException("Cannot add a null entity.");
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
_context.Set<T>().Attach(entity);
entry.State = EntityState.Modified;
}
}
リポジトリに戻るAddressService.csから:
public int Save(vw_address address)
{
if (address.address_pk == 0)
{
_repo.Insert(address);
}
else
{
_repo.Update(address);
}
_repo.SaveChanges();
return address.address_pk;
}
AttachとEntityState.Modifiedにヒットすると、エラーを吐き出します。
同じキーを持つオブジェクトがObjectStateManagerに既に存在します。ObjectStateManagerは同じキーを持つ複数のオブジェクトを追跡できません。
私はスタックやインターネット上で多くの提案を調べてきましたが、それを解決するものは何も思いつきませんでした。回避策をいただければ幸いです。
ありがとう!
編集:Local.SingleOrDefault
の代わりにFind
を使用した元の回答。 @JuanのSave
メソッドと組み合わせて機能しましたが、データベースへの不必要なクエリを引き起こす可能性があり、else
部分はおそらく実行されませんでしたエンティティが見つからなかったため、更新できませんでした)。問題を見つけてくれた@BenSwayneに感謝します。
同じキーを持つエンティティがコンテキストによって既に追跡されているかどうかを確認し、現在のエンティティを添付する代わりにそのエンティティを変更する必要があります。
public override void Update(T entity) where T : IEntity {
if (entity == null) {
throw new ArgumentException("Cannot add a null entity.");
}
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached) {
var set = _context.Set<T>();
T attachedEntity = set.Local.SingleOrDefault(e => e.Id == entity.Id); // You need to have access to key
if (attachedEntity != null) {
var attachedEntry = _context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
} else {
entry.State = EntityState.Modified; // This should attach entity
}
}
}
おわかりのように、主な問題は、SingleOrDefault
メソッドがエンティティを見つけるためにキーを知る必要があるということです。キー(この例ではIEntity
)を公開する簡単なインターフェイスを作成し、この方法で処理するすべてのエンティティに実装できます。
インターフェイスまたは属性を追加して、自動生成されたEFクラスを汚染したくありませんでした。したがって、これは上記の回答の一部から実際に少しです(クレジットはラディスラフMrnkaに行く)。これは私に簡単な解決策を提供しました。
エンティティの整数キーを検出する更新メソッドにfuncを追加しました。
public void Update(TEntity entity, Func<TEntity, int> getKey)
{
if (entity == null) {
throw new ArgumentException("Cannot add a null entity.");
}
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached) {
var set = _context.Set<T>();
T attachedEntity = set.Find.(getKey(entity));
if (attachedEntity != null) {
var attachedEntry = _context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
} else {
entry.State = EntityState.Modified; // This should attach entity
}
}
}
その後、コードを呼び出すときに使用できます。
repository.Update(entity, key => key.myId);
リフレクションを使用して実際にIdを取得できます。以下の例を参照してください。
var entry = _dbContext.Entry<T>(entity);
// Retreive the Id through reflection
var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);
if (entry.State == EntityState.Detached)
{
var set = _dbContext.Set<T>();
T attachedEntity = set.Find(pkey); // access the key
if (attachedEntity != null)
{
var attachedEntry = _dbContext.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // attach the entity
}
}
@ serj-sagan 次のようにしてください:
** YourDbはDbContextから派生したクラスである必要があることに注意してください。
public abstract class YourRepoBase<T> where T : class
{
private YourDb _dbContext;
private readonly DbSet<T> _dbset;
public virtual void Update(T entity)
{
var entry = _dbContext.Entry<T>(entity);
// Retreive the Id through reflection
var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);
if (entry.State == EntityState.Detached)
{
var set = _dbContext.Set<T>();
T attachedEntity = set.Find(pkey); // access the key
if (attachedEntity != null)
{
var attachedEntry = _dbContext.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // attach the entity
}
}
}
}
別の解決策(@Sergeyの回答に基づく)は次のとおりです。
private void Update<T>(T entity, Func<T, bool> predicate) where T : class
{
var entry = Context.Entry(entity);
if (entry.State == EntityState.Detached)
{
var set = Context.Set<T>();
T attachedEntity = set.Local.SingleOrDefault(predicate);
if (attachedEntity != null)
{
var attachedEntry = Context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
}
}
そして、次のように呼び出します。
Update(EntitytoUpdate, key => key.Id == id)
コンテキストをAsNoTracking()に設定すると、メモリ内のエンティティへの変更を追跡するaspmvcが停止します(これはWeb上で必要なことです)。
_dbContext.Products.AsNoTracking().Find(id);
リフレクションがなく、インターフェイスを使用したくない場合は、関数デリゲートを使用してデータベース内のエンティティを見つけることができます。上記の更新されたサンプルを次に示します。
private void Update<T>(T entity, Func<ObservableCollection<T>, T> locatorMap) where T : class
{
var entry = Context.Entry(entity);
if (entry.State == EntityState.Detached)
{
var set = Context.Set<T>();
T attachedEntity = locatorMap(set.Local);
if (attachedEntity != null)
{
var attachedEntry = Context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
}
}
次のように呼び出します。
Update(EntitytoUpdate, p => p.SingleOrDefault(a => a.Id == id))
見つかったエンティティをデタッチ( Ladislav's ソリューションのattachedEntity
を参照)し、修正したエンティティを再アタッチするとうまくいきました。
この背後にある理由は簡単です。何かが不変である場合、それを(全体として、エンティティとして)目的のエンティティに属する場所から置き換えます。
これを行う方法の例を次に示します。
_var set = this.Set<T>();
if (this.Entry(entity).State == EntityState.Detached)
{
var attached = set.Find(id);
if (attached != null) { this.Entry(attached).State = EntityState.Detached; }
this.Attach(entity);
}
set.Update(entity);
_
もちろん、このスニペットはジェネリックメソッドの一部であるため、テンプレートパラメーターであるT
とSet<T>()
を使用していることが簡単にわかります。