Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF - The context cannot be used while the model is being created exception during HTTP requests

I am receiving "The context cannot be used while the model is being created." issue in my web application in one of my webpages. This particular webpage POSTs to the server every 2-3 seconds to refresh the screen. From my testing I found that If I have 2 or more browser instances open to this page, after several minutes I receive a "The context cannot be used while the model is being created" exception from deep in the repository.

This code calls a "service" to retrieve the needed data. This code is executed in an custom authorization attribute of the MVC Controller class.

// Code in custom "Authorization" attribute on the controller
int? stationId = stationCookieValue;  // Read value from cookie
RoomStationModel roomStationModel = RoomStationService.GetRoomStation(stationId); // Error occurs inside this call

Here is the "RoomStationModel"

public class RoomStationModel
{
    [Key]
    public int RoomStationId { get; set; }

    public int? RoomId { get; set; }
    [ForeignKey("RoomId")]
    public virtual RoomModel Room { get; set; }
    /* Some other data properties.... */
 }

public class RoomModel
{
    [Key]
    public int RoomId { get; set; }

    public virtual ICollection<RoomStationModel> Stations { get; set; }
}

Here is the code for the service call above:

public RoomStationModel GetRoomStation(int? roomStationId)
{
    RoomStationModel roomStationModel = null;
    if (roomStationId.HasValue)
    {
        using (IRepository<RoomStationModel> roomStationRepo = new Repository<RoomStationModel>(Context))
        {
            roomStationModel = roomStationRepo.FirstOrDefault(rs => rs.RoomStationId == roomStationId.Value, false, new string[] { "Room" });
        }
    }

    return roomStationModel;
}

Here's the repository....where the error occurs

    public class Repository<TObject> : IRepository<TObject> where TObject : class
    {
        protected MyContext Context = null;

        public Repository(IDataContext context)
        {
            Context = context as MyContext;
        }

        protected DbSet<TObject> DbSet { get { return Context.Set<TObject>(); } }

    public virtual TObject FirstOrDefault(Expression<Func<TObject, bool>> predicate, bool track = true, string[] children = null)
    {
        var objectSet = DbSet.AsQueryable();

        if (children != null)
            foreach (string child in children)
                objectSet = objectSet.Include(child);

        if (track)
            return objectSet.Where(predicate).FirstOrDefault<TObject>(predicate);

        return objectSet.Where(predicate).AsNoTracking().FirstOrDefault<TObject>(predicate);
    }
}

Screenshot of error: Screenshot of error occurring

Stacktrace:

  at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
   at System.Data.Entity.Internal.InternalContext.Initialize()
   at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
   at System.Data.Entity.Internal.Linq.InternalSet`1.Include(String path)
   at System.Data.Entity.Infrastructure.DbQuery`1.Include(String path)
   at System.Data.Entity.DbExtensions.Include[T](IQueryable`1 source, String path)
   at Vanguard.AssetManager.Data.Repository`1.FirstOrDefault(Expression`1 predicate, Boolean track, String[] children) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Data\Repository.cs:line 100
   at Vanguard.AssetManager.Services.Business.RoomStationService.GetRoomStation(Nullable`1 roomStationId) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Services\Business\RoomStationService.cs:line 61
   at Vanguard.AssetManager.Web.Attributes.RoomStationAuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Web\Attributes\RoomStationAuthorizeAttribute.cs:line 52
   at System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)

EF Version: 4.1 (Code first)

like image 338
contactmatt Avatar asked Aug 25 '12 02:08

contactmatt


2 Answers

Your repository is short-lived (you create it for each call to GetRoomStation() but your actual context appears to be long-lived (RoomServiceStation.Context property). This means that every call to your web application is going to use the same context.

This is the "EF in an N-tier" scenario where you're trying to keep something stateful around (the context) in the architecturally stateless model of a web application. All those requests are being channeled to the same context on different threads and you're getting a race condition.

One thread could be kicking off first-time initialization of your context in response to a request, and another comes in attempting to use the context. The second request thinks the context is ready for use and you get this exception. You may even get this if you have multiple contexts trying to "spin up" at the same time as suggested in another SO thread.

You can do a few things. You could try pessimistic locking around access to your context, but you're putting in an unnecessary bottleneck. You could try creating some sort of "before clients are calling me, initialize the context" code, but you have to find a good place to do this, perhaps using the "brute force" method suggested in an MSDN thread.

A better thing to do is to simply create a new context for every request to your back-end service. There is some overhead, yes, but minimal. The overhead is probably less likely to kill performance than pessimistic locking, and won't be subject to app pool recycle events scaling out your web app on a farm and so on.

If you're relying on change tracking or other stateful nature of a context, you will lose this benefit. In this case, you're going to have to come up with a different mechanism for tracking and minimizing database hits.

From an MSDN article this is summed up (emphasis mine):

If you serialize entities from one tier to another, the recommended pattern is to keep the context around on the mid-tier only long enough for a single service method call. Subsequent calls will spin up a new instance of the context to complete each task.

A thread on EF/WCF/N-tier may also give you some insights, and Jorge's blog post #5 talks about EF in N-Tiers (the whole series might be a good read). And by the way, I've run into the exact same thing: many clients hitting the context at the same time, resulting in this issue.

like image 60
Kit Avatar answered Nov 07 '22 04:11

Kit


I encountered this error and have appeared to have solved it by providing an override to the Dispose() method in the controller. It would appear that force closing the database connection before attempting to open an new one subverts this error.

protected override void Dispose(bool disposing)
{
   if(disposing)
   {
        _fooRepository.Dispose();
   }
   base.Dispose(disposing);
}
like image 1
Matt Broyles Avatar answered Nov 07 '22 03:11

Matt Broyles