Question

I have a base repository that looks like this:

public class BaseRepository<T> : IBaseRepository<T> where T : class
{
    private DbContext _context;
    private IDbSet<T> _dbSet;

    protected DbContext Context
    {
        get
        {
            if (_context == null)
            {
                EFUnitOfWork currentUnitOfWork = (EFUnitOfWork)UnitOfWork.Current;
                _context = currentUnitOfWork.Context;
            }

            return _context;
        }
    }

    protected IDbSet<T> DbSet
    {
        get
        {
            if (_dbSet == null)
            {
                _dbSet = Context.Set<T>();
            }

            return _dbSet;
        }
    }

    public void Add(T entity)
    {
        DbSet.Add(entity);
    }

    public void Attach(T entity)
    {
        DbSet.Attach(entity);
    }

    public void Delete(T entity)
    {
        DbSet.Remove(entity);
    }

    public void Update(T entity)
    {
        Context.Entry(entity).State = System.Data.EntityState.Modified;
    }             

    public IQueryable<T> Get(string[] includes=null)
    {
        IQueryable<T> set = DbSet;
        if (includes != null)
        {
            foreach (string include in includes)
            {
                set = set.Include(include);
            }
        }
        return set;
    }
  1. User user = _usersRepository.Get().SingleOrDefault(u => u.Username == "gigi"); This returns the user without the Roles property, which is ok.

  2. User user = _usersRepository.Get(new string[] { "Roles" }).SingleOrDefault(u => u.Username == "gigi"); This returns the user and the Roles property, which is ok.

  3. List<User> users = _usersRepository.Get().Where(u => u.Username.StartsWith("gi")).ToList();

  4. List<User> users = _usersRepository.Get(new string[] { "Roles" }).Where(u => u.Username.StartsWith("gi")).ToList();

Query 3 and 4 both return a list of users with the Roles property. Why is query 3 returns Roles?

LE: This is the call, i examine the users collection after the context is disposed.

            List<User> users = _usersRepository.Get().Where(u => u.Username.StartsWith("gi")).ToList();
            UnitOfWork.Current.Dispose();

LE2: I did the same thing separately:

  1.         List<User> users;
            using (MyEntities ctx = new MyEntities ())
            {
                users= ctx.Users.ToList();
            }
    
  2.             List<User> users;
                using (MyEntities ctx = new MyEntities ())
                {
                    users= ctx.Users.Include("Roles").ToList();
                }
    

In the first case the Roles are not loaded, in the second they are which is ok.

I don't see what i am doing wrong in the repository sample.

LE3: This is the Unit of work

    public class UnitOfWork
    {
        private const string HTTPCONTEXTKEY = "Repository.Key";

        private static IUnitOfWorkFactory _unitOfWorkFactory;
        private static readonly Hashtable _threads = new Hashtable();

        public static IUnitOfWork Current
        {
            get
            {
                IUnitOfWork unitOfWork = GetUnitOfWork();

                if (unitOfWork == null)
                {
                    _unitOfWorkFactory = ObjectFactory.GetInstance<IUnitOfWorkFactory>();
                    unitOfWork = _unitOfWorkFactory.Create();
                    SaveUnitOfWork(unitOfWork);
                }

                return unitOfWork;
            }
        }

        private static IUnitOfWork GetUnitOfWork()
        {
            if (HttpContext.Current != null)
            {
                if (HttpContext.Current.Items.Contains(HTTPCONTEXTKEY))
                {
                    return (IUnitOfWork)HttpContext.Current.Items[HTTPCONTEXTKEY];
                }

                return null;
            }
            else
            {
                Thread thread = Thread.CurrentThread;
                if (string.IsNullOrEmpty(thread.Name))
                {
                    thread.Name = Guid.NewGuid().ToString();
                    return null;
                }
                else
                {
                    lock (_threads.SyncRoot)
                    {
                        return (IUnitOfWork)_threads[Thread.CurrentThread.Name];
                    }
                }
            }
        }

        private static void SaveUnitOfWork(IUnitOfWork unitOfWork)
        {
            if (HttpContext.Current != null)
            {
                HttpContext.Current.Items[HTTPCONTEXTKEY] = unitOfWork;
            }
            else
            {
                lock (_threads.SyncRoot)
                {
                    _threads[Thread.CurrentThread.Name] = unitOfWork;
                }
            }
        }
    }
Was it helpful?

Solution

EF will try to populate as many properties as it can.

If you have loaded a database row into a DbContext, EF will remember that row's data for the lifetime of the DbContext.

Then, when you load any entity that references that row, EF will populate that property with or without an Include clause.

In your case, you are loading ( some of ) the Roles table in Query 2.
When you run Query 3, those rows are populated without an Include because they are already in the DbContext.

OTHER TIPS

This line looks suspiciously like a Singleton.

EFUnitOfWork currentUnitOfWork = (EFUnitOfWork)UnitOfWork.Current;

If it is, and you're using the classic Singleton pattern where you use a static member to maintain the instance then you are playing with dynamite. You should never make your data context a static.

The reasons are many. The first is that this means you context will never get destroyed, and will continue to consume memory for change tracking until you exhaust memory (or the worker process restarts).

The second, and biggest reason is that statics are shared between all threads of the process, which means multiple users will be using the same context, and they will very likely stomp all over each other, destroying any kind of consistency.

EF data contexts are not thread safe, and they're also not concurrency safe (they are two different things).

This line:

UnitOfWork.Current.Dispose();

Is also very bad. You shouldn't be calling dispose like that, unless you are VERY careful. Again, if your context is static, then you could be disposing of it while another thread is using it.

All in all, your real problems are related to having data pre-loaded in your caches, and sometimes not. I suggest that you seriously reconsider how you are using your UnitOfWork. Ideally, you would use a dependency injection container to manage context lifetime, thus you can have a more consistent context state when you need it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top