1

Closed

MappingViewCacheFactory is already set and cannot be modified

description

I have some code that calls SetViewCacheFactory in a DbContext constructor (so that it gets initialized without having to manually call it):

public MyDbContext() : base(connectionString)
{
InteractiveViews.SetViewCacheFactory(this, new FileViewCacheFactory(path));
}

I'm getting the follow exception the second time this code is run in a web app (for example the first page view works after doing a build, but then reloading the same page causes the exception):

[ArgumentException: MappingViewCacheFactory is already set and cannot be modified.
Parameter name: value]
System.Data.Entity.Core.Mapping.StorageMappingItemCollection.set_MappingViewCacheFactory(DbMappingViewCacheFactory value) +181
InteractivePreGeneratedViews.InteractiveViews.SetViewCacheFactory(ObjectContext context, DbMappingViewCacheFactory viewCacheFactory) +101


I've also tried removing it from the constructor and calling it manually when using the DbContext like in the examples:

using (var db = new MyDbContext())
{
InteractiveViews.SetViewCacheFactory(db, new FileViewCacheFactory(path));
}

But I still get the same exception.

Any ideas? I take it this is only meant to be called once? If so, how do you check if it's already been called? Ideally we could just put it in the constructor so that it automatically uses the pre-generated views whenever the DbContext is used.

BTW it looks like there is a typo in the Nuget package description, it is saying "Interacitve Pregenerated Views for Entity Framework 6", where it should be "Interactive" instead of "Interacitve". :)

Thanks for a great Nuget package! Automatically pre-generating views is definitely a pain point when using EF on a decent size project.
Closed Jul 17, 2016 at 5:01 AM by moozzyk
By design

comments

nakchak wrote Mar 10, 2014 at 12:05 PM

I ran into this issue as well, fixed it with a simple helper method to check if either a SqlServerViewCacheFactory or FileViewCacheFactory was already attached to the context. It would be trivial to add this logic to the SetViewCacheFactory method in InteractiveViews.cs
    using System.Data.Entity;
    using System.Data.Entity.Core.Mapping;
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Infrastructure;
    using InteractivePreGeneratedViews;

    public static class InteractiveViewsHelper {

        public static bool Attached(DbContext context) {
            var oCtx = ((IObjectContextAdapter)context).ObjectContext;
            var viewCache = (StorageMappingItemCollection)oCtx.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace);
            return (viewCache.MappingViewCacheFactory is SqlServerViewCacheFactory || viewCache.MappingViewCacheFactory is FileViewCacheFactory);
        }
    }

moozzyk wrote Mar 26, 2014 at 5:21 AM

Sorry for not responding sooner.

I actually pointed this out in my blog post:

Note that this code has to be executed before EF needs views (typically before you send the first query or the first call to the .SaveChanges() method) so make sure it is in the right place (for instance in a static constructor).

Note that EF caches model and the views in the AppDomain and therefore re-setting the view factory after views are generated wouldn't have any effect since EF will use the cached views for any context instance created in the current AppDomain.
One idea to ensure that initialization happens once and only once is to use a static ctor. Another place to do to this would be to close to the entry point of your app but before you do any "real" operation on your context. I would actually recommend keeping the initialization separate from other context operations. I would actually not recommend initializing view cache factory in the ctor of the context because of the issue you are hitting - the context is created multiple times but initialization must happen only once (the solution provided by @nakchak works around this)

Thanks for pointing out the typo in the NuGet description. I will look into fixing this.