Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What steps to get rid of Collection was modified; enumeration operation may not execute. Error?

Our programming involves some Mock testing using In-Memory Data. Therefore, we implemented the following code that would first create In-Memory Data of Customer objects

        // Let us create some in-memory data
        // Create a list of Customer
        List<Customer> listOfCustomers =  new List<BlahBlahExample.Domain.Objects.Customer>()
                                   { new Customer { CustomerID = "1    ",Orders = new HashSet<Order>(), CustomerDemographics = new HashSet<CustomerDemographic>(), CompanyName = "Chicago Bulls", ContactName = "Michael Jordan", ContactTitle = "top basket ball player", Address = "332 testing lane", City = "Chicago", Region = "Illinois", PostalCode = "484894", Country = "USA", Phone = "3293993", Fax = "39393" },
                                     new Customer { CustomerID = "2    ",Orders = new HashSet<Order>(),CustomerDemographics = new HashSet<CustomerDemographic>() , CompanyName = "Miami Heat", ContactName = "Lebron James", ContactTitle = "second best basket ball player", Address = "90 test street", City = "Miami", Region = "Florida", PostalCode = "4869394", Country = "USA", Phone = "3293213", Fax = "33393" },
                                     new Customer { CustomerID = "3    ",Orders = new HashSet<Order>(),CustomerDemographics = new HashSet<CustomerDemographic>() , CompanyName = "Oklahoma City Thunder", ContactName = "Kevin Durant", ContactTitle = "current top basket ball player", Address = "35 test row", City = "Oklahoma City", Region = "Oklahoma", PostalCode = "480290", Country = "USA", Phone = "304923", Fax = "33325" }
                                   };


        // Convert the list to an IQueryable list
        IQueryable<Customer> queryableListOfCustomerInMemoryData = listOfCustomers.AsQueryable();



        // Let us create a Mocked DbSet object.
        Mock<DbSet<BlahBlahExample.Domain.Objects.Customer>> mockDbSet = new Mock<DbSet<BlahBlahExample.Domain.Objects.Customer>>();

        // Force DbSet to return the IQueryable members
        // of our converted list object as its 
        // data source
        mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.Provider).Returns(queryableListOfCustomerInMemoryData.Provider);
        mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.Expression).Returns(queryableListOfCustomerInMemoryData.Expression);
        mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.ElementType).Returns(queryableListOfCustomerInMemoryData.ElementType);
        mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.GetEnumerator()).Returns(queryableListOfCustomerInMemoryData.GetEnumerator());
        mockDbSet.Setup(m => m.Add(It.IsAny<Customer>())).Callback<Customer>(listOfCustomers.Add);


        Mock<BlahBlahAuditMappingProvider> jsAudtMppngPrvdr = new Mock<BlahBlahAuditMappingProvider>();
        Mock<BlahBlahDataContext> fctry = new Mock<BlahBlahDataContext>(jsAudtMppngPrvdr.Object);
        Mock<BlahBlahDataContext> qryCtxt = new Mock<BlahBlahDataContext>();
        Mock<BlahBlahAuditContext> audtCtxt = new Mock<BlahBlahAuditContext>();


         Mock<BlahBlahDataContext> mockedReptryCtxt = new Mock<BlahBlahDataContext>();


         mockedReptryCtxt.Setup(q => q.Customers).Returns(mockDbSet.Object);


        mockedReptryCtxt.Setup(q => q.Set<Customer>()).Returns(mockDbSet.Object);


        mockedReptryCtxt.CallBase = true;


        DbSet<Customer> inMemoryDbSetCustomer = mockedReptryCtxt.Object.Set<Customer>();

In the next excerpt of code( which is our "Code Under Test"), I add a new Customer to the existing In-Memory Data, and then Invoke SaveChanges on the Mocked Object.

                    Customer returnCust = (Customer)(mockedReptryCtxt.Object.Set<Customer>().Add(new Customer { CustomerID = "4    ", Orders = new HashSet<Order>(), CustomerDemographics = new HashSet<CustomerDemographic>(), CompanyName = "Kolkota Knights", ContactName = "Sachin Tendulkar", ContactTitle = "current top cricket player", Address = "35 test row", City = "Kolkota", Region = "West Bengal", PostalCode = "3454534", Country = "India", Phone = "304923", Fax = "33325" }));

            mockedReptryCtxt.Object.SaveChanges();

Later on in the code, I have the following excerpt of code where _context.Set() will return the In-Memory Data DBSet that we created previously

        var query = _context.Set<TEntity>().AsQueryable();

        if (typeof(TEntity).Name.Contains("Audit"))
        {
            return query;
        }

        if (includes != null && includes.Any())
        {
            foreach (var include in includes)
            {
                query = query.Include(include);
            }
        }


        List<TEntity> resultsAsList = query.ToList(); // Error Thrown When using ToList()

       var results = resultsAsList.AsQueryable();

When we invoke ToList(), it Throws the following Error:

System.InvalidOperationException was unhandled by user code
  HResult=-2146233079
  Message=Collection was modified; enumeration operation may not execute.
  Source=mscorlib
  StackTrace:
       at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
       at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
       at System.Collections.Generic.List`1.Enumerator.MoveNext()
       at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
       at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
        at BlahBlah.Framework.EntityFramework.EntityFrameworkRepository`1.ConcreteQuery(List`1      includes) in d:\EMIS\BlahBlah       Framework\BlahBlahFrameworkLightweight\BlahBlah.Framework.EntityFramework\EntityFrameworkRepository.c     s:line 51
        at Castle.Proxies.EntityFrameworkRepository`1Proxy.ConcreteQuery_callback(List`1 includes)
        at Castle.Proxies.Invocations.EntityFrameworkRepository`1_ConcreteQuery.InvokeMethodOnTarget()
       at Castle.DynamicProxy.AbstractInvocation.Proceed()
        at Moq.Proxy.CastleProxyFactory.CallContext.InvokeBase()
        at Moq.InvokeBase.HandleIntercept(ICallContext invocation, InterceptorContext ctx,     CurrentInterceptContext localctx)
        at Moq.Interceptor.Intercept(ICallContext invocation)
        at Moq.Proxy.CastleProxyFactory.Interceptor.Intercept(IInvocation invocation)
        at Castle.DynamicProxy.AbstractInvocation.Proceed()
        at Castle.Proxies.EntityFrameworkRepository`1Proxy.ConcreteQuery(List`1 includes)
         at BlahBlah.Framework.Core.Repository.BaseRepository`1.Query(List`1 includes) in      d:\EMIS\BlahBlah      Framework\BlahBlahFrameworkLightweight\BlahBlah.Framework.Core\Repository\BaseRepository.cs:line 149
         at Castle.Proxies.EntityFrameworkRepository`1Proxy.Query_callback(List`1 includes)
         at Castle.Proxies.Invocations.IRepository`1_Query.InvokeMethodOnTarget()
         at Castle.DynamicProxy.AbstractInvocation.Proceed()
          at Moq.Proxy.CastleProxyFactory.CallContext.InvokeBase()
        at Moq.InvokeBase.HandleIntercept(ICallContext invocation, InterceptorContext ctx,      CurrentInterceptContext localctx)
         at Moq.Interceptor.Intercept(ICallContext invocation)
          at Moq.Proxy.CastleProxyFactory.Interceptor.Intercept(IInvocation invocation)
         at Castle.DynamicProxy.AbstractInvocation.Proceed()
        at Castle.Proxies.EntityFrameworkRepository`1Proxy.Query(List`1 includes)
         at      BlahBlah.Test.Unit.CntrlrsTests.CustomerControllerTest.Test_Creation_Of_Customer_Using_Constructor_Of     _Customer_Controller_That_Expects_Arguments() in d:\EMIS\BlahBlah      Framework\BlahBlahFrameworkLightweight\BlahBlah.Test.Unit\CntrlrsTests\CustomerControllerTest.cs:line       278
    InnerException: 

What steps do we need to take in order to stop the said error from being thrown( preferably without Changing too much of our Code Under Test)?

like image 650
CS Lewis Avatar asked Nov 19 '14 04:11

CS Lewis


2 Answers

I had this problem as well but not iterating over the collection isn't really an option for me. After some thought, I did figure out a solution. The issue is that the mock sets up the various IQueryable properties off of a fixed IQueryable object from the original list. That causes any modification of that list to invalidate the corresponding IQueryable. The solution is to get a new IQueryable on each access using a lambda with Moq.

Here's the helper function I created to make mocking out DBSets easier, using the described technique.

public static Mock<DbSet<T>> MockDbSet<T>(List<T> list) where T : class
{
    var mockSet = new Mock<DbSet<T>>();
    mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(() => list.AsQueryable().Provider);
    mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(() => list.AsQueryable().Expression);
    mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(() => list.AsQueryable().ElementType);
    mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => list.GetEnumerator());
    mockSet.Setup(m => m.Add(It.IsAny<T>())).Callback((T x) => list.Add(x));
    mockSet.Setup(m => m.AddRange(It.IsAny<IEnumerable<T>>())).Callback((IEnumerable<T> x) => list.AddRange(x));
    mockSet.Setup(m => m.Remove(It.IsAny<T>())).Callback((T x) => list.Remove(x));
    mockSet.Setup(m => m.RemoveRange(It.IsAny<IEnumerable<T>>())).Callback((IEnumerable<T> x) => list.RemoveAll(x.Contains));
    return mockSet;
}

Edit: Added AddRange, Remove, RemoveRange since why not...

Edit 2: Correction for RemoveRange

like image 119
ShawnFumo Avatar answered Nov 09 '22 03:11

ShawnFumo


I found a really Clumsy Solution:

        List<TEntity> tempList = new List<TEntity>();

        for (int i = query.Count() - 1; i >= 0; i--)
        {
            tempList.Add(query.ElementAt(i));
        }

        List<TEntity> resultsAsList = tempList.ToList();

       var results = resultsAsList.AsQueryable();

In the aforementioned code, it is important to use a for loop with an index to go through the DBSet instance. Furthermore, in the loop, you add each element to a List. ( Basically, it's important to Avoid using the Iterator)

like image 20
CS Lewis Avatar answered Nov 09 '22 02:11

CS Lewis