web-dev-qa-db-ja.com

EF 6で既存のデータを更新すると、例外がスローされます-「...同じタイプのエンティティはすでに同じ主キー値を持っています。」

Entity Framework 6、コードファースト、流暢なマッピングなし、またはAutomapperなどのツールを使用してレコードを更新しようとしています。

Entity(Employee)には、Addreess(collection)、Departmentなどの他の複合プロパティが関連付けられています

また、Userと呼ばれるベースから継承されます

Saveメソッドは次のとおりです。__dbContext_はDbConextの実装です。

_        public bool UpdateEmployee(Employee employee)
        {
            var entity = _dbContext.Employees.Where(c => c.Id == employee.Id).AsQueryable().FirstOrDefault();
            if (entity == null)
            {
                _dbContext.Employees.Add(employee);
            }
            else
            {
                _dbContext.Entry(employee).State = EntityState.Modified; // <- Exception raised here
                _dbContext.Employees.Attach(employee);

            }

            return _dbContext.SaveChanges() > 0;

        }
_

エラーが発生し続けます:

同じタイプの別のエンティティがすでに同じ主キー値を持っているため、タイプのエンティティのアタッチに失敗しました。これは、「アタッチ」メソッドを使用する場合、またはグラフ内のいずれかのエンティティに競合するキー値がある場合にエンティティの状態を「変更なし」または「変更済み」に設定する場合に発生する可能性があります。これは、一部のエンティティが新しく、データベースで生成されたキー値をまだ受け取っていないことが原因である可能性があります。この場合、「Add」メソッドまたは「Added」エンティティ状態を使用してグラフを追跡し、必要に応じて「Unchanged」または「Modified」に非新規エンティティの状態を設定します。

私は以下を試しました:

  1. _EntityState.Modified_に設定する前に添付する
  2. オブジェクトが存在するかどうかのクエリにAsNoTracking()を追加します(例外はありませんが、DBは更新されません) https://stackoverflow.com/a/23228001/919426
  3. Employeeエンティティの代わりにベースエンティティ__dbContext.Users_を使用して保存 https://stackoverflow.com/a/25575634/919426

現在、どれも私のために働いていません。

これらのソリューションのいくつかが私の状況で機能しない場合、何が間違っていた可能性がありますか?

8
user919426

EFには、オートマッパーを使用せずにプロパティをマップする方法がすでに含まれていますナビゲーションプロパティがないと仮定更新:

_public bool UpdateEmployee(Employee employee)
    {
        var entity = _dbContext.Employees.Where(c => c.Id == employee.Id).AsQueryable().FirstOrDefault();
        if (entity == null)
        {
            _dbContext.Employees.Add(employee);
        }
        else
        {
            _dbContext.Entry(entity).CurrentValues.SetValues(employee);              
        }

        return _dbContext.SaveChanges() > 0;

    }
_

これは通常、変更されたプロパティのみを更新するため、より適切なSQLステートメントを生成します。

それでも元のメソッドを使用する場合は、AsNoTrackingを使用して、コンテキストからentityを削除します(この場合、更新されなかった理由がわからないため、効果がないはずです。問題は別のものである可能性があります)、またはクエリを変更して、最初にエンティティが実体化されないようにします。たとえば、bool exists = dbContext.Employees.Any(c => c.Id == employee.Id)などを使用します。

22
ESG

これは私自身のために働いた

var aExists = _db.Model.Find(newOrOldOne.id);
if(aExists==null)
{
    _db.Model.Add(newOrOldOne);
}
else
{
    _db.Entry(aExists).State = EntityState.Detached;
    _db.Entry(newOrOldOne).State = EntityState.Modified;
}
6
bRySaGeeK

リポジトリと作業単位のパターンを使用しているときに同じことが発生しました( mvc4 with ef5チュートリアル に記載されています)。

GenericRepositoryには、Attachを試行してからEntry.State = Modifiedを設定するUpdate(TEntity)メソッドが含まれています。 uow/repoパターンに固執する場合、上記の賛成票の「回答」はこれを解決しません。

アタッチする前にデタッチプロセスを使用しようとしましたが、最初の質問で示したのと同じ理由で失敗しました。

この理由は、レコードが存在するかどうかを確認し、update()を呼び出す前に、automapperを使用してdtoからエンティティオブジェクトを生成していたためです。

そのレコードの存在を確認することで、エンティティオブジェクトをスコープに入れ、それを切り離すことができませんでした(これは、最初の質問者が切り離すことができなかった理由でもあります)... Ttはレコードを追跡し、 dtoをエンティティに自動マッパーしてから更新を試みた後は、変更を許可しません。

ジェネリックリポジトリの更新の実装は次のとおりです。

public virtual void Update(TEntity entityToUpdate)
{
    dbSet.Attach(entityToUpdate);
    context.Entry(entityToUpdate).State = EntityState.Modified;
}

これは私のPUTメソッドです(AngularでWebApiを使用しています)

[HttpPut]
public IHttpActionResult Put(int id, Product product)
{
    IHttpActionResult ret;
    try
    {
        // remove pre-check because it locks the record
        // var e = unitOfWork.ProductRepository.GetByID(id);
        //  if (e != null) {
        var toSave = _mapper.Map<ProductEntity>(product);
        unitOfWork.ProductRepository.Update(toSave);
        unitOfWork.Save();
        var p = _mapper.Map<Product>(toSave);
        ret = Ok(p);
        // }
        // else
        //    ret = NotFound();
    }
    catch (DbEntityValidationException ex)
    {
        ret = BadRequest(ValidationErrorsToMessages(ex));
    }
    catch (Exception ex)
    {
        ret = InternalServerError(ex);
    }
    return ret;
}

ご覧のとおり、レコードが存在するかどうかを確認するために小切手をコメントアウトしました。 NotFound()を返す機会がなくなったため、存在しなくなったレコードを更新しようとすると、どのように機能するかがわかります。

したがって、最初の質問に答えるには、試行する前にentity == nullを探したり、別の方法を考えたりしないでください。おそらく私の場合、オブジェクトの検出後にUnitOfWorkを破棄してから、更新を行うことができます。

3
PixelSyndicate

SaveChangesを呼び出したときに重複する主キー例外を回避するには、デタッチする必要があります

db.Entry(entity).State = EntityState.Detached;
2
Karan Bhandari