Attaching a Persisted Object to a New Object in Entity Framework

entity entity-framework

Question

I am trying to perform a very simple task which is "Add the user with role in the database". The roles are already populated in the database and I am simply adding the role to the User roles collection but it keeps throwing the following exception:

The EntityKey property can only be set when the current value of the property is null.

Here is the code in User.cs:

  public void AddRole(Role role)
        {
            if (!Exists(role))
            {
                 role.User = this;
                Roles.Add(role);                 
            }
        }

And here is the test that fails:

  [Test]        
        public void should_save_user_with_role_successfully() 
        {
            var _role = _roleRepository.GetByName("Student");                        

            _user.AddRole(_role); 

            _userRepository.Save(_user);

            Assert.IsTrue(_user.UserId > 0);             
        }

The Repository Code:

  public bool Save(User user)
        {
            bool isSaved = false; 

            using (var db = new EStudyDevDatabaseEntities())
            {   
                db.AddToUsers(user);
                isSaved = db.SaveChanges() > 0;  
            }

            return isSaved; 

        }

Here is the AddRole Method:

   public bool Exists(Role role)
        {
            var assignedRole = (from r in Roles
                                where r.RoleName.Equals(role.RoleName)
                                select r).SingleOrDefault();

            if (assignedRole != null) return true;

            return false; 
        }

        public void AddRole(Role role)
        {
            if (!Exists(role))
            {
                 role.User = this;
                Roles.Add(role);                 
            }
        }

And here is the whole exception:

------ Test started: Assembly: EStudy.Repositories.TestSuite.dll ------

TestCase 'EStudy.Repositories.TestSuite.Repositories.when_saving_new_user.should_save_user_with_role_successfully'
failed: System.InvalidOperationException : The EntityKey property can only be set when the current value of the property is null.
 at System.Data.Objects.EntityEntry.GetAndValidateChangeMemberInfo(String entityMemberName, Object complexObject, String complexObjectMemberName, StateManagerTypeMetadata& typeMetadata, String& changingMemberName, Object& changingObject)
 at System.Data.Objects.EntityEntry.EntityMemberChanging(String entityMemberName, Object complexObject, String complexObjectMemberName)
 at System.Data.Objects.EntityEntry.EntityMemberChanging(String entityMemberName)
 at System.Data.Objects.ObjectStateEntry.System.Data.Objects.DataClasses.IEntityChangeTracker.EntityMemberChanging(String entityMemberName)
 at System.Data.Objects.DataClasses.EntityObject.set_EntityKey(EntityKey value)
 at System.Data.Objects.Internal.LightweightEntityWrapper`1.set_EntityKey(EntityKey value)
 at System.Data.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded)
 at System.Data.Objects.ObjectContext.AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, String argumentName)
 at System.Data.Objects.DataClasses.RelatedEnd.AddEntityToObjectStateManager(IEntityWrapper wrappedEntity, Boolean doAttach)
 at System.Data.Objects.DataClasses.RelatedEnd.AddGraphToObjectStateManager(IEntityWrapper wrappedEntity, Boolean relationshipAlreadyExists, Boolean addRelationshipAsUnchanged, Boolean doAttach)
 at System.Data.Objects.DataClasses.RelatedEnd.IncludeEntity(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)
 at System.Data.Objects.DataClasses.EntityCollection`1.Include(Boolean addRelationshipAsUnchanged, Boolean doAttach)
 at System.Data.Objects.DataClasses.RelationshipManager.AddRelatedEntitiesToObjectStateManager(Boolean doAttach)
 at System.Data.Objects.ObjectContext.AddObject(String entitySetName, Object entity)
 C:\Projects\EStudy\EStudySolution\EStudy.BusinessObjects\Entities\EStudyModel.Designer.cs(97,0): at EStudy.BusinessObjects.Entities.EStudyDevDatabaseEntities.AddToUsers(User user)
 C:\Projects\EStudy\EStudySolution\EStudy.BusinessObjects\Repositories\UserRepository.cs(17,0): at EStudy.BusinessObjects.Repositories.UserRepository.Save(User user)
 C:\Projects\EStudy\EStudySolution\EStudy.Repositories.TestSuite\Repositories\Test_UserRepository.cs(47,0): at EStudy.Repositories.TestSuite.Repositories.when_saving_new_user.should_save_user_with_role_successfully()


0 passed, 1 failed, 0 skipped, took 6.07 seconds (NUnit 2.5).

UPDATE:

Here is my UserRepository and RoleRepository and they both uses separate contexts:

  public bool Save(User user)
        {
            bool isSaved = false; 

            using (var db = new EStudyDevDatabaseEntities())
            {              
                db.AddToUsers(user);
                isSaved = db.SaveChanges() > 0;  
            }

            return isSaved; 

        }

 public Role GetByName(string roleName)
        {
            using (var db = new EStudyDevDatabaseEntities())
            {
                return db.Roles.SingleOrDefault(x => x.RoleName.ToLower().Equals(roleName.ToLower())); 
            }           
        }

As, you can see the user and the role are using different context which you have already pointed out. The problem with using single datacontext is that I cannot layer the application properly.

1
2
9/15/2014 8:59:51 PM

Accepted Answer

Updated again based on updated question

I disagree that you "can't layer the application properly" when you share a context between repositories. It's a problem that you need to solve, but it's most certainly solvable. Also, I think you will find it considerably easier to solve than the number of problems which you create when you attempt to use multiple contexts.

At any rate, there are really only two possible solutions to your problem:

  1. Manually keep track of which context a particular entity is attached to, and transfer it (with Attach and Detach), when necessary.
  2. Share a Context between repository instances.

In our ASP.NET MVC applications, the logical unit of work is a single Request. Therefore, we instantiate an ObjectContext at the beginning of a request, Dispose it at the end of a request, and inject it into new repositories when we create them. Repository instances never outlast a single request.

Update based on updated question

Does the role repository and the user repository each have a (separate) context? Here's what's going on in the stack trace:

  1. You add the User to the context.
  2. The RelationshipManager goes through the User and ensures that any related entities are also in the context. This involves, among other things, setting their EntityKey property.
  3. Presuming that the Role came from a different context (which appears to be the case, since otherwise the context should detect that the role is already in the context), you should see an error indicating that you cannot add an entity attached to one context into another context. For some reason, you're not seeing that here. But nevertheless, it's not a valid operation.
  4. Instead, you get an error when the EntityKey of the role is assigned.

In my opinion, using a single ObjectContext at a time should be the general rule for working with the EntityFramework. You should use multiple contexts only when you're absolutely forced to, which, in my experience, is almost never. Working with multiple ObjectContexts concurrently is significantly harder than working with one at a time.

OK, I don't know the details of your mapping, but I would expect AddRole to be something more along the lines of:

public void AddRole(Role role)
{
    this.Roles.Add(role);
}

... if User->Role is .. or:

public void AddRole(Role role)
{
    this.Role = role;
}

if User -> Role is *..1.

If this doesn't help, please post the stack trace for the exception.

4
12/14/2009 11:28:42 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow