web-dev-qa-db-ja.com

同じキーを持つこのタイプの別のインスタンスがすでに追跡されているため、エンティティタイプのインスタンスを追跡できません

サービスオブジェクトUpdateがあります

public bool Update(object original, object modified)
{
    var originalClient = (Client)original;
    var modifiedClient = (Client)modified;
    _context.Clients.Update(originalClient); //<-- throws the error
    _context.SaveChanges();
    //Variance checking and logging of changes between the modified and original
}

これが私がこのメソッドを呼び出す場所です:

public IActionResult Update(DetailViewModel vm)
{
    var originalClient = (Client)_service.GetAsNoTracking(vm.ClientId);
    var modifiedClient = (Client)_service.Fetch(vm.ClientId.ToString());
    // Changing the modifiedClient here
    _service.Update(originalClient, modifiedClient);
}

GetAsNotTrackingメソッドは次のとおりです。

public Client GetAsNoTracking(long id)
{
    return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}

Fetchメソッド:

public object Fetch(string id)
{
   long fetchId;
   long.TryParse(id, out fetchId);
   return GetClientQueryableObject(fetchId).FirstOrDefault();
}

GetClientQueryableObject

private Microsoft.Data.Entity.Query.IIncludableQueryable<Client, ActivityType> GetClientQueryableObject(long searchId)
{
    return _context.Clients
        .Where(x => x.Id == searchId)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.BusinessUnit)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.Probability)
        .Include(x => x.Industry)
        .Include(x => x.Activities)
        .ThenInclude(x => x.User)
        .Include(x => x.Activities)
        .ThenInclude(x => x.ActivityType);
 }

何か案は?

私は次の記事/議論を見ました。利用できない: ASP.NET GitHub Issue 3839

UPDATE:

GetAsNoTrackingの変更点は次のとおりです。

public Client GetAsNoTracking(long id)
{
    return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault();
}

GetClientQueryableObjectAsNoTracking

private IQueryable<Client> GetClientQueryableObjectAsNoTracking(long searchId)
{
    return _context.Clients
        .Where(x => x.Id == searchId)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.BusinessUnit)
        .AsNoTracking()
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.Probability)
        .AsNoTracking()
        .Include(x => x.Industry)
        .AsNoTracking()
        .Include(x => x.Activities)
        .ThenInclude(x => x.User)
        .AsNoTracking()
        .Include(x => x.Activities)
        .ThenInclude(x => x.ActivityType)
        .AsNoTracking();
}
29
Rijnhardt

EFトラックシステムをオーバーライドせずに、「ローカル」エントリを切り離し、保存する前に更新されたエントリを添付することもできます。

// 
var local = _context.Set<YourEntity>()
    .Local
    .FirstOrDefault(entry => entry.Id.Equals(entryId));

// check if local is not null 
if (!local.IsNull()) // I'm using a extension method
{
    // detach
    _context.Entry(local).State = EntityState.Detached;
}
// set Modified flag in your entry
_context.Entry(entryToUpdate).State = EntityState.Modified;

// save 
_context.SaveChanges();

PDATE:コードの冗長性を回避するために、拡張メソッドを実行できます。

public static void DetachLocal<T>(this DbContext context, T t, string entryId) 
    where T : class, IIdentifier 
{
    var local = context.Set<T>()
        .Local
        .FirstOrDefault(entry => entry.Id.Equals(entryId));
    if (!local.IsNull())
    {
        context.Entry(local).State = EntityState.Detached;
    }
    context.Entry(t).State = EntityState.Modified;
}

IIdentifierインターフェイスには、Id文字列プロパティのみがあります。

エンティティが何であれ、コンテキストでこのメソッドを使用できます。

_context.DetachLocal(tmodel, id);
_context.SaveChanges();
27
Andres Talavera

実際にモデルに加えられた変更を追跡するだけで、追跡されていないモデルを実際にメモリに保持するのではないように聞こえます。問題を完全に取り除く代替アプローチを提案できますか?

EFは変更を自動的に追跡します。組み込みのロジックを使用してみてはどうですか?

DbContextのOvverride SaveChanges()

    public override int SaveChanges()
    {
        foreach (var entry in ChangeTracker.Entries<Client>())
        {
            if (entry.State == EntityState.Modified)
            {
                // Get the changed values.
                var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
                var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
                foreach (var propName in modifiedProps)
                {
                    var newValue = currentValues[propName];
                    //log changes
                }
            }
        }

        return base.SaveChanges();
    }

良い例はここにあります:

Entity Framework 6:変更の監査/追跡

MVCおよびEntity Frameworkによる監査ログ/変更履歴の実装

EDIT:Clientは簡単にインターフェースに変更できます。 ITrackableEntityとしましょう。これにより、ロジックを集中化して、特定のインターフェイスを実装するすべてのエンティティに対するすべての変更を自動的に記録できます。インターフェイス自体には、特定のプロパティはありません。

    public override int SaveChanges()
    {
        foreach (var entry in ChangeTracker.Entries<ITrackableClient>())
        {
            if (entry.State == EntityState.Modified)
            {
                // Same code as example above.
            }
        }

        return base.SaveChanges();
    }

また、実際にSaveChanges()をオーバーライドする代わりに、 eranga がサブスクライブすることをお勧めします。

5
smoksnes

私の場合、テーブルのid列はIdentity列として設定されていません。

2
daviesdoesit

データが一度変更されると、テーブルをトレースしてはいけないことに気づくでしょう。たとえば、tiggerを使用してテーブル更新ID([キー])をトレースすると、同じIDを取得して問題が発生します。

0
menxin

ああ、これは私を手に入れ、私はそれをトラブルシューティングするのに多くの時間を費やしました。問題は、私のテストがParellel(XUnitのデフォルト)で実行されていたことです。

テストを順番に実行するために、各クラスに次の属性を追加しました。

[Collection("Sequential")]

これは私がそれをどのように解決したかです:ユニットテストを(並列ではなく)連続的に実行する


GenFuでEF In Memoryコンテキストのモックアップを作成します。

private void CreateTestData(TheContext dbContext)
{
    GenFu.GenFu.Configure<Employee>()
       .Fill(q => q.EmployeeId, 3);
    var employee = GenFu.GenFu.ListOf<Employee>(1);

    var id = 1;
    GenFu.GenFu.Configure<Team>()
        .Fill(p => p.TeamId, () => id++).Fill(q => q.CreatedById, 3).Fill(q => q.ModifiedById, 3);
    var Teams = GenFu.GenFu.ListOf<Team>(20);
    dbContext.Team.AddRange(Teams);

    dbContext.SaveChanges();
}

私が差し引くことができるものからテストデータを作成するとき、それは2つのスコープで生きていました(チームテストの実行中に従業員のテストで一度):

public void Team_Index_should_return_valid_model()
{
    using (var context = new TheContext(CreateNewContextOptions()))
    {
        //Arrange
        CreateTestData(context);
        var controller = new TeamController(context);

        //Act
        var actionResult = controller.Index();

        //Assert
        Assert.NotNull(actionResult);
        Assert.True(actionResult.Result is ViewResult);
        var model = ModelFromActionResult<List<Team>>((ActionResult)actionResult.Result);
        Assert.Equal(20, model.Count);
    }
}

両方のテストクラスをこの順次コレクション属性でラップすると、明らかな競合が解消されました。

[Collection("Sequential")]

追加の参照:

https://github.com/aspnet/EntityFrameworkCore/issues/734
EF Core 2.1メモリ内DBでレコードが更新されない
http://www.jerriepelser.com/blog/unit-testing-aspnet5-entityframework7-inmemory-database/
http://gunnarpeipman.com/2017/04/aspnet-core-ef-inmemory/
https://github.com/aspnet/EntityFrameworkCore/issues/12459
単体テストでEF Core SqlLiteを使用する場合の追跡問題の防止

0
Jeremy Thompson