1

I attempting to implement the Unit of Work and Repository Pattern in my ASP.NET MVC app as described here.

I was receiving the following error:

Value cannot be null.
Parameter name: entitySet

during a query. After doing some debugging, I noticed that my DBSet<T> classes were throwing the following error:

{
    "The context cannot be used while the model is being created. This exception may be
    thrown if the context is used inside the OnModelCreating method or if the same context
    instance is accessed by multiple threads concurrently. Note that instance members of
    DbContext and related classes are not guaranteed to be thread safe."
}

System.SystemException { System.InvalidOperationException }

I have looked around Stack Overflow but cannot find a solution. I used the "Code First from Database Approach." I checked my connection string and it seems right. My version of Entity, as defined in the packages.config file, is 6.1.3. I have tried commenting out some of the relationships within the DbContext class. The definition of which is as follows:

public partial class BKTrainerContext : DbContext
{
    public BKTrainerContext()
        : base("name=BKTrainerContext")
    {
    }

    public virtual DbSet<AccessLevel> AccessLevels { get; set; }
    public virtual DbSet<BuildCardCategory> BuildCardCategories { get; set; }
    public virtual DbSet<BuildCard> BuildCards { get; set; }
    public virtual DbSet<Manager> Managers { get; set; }
    public virtual DbSet<SliderImage> SliderImages { get; set; }
    public virtual DbSet<StoreMessage> StoreMessages { get; set; }
    public virtual DbSet<Store> Stores { get; set; }
    public virtual DbSet<Video> Videos { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AccessLevel>()
            .HasMany(e => e.Managers)
            .WithRequired(e => e.AccessLevel1)
            .HasForeignKey(e => e.AccessLevel)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<BuildCardCategory>()
            .HasMany(e => e.BuildCards)
            .WithRequired(e => e.BuildCardCategory1)
            .HasForeignKey(e => e.BuildCardCategory)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Manager>()
            .HasMany(e => e.StoreMessages)
            .WithRequired(e => e.Manager)
            .HasForeignKey(e => e.MessageAuthor)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Manager>()
            .HasMany(e => e.Stores)
            .WithRequired(e => e.Manager)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<StoreMessage>()
            .Property(e => e.MessageBody)
            .IsUnicode(false);

        modelBuilder.Entity<StoreMessage>()
            .HasMany(e => e.Stores)
            .WithMany(e => e.StoreMessages)
            .Map(m => m.ToTable("MessagesStores").MapLeftKey("MessageID").MapRightKey("StoreID"));
    }
}

This is my GenericRepo class

public class GenericRepo<T> : IGenericRepo<T> where T : class 
{
    internal BKTrainerContext dbContext;
    internal DbSet<T> db;

    public GenericRepo(BKTrainerContext context)
    {
        this.dbContext = context;
        this.db = context.Set<T>();

    }

    public virtual IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
    {
        IQueryable<T> query = db;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }


    public virtual T GetEntity(object id)
    {
        return db.Find(id);
    }


    public virtual void Insert(T newEntity)
    {
        db.Add(newEntity);
    }

    public virtual void Delete(object id)
    {
        T enity = db.Find(id);
        Delete(enity);
    }

    public virtual void Delete(T entity)
    {
        if (dbContext.Entry(entity).State == EntityState.Detached)
        {
            db.Attach(entity);
        }

        db.Remove(entity);
    }

    public virtual void Update(T entity)
    {
        db.Attach(entity);
        dbContext.Entry(entity).State = EntityState.Modified;
    }
}

The original error is thrown by the lines within Get(); those that call a query.xx method.

My Unit of Work class is as follows:

public class UnitOfWork : IUnitOfWork
{

    private BKTrainerContext dbContext = new BKTrainerContext();
    private GenericRepo<BuildCard> buildCardRepo;
    private GenericRepo<BuildCardCategory> buildCardCategoryRepo;
    private GenericRepo<Manager> managerRepo;
    private GenericRepo<StoreMessage> messageRepo;
    private GenericRepo<SliderImage> sliderRepo;
    private GenericRepo<Store> storeRepo;
    private GenericRepo<Video> videoRepo;

    private bool disposed = false;

    public GenericRepo<BuildCard> BuildCardRepo
    {
        get
        {
            if (this.buildCardRepo == null) { buildCardRepo = new GenericRepo<BuildCard>(dbContext); }
            return buildCardRepo;
        }
    }


    public GenericRepo<BuildCardCategory> BuildCardCategoriesRepo
    {
        get
        {
            if (this.buildCardCategoryRepo == null) { buildCardCategoryRepo = new GenericRepo<BuildCardCategory>(dbContext); }
            return buildCardCategoryRepo;
        }
    }


    public GenericRepo<Manager> ManagerRepo
    {
        get
        {
            if (this.managerRepo == null) { managerRepo = new GenericRepo<Manager>(dbContext); }
            return managerRepo;
        }
    }


    public GenericRepo<StoreMessage> MessageRepo
    {
        get
        {
            if (this.messageRepo == null) { messageRepo = new GenericRepo<StoreMessage>(dbContext); }
            return messageRepo;
        }
    }


    public GenericRepo<SliderImage> SliderRepo
    {
        get
        {
            if (this.sliderRepo == null) { sliderRepo= new GenericRepo<SliderImage>(dbContext); }
            return sliderRepo;
        }
    }


    public GenericRepo<Store> StoreRepo
    {
        get
        {
            if (this.storeRepo == null) { storeRepo= new GenericRepo<Store>(dbContext); }
            return storeRepo;
        }
    }


    public GenericRepo<Video> VideoRepo
    {
        get
        {
            if (this.videoRepo == null) {videoRepo = new GenericRepo<Video>(dbContext); }
            return videoRepo;
        }
    }

    public void Save()
    {
        dbContext.SaveChanges();
    }

    protected virtual void Dispose(bool disposing) {
        if (!this.disposed)
        {
            if (disposing)
            {
                dbContext.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

I am using Unity to inject it into my Controller.

public class HomeController : Controller
{

    private IUnitOfWork worker;

    public HomeController(IUnitOfWork unit)
    {
        this.worker = unit;
    }



    public ActionResult Index(bool failedLogin = false)
    {
        IndexViewModel vm = new IndexViewModel();
        vm.invalidLogin = failedLogin;
        return View(vm);
    }

    [HttpPost]
    public ActionResult AttemptLogin(string userName, string password)
    {

       List<Store> stores = worker.StoreRepo.Get().ToList();
       Store store = worker.StoreRepo.Get(u => u.Username == userName && u.Password == password).FirstOrDefault();
        if (stores != null)
        {
            Session["UserLevel"] = 0;
            Session["UserID"] = store.ID;
            return RedirectToAction("Index", "User");
        }
        else
        {
            Manager manager = worker.ManagerRepo.Get(m => m.Username == userName && m.Password == password).FirstOrDefault();
            if (manager != null)
            {
                Session["UserLevel"] = manager.AccessLevel;
                Session["UserID"] = manager.ID;
                return RedirectToAction("Index", "Admin");
            }
            else
            {
                return RedirectToAction("Index", new { failedLogin = true });
            }
        }
    }
}

I would really appreciate any help. Thanks!

EDIT:

Injection configuration:

    public class UnityConfig
{
    #region Unity Container
    private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
    {
        var container = new UnityContainer();
        RegisterTypes(container);
        return container;
    });

    /// <summary>
    /// Gets the configured Unity container.
    /// </summary>
    public static IUnityContainer GetConfiguredContainer()
    {
        return container.Value;
    }
    #endregion

    /// <summary>Registers the type mappings with the Unity container.</summary>
    /// <param name="container">The unity container to configure.</param>
    /// <remarks>There is no need to register concrete types such as controllers or API controllers (unless you want to 
    /// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered.</remarks>
    public static void RegisterTypes(IUnityContainer container)
    {
        // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
        // container.LoadConfiguration();

    container.RegisterType<IUnitOfWork, UnitOfWork>();
        // container.RegisterType<IProductRepository, ProductRepository>();
    }
}

}

The UnityMVCActivator class

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(BKBuildCard.App_Start.UnityWebActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(BKBuildCard.App_Start.UnityWebActivator), "Shutdown")]

namespace BKBuildCard.App_Start
{
    /// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary>
    public static class UnityWebActivator
    {
        /// <summary>Integrates Unity when the application starts.</summary>
        public static void Start() 
        {
            var container = UnityConfig.GetConfiguredContainer();

            FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
            FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));

            DependencyResolver.SetResolver(new UnityDependencyResolver(container));


        }

        /// <summary>Disposes the Unity container when the application is shut down.</summary>
        public static void Shutdown()
        {
            var container = UnityConfig.GetConfiguredContainer();
            container.Dispose();
        }
    }
}

EDIT

I attempted to run the application by using a concrete implementation of the class, but the problem persists.

    private UnitOfWork worker;

    public HomeController()        {

        this.worker =  new UnitOfWork();
    }
KellyM
  • 2,472
  • 6
  • 46
  • 90
  • 1
    What scope does the `IUnitOfWork` have? The error reads like it is injecting same `IUnitOfWork` into different actions/threads. – Mats391 May 08 '17 at 13:57
  • In the Controller? It is a private class-level variable. It is a public interface. Hope that is what you were asking. – KellyM May 08 '17 at 14:01
  • 1
    Can you please share your code that configures the lifetime of IUnitOfWork? – sachin May 08 '17 at 14:01
  • @KellyMarchewa please post where you register the `IUnitOfWork` for dependency injection. That is where you define the lifetime/scope of it. – Mats391 May 08 '17 at 14:03
  • I have added the classes that configure Unity. I hope that is what you were meaning. Thanks! – KellyM May 08 '17 at 14:07
  • I am not positive that the issue is tied to my Unity configuration. I tried using the concrete UnitOfWork class but with no success. – KellyM May 08 '17 at 14:44

2 Answers2

1

Just change one line in your RegisterTypes method:

container.RegisterType<IUnitOfWork, UnitOfWork>(new TransientLifetimeManager());

or try changing to

container.RegisterType<IUnitOfWork, UnitOfWork>(new PerThreadLifetimeManager());

What is happening here is that multiple threads are trying to share the same DbContext. The second one tries querying the model while the model is still being built on first thread. The solution would be to make sure that no 2 threads share the same DbContext. If the DbContext would be different on different threads, each one would build and query model on its own.

sachin
  • 2,341
  • 12
  • 24
  • Thanks for the answer. I tried the change above, but unfortunately it is still giving the same error. Any other suggestions? Thanks again. – KellyM May 08 '17 at 14:23
0

Finally figured it out. I neglected to mark a customer property on my class as [NotMapped].

    public HttpPostedFileBase cardImg { get; set; }

The correction simply involved adding the attribute.

    [NotMapped]
    public HttpPostedFileBase cardImg { get; set; }

I was under the impression that the following error:

Value cannot be null.
Parameter name: entitySet

was caused by the context issues. Evidently, it was the reverse; the null parameter issue was causing "Context cannot be used" error. This is a good summary of resolving issues related to null entitySet parameters (which can cause the context issues noted above).

The solution was simple, but, it would not have been my initial guess based upon the error message. As such, I hope somebody may be able to gain some use insight from my foolish mistake.

Community
  • 1
  • 1
KellyM
  • 2,472
  • 6
  • 46
  • 90