locked
How to udpate object graph in EF4.0 RRS feed

  • Question

  • Hi, all

    I am developing an application and in that I am using Entity Framework 4.0 with POCO classes.

    I am stuck on one issuse which I have been trying to solve for last few days.

    I have A User entity which links with ApplicationUser entity and Tamplate Entity

    RelatationShips between Entitites

    User to ApplicationUser: One to One

    User to Templates : One To Many

    Problem 1:

    When I update my existing user and try to attach it in context it throws me error that:

    An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. 

    How can I achive this??

    Problem 2:

    If I have ObjectGraph which contains updated entities and newly added entities and I want to save that whole object graph in database using EF4.0 how can I do that?

    I am using MVC3.0 and repository pattern in my project. I am sharing object context to all repository but it doen't solve my problems

    My ObjectContext Class:

     public class MyDatabaseContext : ObjectContext
        {
            public const string ConnectionString = "name=DBEntities";
            public const string ContainerName = "DBEntities";
    
            public MyDatabaseContext()
                : base(ConnectionString, ContainerName)
            {
                this.ContextOptions.LazyLoadingEnabled = true;
            }
        }

    My GenericRepositoryClass

        public class GenericRepository<EntityObject> : IRepository<EntityObject> where EntityObject : class
        {
            private bool shareContext = false;
            protected MyDatabaseContext Context = null;
    
            public GenericRepository()
            {
                Context = new MyDatabaseContext();
            }
    
            public GenericRepository(DatabaseContext context)
            {
                Context = context;
                shareContext = true;
            }
    
            protected ObjectSet<EntityObject> ObjectSet
            {
                get
                {
                    return Context.CreateObjectSet<EntityObject>();
                }
            }
    
            public virtual int Count
            {
                get
                {
                    if (ObjectSet == null)
                        return 0;
    
                    return ObjectSet.Count();
                }
            }
                         
            public virtual IQueryable<EntityObject> SelectAll()
            {
                return ObjectSet.AsQueryable();
            }
    
            public virtual IQueryable<EntityObject> Filter(Expression<Func<EntityObject, bool>> predicate)
            {
                return ObjectSet.Where(predicate).AsQueryable<EntityObject>();
            }
    
            public virtual IQueryable<EntityObject> Filter(Expression<Func<EntityObject, bool>> filter, out int total, int index = 0, int size = 50)
            {
                int skipCount = index * size;
                IQueryable<EntityObject> _resetSet = null;
    
                if (filter != null)
                    _resetSet = ObjectSet.Where(filter).AsQueryable();
                else
                    _resetSet = ObjectSet.AsQueryable();
    
                if (skipCount == 0)
                    _resetSet.Take(size);
                else
                    _resetSet.Skip(skipCount).Take(size);
    
                total = _resetSet.Count();
                return _resetSet.AsQueryable();
            }
    
            public bool Contains(Expression<Func<EntityObject, bool>> predicate)
            {
                return ObjectSet.Count(predicate) > 0;
            }
    
            public virtual EntityObject Find(Expression<Func<EntityObject, bool>> predicate)
            {
                return ObjectSet.FirstOrDefault(predicate);
            }
    
            public virtual void Detach(EntityObject entity)
            {
                Context.Detach(entity);
    
                if (!shareContext)
                    Context.SaveChanges();
            }
    
            public virtual EntityObject Create(EntityObject entity)
            {
                //ObjectSet.AddObject(TObject);
                Context.AddObject(ObjectSet.EntitySet.Name, entity);
    
                if (!shareContext)
                    Context.SaveChanges();
    
                return entity;
            }
    
            public virtual int Update(EntityObject entity)
            {
                Context.AttachTo(ObjectSet.EntitySet.Name, entity);
                Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
    
                //Context.ApplyCurrentValues(ObjectSet.EntitySet.Name, entity);
    
                if (!shareContext)
                    return Context.SaveChanges();
    
                return 0;
            }
    
            public virtual int Delete(EntityObject entity)
            {
                Context.AttachTo(ObjectSet.EntitySet.Name, entity);
                Context.DeleteObject(entity);
                
                if (!shareContext)
                    return Context.SaveChanges();
                return 0;
            }
    
            public virtual int Delete(Expression<Func<EntityObject, bool>> predicate)
            {
                var objects = Filter(predicate);
    
                //foreach (var obj in objects)
                    //ObjectSet.DeleteObject(obj);
    
                foreach (var obj in objects)
                    Context.DeleteObject(obj);
    
                if (!shareContext)
                    return Context.SaveChanges();
    
                return 0;
            }
    
            internal IQueryable<EntityObject> FullTextSearch(IQueryable<EntityObject> queryable, string searchKey, bool exactMatch)
            {
                ParameterExpression parameter = Expression.Parameter(typeof(EntityObject), "c");
    
                MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
                MethodInfo toStringMethod = typeof(object).GetMethod("ToString", new Type[] { });
    
                var publicProperties = typeof(EntityObject).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(p => p.PropertyType == typeof(string));
                Expression orExpressions = null;
    
                string[] searchKeyParts;
                if (exactMatch)
                {
                    searchKeyParts = new[] { searchKey };
                }
                else
                {
                    searchKeyParts = searchKey.Split(' ');
                }
    
                foreach (var property in publicProperties)
                {
                    Expression nameProperty = Expression.Property(parameter, property);
                    foreach (var searchKeyPart in searchKeyParts)
                    {
                        Expression searchKeyExpression = Expression.Constant(searchKeyPart);
                        Expression callContainsMethod = Expression.Call(nameProperty, containsMethod, searchKeyExpression);
                        if (orExpressions == null)
                        {
                            orExpressions = callContainsMethod;
                        }
                        else
                        {
                            orExpressions = Expression.Or(orExpressions, callContainsMethod);
                        }
                    }
                }
    
                MethodCallExpression whereCallExpression = Expression.Call(
                    typeof(Queryable),
                    "Where",
                    new Type[] { queryable.ElementType },
                    queryable.Expression,
                    Expression.Lambda<Func<EntityObject, bool>>(orExpressions, new ParameterExpression[] { parameter }));
    
                return queryable.Provider.CreateQuery<EntityObject>(whereCallExpression);
            }
    
            public void Dispose()
            {
                if (shareContext && (Context != null))
                    Context.Dispose();
            }
        }

    My UserRepositoryClass

      public class UserRepository : Repository<User>, IUserRepository
        {
            /// <summary>
            ///  USer reporsitory : Insert updated delete Selectall  <see cref=" UserRepository"/>
            /// </summary>
            public UserRepository()
                : base()
            {
            }
    
            /// <summary>
            ///  User Repostiroy 
            /// </summary>
            /// <see cref=" UserRepository"/>
            /// <param name="context"> Pass Datacontext</param>
            public UserRepository(MyDatabaseContext context)
                : base(context)
            {
    
            }
    
            /// <summary>
            ///  Select user 
            /// </summary>
            /// <param name="id"> Pass User ID </param>
            /// <returns> return User list</returns>
            public User Select(Guid id)
            {
    
                var selectedUser = ObjectSet.Where(c => c.Id == id).FirstOrDefault();
                if (selectedUser != null)
                {
                    Context.LoadProperty(selectedUser, "ApplicationUser");
                    Context.LoadProperty(selectedUser, "Templates");
                    Context.LoadProperty(selectedUser, "UserGroups");
                }
                return selectedUser;
            }
    
            /// <summary>
            ///  Search From any text in User
            /// </summary>
            /// <param name="Text"> Enter Text </param>
            /// <returns> Return list computer</returns>
            public List<User> SelectFromText(string text)
            {
                return ObjectSet.Where(c => c.FirstName.Contains(text) || c.LastName.Contains(text)).ToList();
            }
    
            public IQueryable<User> FullTextSearch(string searchKey, bool exactMatch)
            {
                var users = base.FullTextSearch(ObjectSet.AsQueryable(), searchKey, exactMatch);
                return users;
            }
    
           }

    My RepositoryContext class : from here I shares my ObjectContext class instance. I also want to know that as per my project architecture is it a right way to shave ObjectContext to all among Repositories

     /// <summary>
        /// Implements IDALContext and implements all read only repository properties
        /// Also implements 
        /// </summary>
        public class RepositoryContext : IRepositoryContext
        {
            #region Global variables
            private MyDatabaseContext DatabaseContext;
            private IRepository<Group> _groups;
            private IUserRepository _users;
            private IRepository<ApplicationUser> _applicationUser;
            private IRepository<Template> _templates;
    
            #endregion
    
            #region Constructor
            public RepositoryContext()
            {
                DatabaseContext = new MyDatabaseContext();
            }
            #endregion
    
            #region Add properties for each Repository
    
            public IRepository<Group> Groups
            {
                get
                {
                    if (_groups == null)
                        _groups = new Repository<Group>(DatabaseContext);
                    return _groups;
                }
            }
    
            public IUserRepository Users
            {
                get
                {
                    if (_users == null)
                        _users = new UserRepository(DatabaseContext);
                    return _users;
                }
            }
    
            public IRepository<ApplicationUser> ApplicationUsers
            {
                get
                {
                    if (_applicationUser == null)
                        _applicationUser = new Repository<ApplicationUser>(DatabaseContext);
                    return _applicationUser;
                }
            }
    
            
            public IRepository<Template> Templates
            {
                get 
                {
                    if (_templates == null)
                        _templates = new Repository<Template>(DatabaseContext);
    
                    return _templates;
                }
            }
    
          #endregion
    
            #region Implementation of IDisposable
            public void Dispose()
            {
                if (_users != null)
                    _users.Dispose();
                if (_groups != null)
                    _groups.Dispose();
                if (_applicationUser != null)
                    _applicationUser.Dispose();            
                if (_roles != null)
                    _roles.Dispose();
                if (_privileges != null)
                    _privileges.Dispose();
                if (_userGroups != null)
                    _userGroups.Dispose();
                if (_userGroupPrivileges != null)
                    _userGroupPrivileges.Dispose();
                if (_templates != null)
                    _templates.Dispose(); 
    
                if (DatabaseContext != null)
                    DatabaseContext.Dispose();
                GC.SuppressFinalize(this);
            }
            #endregion
    
            #region IUnitOfWork Implementation
            public void Savechanges()
            {
                DatabaseContext.SaveChanges();
            }
            #endregion
        }

    My BusinessManager class : here I am writing my businesslogic and this class will be called from controller

     public class CatalogManager : ICatalogManager
        {
            #region Global Fields or variables
            /// <summary>
            ///  this Variable Define for Communicate with database
            /// </summary>
            private IRepositoryContext context = null;
            #endregion
    
            #region Constructor
    
            /// <summary>
            /// this contrstuctor call with everytime with new repository
            /// </summary>
            /// <see cref="CatelogManager"/>
            /// <param name="repositoryContext"> pass repository</param>
            public CatalogManager(RepositoryContext repositoryContext)
            {
                this.context = repositoryContext;
            }
    
            #endregion
    
            #region ICatalogManager Implementation
    
            #region User Related Operations
    
            /// <summary>
            ///  Creat New user
            /// </summary>
            /// <param name="user"> Pass User Inforamtion</param>
            /// <returns> Return Added user</returns>
            public User UserAdd(User user)
            {
                User result = null;
    
                try
                {
                    result = this.context.Users.Create(user);
                    this.context.Savechanges();
                    return user;
                }
                catch (Exception ex)
                {
                    ExceptionService.HandleException(ex);
                }
    
                return result;
            }
    
            /// <summary>
            ///  Select particular User For Edit
            /// </summary>
            /// <param name="id">pass UseID</param>
            /// <returns>return User</returns>
            public User UserSelect(Guid id)
            {
                User result = null;
    
                try
                {
                    result = this.context.Users.Select(id);
                }
                catch (Exception ex)
                {
                    ExceptionService.HandleException(ex);
                }
    
                return result;
            }
    
           
    
            /// <summary>
            ///  Update Selected User
            /// </summary>
            /// <param name="user"> Updated User</param>
            /// <returns>Upadated USer</returns>
            public User UserEdit(User user)
            {
                try
                {
                    if (user.Templates != null && user.Templates.Count > 0)
                    {
                        foreach (var item in user.Templates)
                        {
                            if (this.context.Templates.Find(template => template.Id == item.Id) != null)
                            {
                                //this.context.Templates.Detach(item);
                                this.context.Templates.Update(item);
                            }
                            else
                            {
                                this.context.Templates.Create(item);
                            }
                        }
                    }
    
                    //this.context.Users.Detach(user);
                    this.context.Users.Update(user);
                    this.context.Savechanges();
    
                }
                catch (Exception ex)
                {
                    ExceptionService.HandleException(ex);
                }
    
                return user;
            }
    
          }


    Regards, Hiren Bharadwa


    Monday, September 24, 2012 9:27 AM

All replies

  • reminder!!!

    Regards, Hiren Bharadwa

    Tuesday, September 25, 2012 5:34 AM
  • Hi Himo,

    Welcome to the MSDN forum.

    For your first problem, If you load the entity from the context you cannot attach an entity with the same key again. The first entity is still kept in internal context cache and context can hold only one instance with given key value per type. If you want to update an existing record in database, please try this:

            public virtual Int32 Update(EntityObject entity)
            {
                Context.CreateObjectSet<EntityObject>().AddObject(entity);
                Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
    
                if (!shareContext)
                    return Context.SaveChanges();
    
                return 0;
            }
    

    For your second problem, please check this article: http://blog.oneunicorn.com/2012/05/03/the-key-to-addorupdate/

    Although this article is using DbContext instead of ObjectContext, all the same things can be done by ObjectContext.

    Good day.


    Alexander Sun [MSFT]
    MSDN Community Support | Feedback to us

    Tuesday, September 25, 2012 7:02 AM
  • Hello Mr. Alexander

    Thanks for your response.

    I have seen you post and also seen the link which you have sent with post. It is very helpful for me.

    In previous post I asked one more question was that, as per my project architecture is it a right way to share ObjectContext to all among Repositories?

    Please see my RepositoryContext class where I am sharing my ObjectContext.

    I am waiting for your valuable reply.

    Thanks.


    Regards, Hiren Bharadwa

    Tuesday, September 25, 2012 1:20 PM
  • any update on my previous post?

    Regards, Hiren Bharadwa


    Wednesday, September 26, 2012 12:58 PM
  • Hi Hiren,

    In my humble opinion, the RepositoryContext class shares the context in a right way. You can refer to this page: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

    Good day.


    Alexander Sun [MSFT]
    MSDN Community Support | Feedback to us

    Thursday, September 27, 2012 6:43 AM
  • Hello, Mr. Alexander

    Thank you very much for your guidence.

    I was little bit confiused regarding ObjectContext sharing because every time when I fire any event from my asp.net mvc (view) page,every time RepositoryContext object instantiates.

    For example :

    when I call Select method of UserRepository to get a user by it's Id, DatabaseContext class obj gets instantiated through Constructor of RepositoryContext class. Same thing happens(new DatabaseContext class obj gets instantiated) when I edit that selected user and try to call Edit method to save changes in the database.

    So can I say that the obj by which my user obj has been selected and will get update are the same Object of DatabaseContext class (means shared)??


    Regards, Hiren Bharadwa

    Friday, September 28, 2012 6:01 AM