EF Core 内存数据库在测试更新操作时生成 System.InvalidOperationException

当我尝试使用实体框架核心测试更新操作时出现以下错误:

System.InvalidOperationException:无法跟踪实体类型“公司”的实例,因为另一个具有键值“{Id: 1}”的实例已被跟踪。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。

在做了一些研究之后,我尝试了所有我发现的东西:

  • 在范围内创建数据库上下文
  • 分离并附加我想从数据库上下文更新的对象
  • 使用 "AsnoTracking()" 返回要更新的对象,我的存储库实际上是这样做的。

对于测试,我使用 EF 内存数据库和它的夹具,我使用 XUnit 和 .NET 5。 请问我可以得到任何帮助吗?

这是我的代码:

    // 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();
        }
    }

测试方法“Update_WhenCalled_UpdateACompanyObject”对我不起作用。

    // 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);
        }
    }
gdszlg2008 回答:EF Core 内存数据库在测试更新操作时生成 System.InvalidOperationException

如果您使用的是 EF Core 5.0,则在 Detached 之后调用 DbContext.ChangeTracker.Clear()(或通过 DbContext.Entries 集合并将状态设置为 DbContext.SaveChanges();) }} 演员。添加/更新条目使其被跟踪,并且您正在重用创建 Id = 1 条目的上下文,因此当 PrjSeedDataFixture 被调用时,它将尝试再次跟踪它(因为 _sut.Update(company); 应该返回一个未跟踪的一个)。

附言

在 EF 周围添加额外的存储库抽象层可以被视为 antipattern(因为 EF 已经实现了存储库/UoW 模式)并且您遇到的问题可能是为什么这是真的以及为什么会这样的示例之一抽象可能是有漏洞的。因此,如果您仍然认为拥有一个是个好主意 - 您需要谨慎行事。

本文链接:https://www.f2er.com/694.html

大家都在问