Search code examples
c#entity-framework-6

"The RelatedEnd with role name X has already been loaded" error message


I am strugling to understand why the query is retuning this error when I perform the following statement:

SelectFor(x => x.Id == id, true, false, "Scope.Hiring.Scopes")

The string in the above statement are the entities to include.

SelectFor Method:

public virtual TEntity SelectFor(Expression<Func<TEntity, bool>> predicate, bool noTracking = true, bool selectInactiveItems = false, params string[] entitiesToLoad)
        {
            var query = this.LoadRelatedEntities(this.transaction.Context.Set<TEntity>().AsQueryable(), entitiesToLoad);
            query = query.Where(x => x.OwnerId == ownerId).Where(predicate);

            if (!selectInactiveItems)
            {
                query = query.Where(x => x.Active);
            }

            if (noTracking)
            {
                query = query.AsNoTracking();
            }

            return query.FirstOrDefault();
        }

Include Method

protected IQueryable<TEntity> LoadRelatedEntities(IQueryable<TEntity> query, params string[] entitiesToLoad)
        {
            if (entitiesToLoad != null && entitiesToLoad.Count() > 0)
            {
                foreach (var entityToLoad in entitiesToLoad)
                {
                    query = query.Include(entityToLoad);
                } 
            }

            return query;
        }

It seems quite easy thing to do, but it's taking my time and patience.


Solution

  • You use AsNoTracking in your query. Because of this, EF doesn't keep track of the entities it materializes. This means that when it fetches Scopes from the database it will create multiple objects for each "identical" scope. This definitely happens here because of the circular query pattern Scope.Hiring.Scopes.

    However, EF does perform relationship fixup while building the object graph, i.e. it populates navigation properties with entities that are materialized so that f.e. each Scope object will have a Hiring reference. While doing this, I figure it tries to assign a duplicated Scope object to a Hiring object that already has this object.

    Solution: remove the AsNoTracking or remove the circular query: Scope.Hiring instead of Scope.Hiring.Scopes.