Generic Entity Object Update throws Exception

7 posts, 0 answers
  1. Chirs
    Chirs avatar
    4 posts
    Member since:
    Sep 2012

    Posted 08 Feb 2014 Link to this post

    I have written generic methods for Add, Update, Delete, Get Single and Get All, in which i am passing a T type object where T is the Model Class object that has been generated by Telerik Data Access. All is working well except the only issues that occur in the Update method. "AttachCopy cannot attach yet instances that are not clean or modified or new." this exception is thrown when i am updating an entity only when changing the foreign key field of an entity. for example i have Offer Table in which i have added a foreign Key field that is MerchantID, when i change all the field except this foreign key field it works good all the data is updated but when i change the MerchantID it throws the above exception. i have searched over the forum and found many threads but none of these worked for me.
    can you please tell me what status should i check for update case. I need that the object passed should be updated if any of the field has been changed. If there is no field changed then do not update. the following is my method I have used. The context factory is a class which only return the new instance of the context. i have used this method in many of the pages. It will be very hard for me to re-write the whole update method for more than hundred tables. Please help me.

    public static void Update(T t)
            {
                try
                {
                    using (MyModel ctx = Utility.ContextFactory.ObtainContext())
                    {
                        PropertyInfo lastUpdated = t.GetType().GetProperties().Where(x => x.Name == "LastUpdated").FirstOrDefault();
                        if (lastUpdated != null)
                        {
                            lastUpdated.SetValue(t, DateTime.Now, null);
                        }
                        ObjectState state = OpenAccessContext.PersistenceState.GetState(t);
                        if (state == Telerik.OpenAccess.ObjectState.New || state == Telerik.OpenAccess.ObjectState.Dirty)
                            ctx.AttachCopy(t);
                        ctx.SaveChanges();
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                    //
                }
            }

  2. Thomas
    Admin
    Thomas avatar
    590 posts

    Posted 10 Feb 2014 Link to this post

    Hello Chris,

    the exception says, that the instance t (which you try to attach) is not clean, dirty or new. Please check where you got this instance from, maybe this is an instance that was not really detached or detached as a proxy only.

    Regards,
    Thomas
    Telerik
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
  3. DevCraft banner
  4. Chirs
    Chirs avatar
    4 posts
    Member since:
    Sep 2012

    Posted 10 Feb 2014 in reply to Thomas Link to this post

    Thanks Thomas for Quick response,
            Can you please tell me if the foreign key field is changed then what will be the state of the object?, and it is all working good except when the foreign key field is changed.
            For the detached option, I want to confirm that i am getting one object, and access the properties of another object related to it for example if I get the object of Customer, I get the billing info, state and country info as customer.Billing.CardNumber, customer.State.Abbreviation and customer.Country.Name. And this is not for only object of object, in many case I use to get object of object of another object. So my question is that if I detach the Entity object from context whether it will allow me to get such properties of related object or i have to use the Include function to get the related data. Well Include function will create problem for me, as i will have to write this for more than 100 entities.
  5. Chirs
    Chirs avatar
    4 posts
    Member since:
    Sep 2012

    Posted 10 Feb 2014 in reply to Chirs Link to this post

    I have also noticed that this exception is thrown for only those foreign key fields which are set to null able in database. those fields are not null, changing their values work fine.
  6. Thomas
    Admin
    Thomas avatar
    590 posts

    Posted 11 Feb 2014 Link to this post

    Hi Chirs,

    I should have looked more carefully on the code that you pasted: I think there is a mistake visible.

    if (state == Telerik.OpenAccess.ObjectState.New || state == Telerik.OpenAccess.ObjectState.Dirty)
                            ctx.AttachCopy(t);

    But the state for object t should always include the Detached bit! Or should be MaskNoMask, in case your detached object is a freshly created one. Which indicates to me that the instance t is actually managed by a context and should not be attached into a new one (ctx).

    So, I would try with this rewrite (not compile-checked by me):

    public static void Update(T t)
    {
         PropertyInfo lastUpdated = t.GetType().GetProperties().Where(x => x.Name == "LastUpdated").FirstOrDefault();
         if (lastUpdated != null)
         {
             lastUpdated.SetValue(t, DateTime.Now, null);
         }
         ObjectState state = OpenAccessContext.PersistenceState.GetState(t);
         if (state & Telerik.OpenAccess.ObjectState.NotManaged) == 0 &&
             (state & Telerik.OpenAccess.ObjectState.Detached) == 0)
        {
              return; // or throw an exception, because t is neither new nor detached...
        }
        try
        {
             using (MyModel ctx = Utility.ContextFactory.ObtainContext())
             {
                    ctx.AttachCopy(t);
                    ctx.SaveChanges();
             }
         }
         catch (Exception ex)
         {
               throw;
         }
    }

    The key is that t must be new or detached. Otherwise attaching is not correct - an existing context could manage the instance already. So, again, who generates/passes in the instance t ?

    I tried with nullable foreign key columns, but this does not make a dfference for me.

    Regards,
    Thomas
    Telerik
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
  7. Chirs
    Chirs avatar
    4 posts
    Member since:
    Sep 2012

    Posted 12 Feb 2014 in reply to Thomas Link to this post

    Hi Thomas, 
    I used your code and still the issue is there. I will explain in detail what i wanted to do. Basically i have more than 100 tables and many of the tables are updated in more than one places in my project. for convenience i have created a class with generic method, all i need to pass the T type object and the operation of Add, Update, Delete and Get are performed on fly.
    Now i have reached to the exact Error place. The Issue occurs only when there is more than one relation with the same table and changing value of any of them throws exception. Here are the realtions I have http://screencast.com/t/Fg2h3OUDCSU  and the following are the classes I am using.

    public static class ContextFactory 
        
            private static CrmModel context; 
      
            public static CrmModel ObtainContext()
            
               //if (context == null) 
                context = new CrmModel(Common.ConnectionString);
                return context; 
            
        }
     
    public abstract class DataUtil<T>
        {
            private static string TypeName
            {
                get
                {
                    Type type = typeof(T);
                    string pluralise = "";
                    System.Data.Entity.Design.PluralizationServices.PluralizationService pls = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(new System.Globalization.CultureInfo("en"));
                    if (pls.IsSingular(type.Name))
                        pluralise = pls.Pluralize(type.Name);
                    else
                        pluralise = type.Name;
                    return pluralise;
                }
            }
     
            public static T Get(object id)
            {
                try
                {
                    using (CrmModel ctx = Utility.ContextFactory.ObtainContext())
                    {
                        var obj = ((IQueryable<T>)ctx.GetType().GetProperties().Where(x =>
                             x.Name == TypeName).FirstOrDefault().GetValue(ctx, null)).ToList().Where(y =>
                                 //id
                            (y.GetType().GetProperties().Where(z =>
                            z.Name == "ID").FirstOrDefault().GetValue(y, null) == null ? string.Empty :
                            y.GetType().GetProperties().Where(z =>
                            z.Name == "ID").FirstOrDefault().GetValue(y, null)).Equals(id)).FirstOrDefault();
                        return obj;
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                    //return default(T);
                }
            }
     
            public static void Update(T t)
            {
                try
                {
                    Telerik.OpenAccess.ObjectState state = Telerik.OpenAccess.OpenAccessContext.PersistenceState.GetState(t);
     
                    if ((state & Telerik.OpenAccess.ObjectState.NotManaged) == 0 &&
     (state & Telerik.OpenAccess.ObjectState.Detached) == 0)
                    {
                        return;// or throw an exception, because t is neither new nor detached...
                    }
                    using (CrmModel ctx = Utility.ContextFactory.ObtainContext())
                    {
                        if (state == Telerik.OpenAccess.ObjectState.New || state == Telerik.OpenAccess.ObjectState.Dirty)
                            ctx.AttachCopy(t);
                        ctx.SaveChanges();
                    }
                }
                catch (Exception ex)
                {
                    //throw ex;
                    //
                }
            }
        }


    what I do is to get the object using the Get method, and on page I change the properties required, and then pass again to the update method. the context object is disposed after getting or updating the object. I also tried to rename the property in Model design view but no worth.

    Regards
  8. Thomas
    Admin
    Thomas avatar
    590 posts

    Posted 12 Feb 2014 Link to this post

    Hi Chirs,

    now, the issue becomes more clear: The Get method is not returning a detached object, but one that is still 'managed' by the already disposed context! This is, because you dispose it as in this page. So either you leave the context alive (but you must still control it's lifetime) or you need to make a detached copy. See
    here for more information.

    Also, I think that your code to fetch the one instance identified by id is flawed in another way. Firstly, you don't need to guess the name of the property on the context because you have the type, therefore you can use the context.GetAll<T>() method (which just happens to be wrapped by the property that you accessed). Secondly, and more importantly: Immediately after the property is access, your code calls .ToList(), which will fetch all objects of that type into the client immediately. This is inefficient for larger number of instances.

    A common pattern I've found useful in these kind of situations is to have an interface which declares, that there is an Id property. Later you use this interface to call real LINQ queries against this interface.
    interface IHaveAnID
    {
        int Id { get; }
    }
     
    private T Get<T>(int id) where T : IHaveAnID
    {
        var queryable = Context.GetAll<T>();
        return queryable.FirstOrDefault(x => x.Id == id);
    }

    Hope this solves it.
    Regards,
    Thomas
    Telerik
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
Back to Top
DevCraft banner