question

MarcosRoig-6312 avatar image
0 Votes"
MarcosRoig-6312 asked MarcosRoig-6312 commented

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

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);
         }
     }
dotnet-csharp
· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


Did you try applying EntityState.Detached to the object that is added in PrjSeedDataFixture? By the way, I think that your AsNoTracking does not have any effect.


1 Vote 1 ·

I tried EntityState.Detached and didn't work. I agree with you, my AsNoTracking does not seems to work.

0 Votes 0 ·

@MarcosRoig-6312
XUnit is a third-party framework and I don't know how to test it.
Could you please use ordinary C# code to write a minimal example that can reproduce the problem, so that we can quickly reproduce your error and analyze it?

0 Votes 0 ·

Not sure what is ordinary C# code, but that is C# with XUnit that looks to be the default testing framework for .NET Core?

0 Votes 0 ·

1 Answer

karenpayneoregon avatar image
0 Votes"
karenpayneoregon answered MarcosRoig-6312 commented

I believe @TimonYang-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
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thanks, yes I will create a plain C# test method and post here.

0 Votes 0 ·