This project has moved and is read-only. For the latest updates, please go here.

Problems with AsNoTracking() in the Logic with unit tests.

Nov 24, 2014 at 6:35 PM
Hi, I'm pretty new to Effort and for the most part I like this tool.

I stumbled on this tool when I was researching a way to unit test my linq queries with entity framework 6 in Visual Studio 2013. For my work we started a new project and with this project I wanted to setup the automated tests propperly, because with older projects this was never done and it lead to major problems. In particular with the database queries.

So Effort gave me a very simple method for testing my linq queries. The Get methods perform flawlessly, however deleting and updating is giving me problems. I have the following repository:
public class FooRepository : IFooRepository
{
   private readonly FooEntities dbContext;
   
   public FooRepository(FooEntities context)
   {
      this.dbContext = context;
   }

   public async Task<Foo> GetAsync(int key)
   {
      var result = await this.dbContext.Foos
                  .AsNoTracking()
                  .SingleOrDefaultAsync(t => t.ID == key);
      return result;
   }

   // .... more code
      
   public void Delete(Foo entity)
   {
      this.dbContext.Foos.Attach(entity);
      this.dbContext.Entry(entity).State = EntityState.Deleted;
   }
}
I call SaveChanges on the database context from a UnitOfWork instance. On this repository I am running the following unit test:
[TestClass]
public class FooRepositoryTests
{
   private FooEntities context;

   [TestInitialize]
   public void TestInitialize()
   {
      var connection = Effort.EntityConnectionFactory.CreateTransient("name=FooEntities");
      this.context = new FooEntities(connection);
      this.context.Configuration.AutoDetectChangesEnabled = false;
      this.context.Configuration.LazyLoadingEnabled = false;
      this.context.Configuration.ProxyCreationEnabled = false;
      this.context.Configuration.ValidateOnSaveEnabled = false;
      EffortDbConfig.ConfigDatabase(this.context);
   }

   [TestCleanup]
   public void TestCleanup()
   {
      this.context.Dispose();
   }

   // ... other test methods

   [TestMethod]
   public void UT_FooRepositoryDelete_OK()
   {
      // Arrange
      var repository = new FooRepository(this.context);
         
      var task = repository.GetAsync(1);
      var foo = task.Result;
      var fooID = foo.ID;
      
      // Act
      repository.Delete(foo);
      var result = this.context.SaveChanges();

      // Assert
      Assert.AreEqual(1, result);

      // Additional Assert
      var result2 = repository.GetAsync(fooID).Result;
      Assert.IsNull(result2);
   }

}
I have set up the dbContext the same way as I did the context in my production code. But when I run the test from VS2013's Test Explorer it fails. In the Delete method the line
this.dbContext.Foos.Attach(entity);
throws a InvalidOperationException with the message:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I can make the unit test succeed by not using "AsNoTracking()" in my Get query. However I do not really want to do this, because 85% of the requests are for reads (to the database and webservices) and not for creating, updating or deleting. AsNoTracking turns off a lot of overhead and increases the performance of the webservices.

When I run the repository code in production setting with manual tests then the "AsNoTracking()" does not give any problems. And the webservices function as they are supposed to.

Is there some kind of setting in effort that I have to make in order for my unit test to function with "AsNoTracking()" in the code?

Thanks in advance and kind regards

Niek Hoeijmakers
Jan 5, 2015 at 10:44 PM
Hello,

Sorry for the late response.

Most of the time these kind of problems are not related to Effort, because it doesn't know anything about conceptional features like "no tracking", attach, detach, lazy loading... etc. I completely operates on the storage level of Entity Framework and receives SQL-like commands that is compiled into inmemory operations.

But if you provided a little proof of concept project I would definitely take a closer look to this problem.

Thanks,
Tamas
Jan 6, 2015 at 10:19 AM
Tamas, thanks for the reply.

I actually went ahead and worked something out. I looked deeper into what AsNoTracking() does. For as far as I understand it prevents the data that is retrieved from the database, to be attached to the database context and its changetracker. This is no problem for a production environment, because the database is not inmemory and thus not attached to the context and the change tracker.

As far as I understand Effort it has the database completely inmemory attached to the context and the changetracker. And when a query with AsNoTracking() is used a copy of the entities in the database context is returned. Thus when trying to attach it again the context will complain about having already an entity with the same primary key attached to the context and the changetracker.

For what I can see from the behavior of my tests, the basis of Effort is what AsNoTracking() tries to prevent. I have looked at several articles about AsNoTracking and it looks like that it does give a performance increase when getting lists with data, but for single entities the performance increase seems negligable or in some articles even worse.

We have talked it over and we are now only using AsNoTracking() with list, because that are only read requests and not edit requests. So the issue kinda became invalid.

Still thanks for the time.

Niek