EF Core in-memory database generate System.InvalidOperationException when testing an update operation

Marcos Roig 1 Reputation point
2021-08-01T20:07:21.617+00:00

I got the following error when I try to test an update operation using Entity Framework core:

System.InvalidOperationException : The instance of entity type 'Companies' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

After doing some research, I tried everything that I found:

Create in scope DB context
deattach and attached the object I want to update from the DB context
Return the object to be updated using "AsNoTracking()" , my repository actually do this.
For the testing I am using EF in-memmory database with it fixture, I am using XUnit and .NET 5. Can I get any help with this please?

Here is my code:

// The repository I am trying to test
public class RepositoryBase<T> : ICrudRepository<T> where T : class, IModel
{
protected PrjDbContext DatabaseContext { get; set; }

        public RepositoryBase(PrjDbContext databaseContext) => DatabaseContext = databaseContext;

        protected IQueryable<T> FindAll() => DatabaseContext.Set<T>().AsNoTracking();

        protected IQueryable<T> FindBy(Expression<Func<T, bool>> expression) => DatabaseContext.Set<T>().Where(expression).AsNoTracking();

        public void Create(T entity) => DatabaseContext.Set<T>().Add(entity);

        public void Update(T entity) => DatabaseContext.Set<T>().Update(entity);

        public void Delete(T entity) => DatabaseContext.Set<T>().Remove(entity);

        public async Task<IEnumerable<T>> ReadAllAsync() => await FindAll().ToListAsync().ConfigureAwait(false);

        public async Task<T> ReadByIdAsync(int id) => await FindBy(entity => entity.Id.Equals(id)).FirstOrDefaultAsync().ConfigureAwait(false);
    }


    //The Database context  
    public partial class PrjDbContext : DbContext
    {
        public PrjDbContext()
        {

        }

        public PrjDbContext(DbContextOptions<PrjDbContext> options)
            : base(options)
        {

        }

        public virtual DbSet<Companies> Companies { get; set; }

    }  

    // This is my fixture with the in-memory Database 
    public sealed class PrjSeedDataFixture : IDisposable
    {
        public PrjDbContext DbContext { get; }

        public PrjSeedDataFixture(string name)
        {
            string databaseName = "PrjDatabase_" + name + "_" + DateTime.Now.ToFileTimeUtc();
            DbContextOptions<PrjDbContext> options = new DbContextOptionsBuilder<PrjDbContext>()
                .UseInMemoryDatabase(databaseName)
                .EnableSensitiveDataLogging()
                .Options;

            DbContext = new PrjDbContext(options);

            // Load Companies
            DbContext.Companies.Add(new Companies { Id = 1, Name = "Customer 1", Status = 0, Created = DateTime.Now, LogoName = "FakeLogo.jpg", LogoPath = "/LogoPath/SecondFolder/", ModifiedBy = "Admin" });
            DbContext.Companies.AsNoTracking();

            DbContext.SaveChanges();
        }

        public void Dispose()
        {
            DbContext.Dispose();
        }
    }

The test method "Update_WhenCalled_UpdateACompanyObject", is not working for me.

    // And finally, this is my test class, Create_WhenCalled_CreatesNewCompanyObject pass the test, but Update_WhenCalled_UpdateACompanyObject isn't passing the test.
    public class RepositoryBaseCompanyTests
    {
        private Companies _newCompany;
        private PrjDbContext _databaseContext;
        private RepositoryBase<Companies> _sut;

        public RepositoryBaseCompanyTests()
        {
            _newCompany = new Companies {Id = 2};
            _databaseContext = new PrjSeedDataFixture("RepositoryBase").DbContext;
            _sut = new RepositoryBase<Companies>(_databaseContext);
        }

        [Fact]
        public void Create_WhenCalled_CreatesNewCompanyObject()
        {
            //Act
            _sut.Create(_newCompany);
            _databaseContext.SaveChanges();

            //Assert
            Assert.Equal(2, _databaseContext.Companies.Where( x => x.Id == 2).FirstOrDefault().Id);

        }

        [Fact]
        public async void Update_WhenCalled_UpdateACompanyObject()
        {
            //Arrange
            var company = await _sut.ReadByIdAsync(1);
            company.Name = "Customer 2";
            //_databaseContext.Entry(company).State = EntityState.Detached;
            //_databaseContext.Attach(company);
            //_databaseContext.Entry(company).State = EntityState.Modified;

            //Act
            _sut.Update(company);
            await _databaseContext.SaveChangesAsync();

            //Assert
            Assert.Equal("Customer 2", _databaseContext.Companies.Where(x => x.Id == 1).FirstOrDefault().Name);
        }
    }
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,030 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Karen Payne MVP 35,441 Reputation points
    2021-08-02T13:15:16.963+00:00

    I believe @Timon Yang-MSFT is saying or close to what is shown below, a simple non-repository test.

    [TestMethod]  
    public async Task CustomersUpdateTest()  
    {  
          
        using (var context = new NorthWindContext(ContextInMemoryOptions()))  
        {  
            // arrange  
            var customer = new Customer()  
            {  
                ContactIdentifier = 1,  
                CustomerIdentifier = 1,  
                CompanyName = "ABC",  
                CountryIdentfier = 9  
            };  
      
            await context.Customers.AddAsync(customer);  
            await context.SaveChangesAsync();  
      
            // act  
            var companyNameNew = "DEF";  
            customer.CompanyName = companyNameNew;  
            context.Customers.Update(customer);  
            await context.SaveChangesAsync();  
      
            var customerModified = context.Customers.Find(customer.CustomerIdentifier);  
              
            // assert  
            Assert.IsTrue(customerModified.CompanyName == companyNameNew);  
      
        }  
      
    }  
    

    Also, rather than

    public async void   
    

    Use

    public async Task  
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.