ObjectContext attachment to cached entity.

Sep 19, 2012 at 8:42 PM

I'm trying out your caching repository, and I've come into an issue with using it across multiple web requests.

The problem is that the query results were cached on my first web request, and it appears that the ObjectContext was cached with them.  And on my second web request, I am dealing with a new ObjectContext, so none of the entities from the cache are "auto filling" into the entity relationships.  So, I try to attach them manually, and I get an error about an entity not being able to be attached to two ObjectContext change trackers.

It seems to me that maybe this library isn't really made to work with a web project.  I say this because the ObjectContext is getting cached, too.  And caching an ObjectContext at the AppDomain level is very bad in a web site, as it will grow huge in size.

What are you thoughts/suggestions?  I want to cache my queries.  Should I use your library?  Is there another I should try?

Coordinator
Sep 19, 2012 at 9:22 PM

Hi joshmouch,

I've primarily designed this library with Web usage in mind. I also use it at work, and 90% of time in web projects (webforms or mvc)

Now on your problem, can you provide us some sample code to reproduce?

As you can see in the MemoryCacheProvide in the Add method, we call ToList() on the IEnumerable query before storing it in the cache; Therefore, the ObjectContext should not be pulled in the cache too.

Anyway, cache features are quite new so you might have found a bug;

Once again please could you provide some simple code to reproduce the behavior you've experimented.

Sep 19, 2012 at 10:52 PM
Edited Sep 19, 2012 at 10:53 PM

I think what you say about ToList() excluding a reference to the ObjectContext might be when you’re using POCO entities. Any entity with IEntityWithChangeTracker will maintain a reference.

Here is some ASP.Net WebForms psuedocode to show you what I mean. On the first page load, this should work, but on the second page load, you’ll get an exception.

var objectContext = �;
// Create CacheableRepository using the ObjectContext above. // NOTE: Based on how your API is set up, you may have to actually create a DbContext and then access its IObjectContextAdapter.ObjectContext property. var firstEntityRepository = new CacheableRepository(�); var loadedFirstEntity = firstEntityRepository.First(�);
// NOTE: The following will be false because the related property has not been loaded. // loadedFirstEntity.SecondEntityReference.IsLoaded var secondEntityRepository = new CacheableRepository(�); var loadedSecondEntity = secondEntityRepository.First(�{some query that is equivalent to firstEntityRepository.SecondEntity}�);
// NOTE: The following *should* be *true* because the related property has been loaded. // HOWEVER, it�s not true on the second pass because the ObjectContext created on the first pass is different than the ObjectContext from the second pass. if (!loadedFirstEntity.SecondEntityReference.IsLoaded) { throw new Exception(�Something�s wrong!�);
// As an extra measure, this will also throw an exception about loadedSecondEntity already existing on a different ObjectContext. loadedFirstEntity.SecondEntityReference.Attach(loadedSecondEntity); }
Coordinator
Sep 23, 2012 at 9:43 PM
Edited Sep 23, 2012 at 9:44 PM

Joshmouch,

here is how I've reproduced what you describing in a MVC web site :

//
        // GET: /Product/
        public ViewResult Index()
        {
            DbContextAdapter adpt = new DbContextAdapter(_ctx);
            IUnitOfWork uof = new UnitOfWork(adpt);

            IRepository<Product> repo =
                new CacheableRepository<Product>(
                    new Repository<Product>(adpt)
                    );

            IEnumerable<Product> p = repo.GetAll();


            IRepository<ProductCategory> repo2 =
                new CacheableRepository<ProductCategory>(
                    new Repository<ProductCategory>(adpt)
                    );

            ProductCategory pc2 = repo2.First(productCategory => productCategory.Id > 0);

            _ctx.Categories.Attach(pc2);

            return View(p);
        }

I didn't notice this behavior before, and my unit tests didn't show it either.

As a workaround (while i correct the lib) you might want to use the extension method AsNoTracking() like this :

ProductCategory pc2 = repo2.AsQueryable().AsNoTracking().First(productCategory => productCategory.Id > 0);

I hope this can help you.