web-dev-qa-db-ja.com

EF Core「別のインスタンスはすでに追跡されています」

EF Core 2.2.3を使用して.Net Core 2.2.0のエンティティを更新する際に問題が発生します。

変更の保存中にエラーが発生しました。エラーの詳細:{'Id'}の同じキー値を持つ別のインスタンスが既に追跡されているため、エンティティタイプ 'Asset'のインスタンスは追跡できません。既存のエンティティをアタッチするときは、特定のキー値を持つエンティティインスタンスが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_へのすべてのエントリの状態を設定することです。その後、動作しますが、少なくとも私の現在の知識では、意味がありません。

私は この質問 を見つけましたが、有効な答えはなく、回避策とEFが追跡をどのように行うかについての仮定のみがありました。


[〜#〜] update [〜#〜]これは、この問題を再現するサンプルを含むbitbucketへのリンクです。 EF Core Update Sample

コンテキストから取得したオブジェクトをシリアル化しました。

左にトラッキングあり<====>右にトラッキングなし With Tracking on the LEFT <====> With NO tracking on the RIGHT

8
Simonca

デフォルトでは、エンティティを取得すると追跡されます。追跡されるため、SaveChangesを呼び出すだけでUpdateを呼び出すことはできません。 .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;
    }
}
5
Joe Audette

私は気づかずに同じデータを更新しようとしました。それを実感するのに10時間かかりました。私のような重複した値がある場合、私はそのデータを削除することをお勧めします...この回答を読んだ人は、私のようなインターネット上のすべての解決策を試し、プロジェクトを台無しにし、同じエラーを引き起こし、重複の省略ちょうど私のようなデータ。あなたは私の友達だけではありません。

model.GroupBy(gb => gb.ID).Select(s=>s.First()).ToList();//remove duplicates!!!!!
1

したがって、最終的にはカスタムUpdateEntityメソッドを使用して、一部のエンティティの変更を保存することになりました。このメソッドは、エンティティの各プロパティ、ナビゲーションプロパティ、およびコレクションプロパティを調べ、チェーンがないことを確認し、オブジェクトを1回更新します。

これは大きなオブジェクトで機能し、そのような場合にのみ使用します。単純な操作では、単純な更新を使用し続けます

これはソースコードを含むbitbucketリポジトリへのリンクです

このメソッドを使用するには、最初にdbエンティティを取得する必要があります。次に、リクエストで受信したdbエンティティとエンティティを使用してUpdateEntityメソッドを呼び出します。

それが私を助けてくれたように、それがあなたを助けることを願っています。乾杯!

0
Simonca

同じ問題がありましたが、同じEntiyに2行目を追加しようとしました。私の場合は、主キーがInt Identityであり、自動生成されていると想定していたためです。私はそれに値を割り当てていなかったので、2行目には同じIDがあり、これはceroのデフォルトです。教訓として、EFでAdd()操作中にこのエラーが表示された場合は、意図的にキーがIDENTITYであることを確認してください。

0
Javier Alvarez