EF Core 2.2.3を使用して.Net Core 2.2.0のエンティティを更新する際に問題が発生します。
変更の保存中にエラーが発生しました。エラーの詳細:{'Id'}の同じキー値を持つ別のインスタンスが既に追跡されているため、エンティティタイプ「アセット」のインスタンスを追跡できません。既存のエンティティをアタッチするときは、特定のキー値を持つエンティティインスタンスが1つだけアタッチされるようにしてください。使用を検討してください
これは、DBコンテキストの登録方法です。
services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("DbConnection")), ServiceLifetime.Scoped);
Scoped
ライフタイムはデフォルトで設定されていますが、理解しやすいように記述しました。
Anomaly
オブジェクトは次のようになります。
public IQueryable<Anomaly> GetAll()
{return _context.Anomalies.Include(a => a.Asset).Include(a => a.Level)
}
public async Task<Anomaly> GetAnomaly(int anomalyId, User user)
{
var anomaly = await GetAll()
.FirstOrDefaultAsync(a => a.Id == anomalyId);
return anomaly;
}
そして、 Update()
メソッドは次のようになります。
using (var transaction = _context.Database.BeginTransaction())
{
try
{
_context.Anomalies.Update(anomaly);
_context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
このトランザクションの前にいくつかのチェックが含まれていますが、このコンテキストでは十分に関連性がありません。
これは、 すでに追跡されているインスタンスでエラーが発生する場所です。これがどのように発生するか理解できません..コンテキストがScoped
場合、
...この場合、リクエストごとに「スコープごとにサービスの新しいインスタンスが作成されます」
PUTリクエストのコンテキストがGETリクエストのコンテキストと異なる場合、エンティティはどのように追跡されていますか?これは最も基本的なレベルでどのように機能しますか?
これを機能させる唯一の方法は、 ChangeTracker
からEntityState.Detached
へのすべてのエントリの状態を設定することEntityState.Detached
。その後、動作しますが、少なくとも私の現在の知識では、意味がありません。
私はこの質問を見つけましたが、有効な答えはなく、EFが追跡をどのように行うかについての回避策と仮定だけがありました。
更新この問題を再現するサンプルを含むbitbucketへのリンクは次のとおりです: EF Core Update Sample
コンテキストから取得したオブジェクトをシリアル化しました。
デフォルトでは、エンティティを取得すると追跡されます。追跡されるため、Updateを呼び出さずにSaveChangesを呼び出すだけで済みます。 .AsNoTracking()を使用して、追跡せずにエンティティを取得することもできます
まだ追跡されていない場合はUpdateを呼び出す必要があるため、AsNoTrackingを使用する場合は、SaveChangesの前にUpdateを使用する必要があります。
public IQueryable<Anomaly> GetAll()
{ return _context.Anomalies
.Include(a => a.Asset)
.Include(a => a.Level);
}
public async Task<Anomaly> GetAnomaly(int anomalyId, User user)
{
var anomaly = await GetAll()
.AsNoTracking()
.FirstOrDefaultAsync(a => a.Id == anomalyId);
return anomaly;
}
また、エンティティを追跡して、Updateを呼び出すかどうかを確認することもできます。
using (var transaction = _context.Database.BeginTransaction())
{
try
{
bool tracking = _context.ChangeTracker.Entries<Anomaly>().Any(x => x.Entity.Id == anomaly.Id);
if (!tracking)
{
_context.Anomalies.Update(anomaly);
}
_context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}