🐥note.

小鳥とMicrosoft <3 なエンジニアの技術Blog📚

EntityFrameworkCoreのThrowIdentityConflictでハマったメモ

EFCoreでこんな感じの例外が出た。

Unhandled exception. System.InvalidOperationException: The instance of entity type 'Person' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity 
instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
   ...

なんぞこれ?と思いきや、なんてことはないAsNoTracking()付きで取得したEntityをRemoveしているだけだった。

以下検証コード

public class Person { public int Id { get; set; } }
public class MyDbContext : DbContext
{
    public DbSet<Person> Persons { get; set; }
    public MyDbContext(DbContextOptions options) : base(options)
    {
        Database.EnsureDeleted();
        Database.EnsureCreated();
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        var opt = new DbContextOptionsBuilder<MyDbContext>().UseSqlite("Data Source=test.db;").Options;
        var context = new MyDbContext(opt);

        var person = new Person { Id = 1 };
        var person2 = new Person { Id = 2 };
        context.Persons.Add(person);
        context.Persons.Add(person2);
        context.SaveChanges();

        var entity1 = await context.Persons.FirstOrDefaultAsync(f => f.Id == person.Id);
        context.Persons.Remove(entity1);
        context.SaveChanges();

        var entity2 = await context.Persons.AsNoTracking().FirstOrDefaultAsync(f => f.Id == person2.Id);
        context.Persons.Remove(entity2); // エラーが発生する部分
        context.SaveChanges();
    }
}

余談: DbContextOptionsBuilder.EnableSensitiveDataLoggingについて

エラーメッセージにあるようにDbContextOptionsBuilder.EnableSensitiveDataLoggingで詳細なLoggingができる

using Microsoft.Extensions.Logging;

// 途中省略

ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
var opt = new DbContextOptionsBuilder<MyDbContext>()
    .UseLoggerFactory(MyLoggerFactory)
    .EnableSensitiveDataLogging()
    .UseSqlite("Data Source=test.db;").Options;
var context = new MyDbContext(opt);

おわり

Repositoryクラスに実装したDelete関数がAsNoTrackingを付与したGetById関数を使用していたためハマりました。。
注意せねば...。

以上です。