Add-or-Update entity with its associations

10 posts, 1 answers
  1. Mehdi
    Mehdi avatar
    9 posts
    Member since:
    Jun 2012

    Posted 04 Apr 2013 Link to this post

    Hi
    I'm working on winforms project with openaccess as my orm and this is my first try of openaccess orm. 
    I have some question:
    In your examples, you define a private variable on top of form holding data context. is this ok to keep context such a long-lived?
    If i use a short-lived context like using(var context=new DataContext()) to load entities at form load event and then try to save changes at save button clicked event use another short-lived context, i'v got errors like: "Object references between two different object scopes are not allowed." which is understandable. but is there any solution for such a case? i mean using short-lived context and disconnected entities. maybe something like AsNoTracking() in entity framework but more clever!

    Second, is there any easy way to Add-or-Update an entity with all associations in openaccess? Does AttachCopy method do it all?
    Consider my models like:
    City with Id and Name props and Stations colletion.
    Station with Id and Name props and City reference.
    I have a form for add/edit a city which has textboxes for city props and a grid showing a list of associated stations. there is add, edit & delete buttons for stations grid. add/edit stations happens at a new form, but i want to saving changes fire once when save button on city form was clicked(not for each new or edited station). so when city form save button clicked i have a new or edited city with collection of new or edited or even deleted stations. :?
    So what's the more efficient way to do this scenario? I think it is easier to use a one context for each form, but what if i want saving happens only on parent entity form?
    Sorry for the long story ;)
    Thanks.
  2. Mehdi
    Mehdi avatar
    9 posts
    Member since:
    Jun 2012

    Posted 07 Apr 2013 Link to this post

    Ok, i defined a private variable for context on each form, then used Add method not AttachCopy to add new or update existing entities.
    However transactions goes well until now, but i really don't like this way!
    In a complex db relationships sometimes 3 or more forms was opened on top of each other to add/edit each associated entity in a different form. so using a long lived context for each form may not be a good idea. don't you think so?
    Here is a sample and solution for Entity Framework : Reattaching detached objects in short Lived contexts .
  3. DevCraft banner
  4. Yordan
    Admin
    Yordan avatar
    39 posts

    Posted 09 Apr 2013 Link to this post

    Hello Mehdi,

    There should not be any problems when you use long-lived dbcontext objects in your forms. You can follow the pattern described in this article. Alternatively you could use short-living dbcontext 
    objects, but you have to call SaveChanges before the context is disposed.
    If you want to pass entities from one form to another you can use the CreateDetachedCopy  method from the first context and than attach the object using the AttachCopy method to the second context and call SaveChanges on the latter. If you use this approach and want to detach a graph of objects you have to use an appropriate FetchStrategy. You could use a FetchStrategy in a similar way:

    1.public void DetachObjectGraph( EntitiesModel dbContext )
    2.{
    3.   FetchStrategy fetchStrategy = new FetchStrategy();
    4.   fetchStrategy.LoadWith<Employee>( e => e.Orders );
    5.   List<Employee> employees = dbContext.Employees.ToList();
    6.   var detachedEmployees = dbContext.CreateDetachedCopy<Employee>( employees, fetchStrategy );
    7.}

    To save an object and its relations you may use code similar to the following:

    01.Category category = new Category();
    02.category.CategoryName = "My Category";
    03.category.Description = "Some Description";
    04.Product product = new Product();
    05.product.Discontinued = false;
    06.product.ProductName = "My Product";
    07.product.Category = category;
    08.// It is enough just to pass the root object in the hierarchy.
    09.dbContext.Products.Add(product);
    10.// dbContext.Categories.Add(category); --- not needed.
    11.dbContext.SubmitChanges();

    The important line is number 7: product.Category = category;. It makes the relationship between the Category and the Product objects (in your case City and Station). More information about working with relations can be found in this article.

    I hope that this helps. If you need more assistance about handling of your context and  managing relations between objects please do not hesitate to contact us again.
     
    Kind regards,
    Yordan
    the Telerik team
    Using Encrypted Connection Strings with Telerik OpenAccess ORM. Read our latest blog article >>
  5. Mehdi
    Mehdi avatar
    9 posts
    Member since:
    Jun 2012

    Posted 09 Apr 2013 Link to this post

    Thanks for reply, but i couldn't manage how to use short-lived context without getting this error:
    "Object references between two different object scopes are not allowed."
    I could manage this exception only if i use CreateDetachCopy method for loading my entities from db.
     This is what i use in form load event:
    using (var context = new DataContext())
    {
        customers = context.Customers.Include(c => c.Orders).ToList();
        //calling SaveChanges or ClearChanges here doesn't help!
    }
    Note: the private variable of customers now contains list of loaded entites from db.
    Then i try add new customer to the customers list:
    var cust = new Customer {Name = "John Smith" };
    customers.Add(cust);
    Finally i try to send customers list changes into the db:
    using (var context = new DataContext())
    {
        context.Add(customers);
        context.SaveChanges(); //here i'v got exception.
    }
    So, as you can see disposing the first short-lived context at the form load event does not cause the state of already loaded object changed to un-attach state or something like that. so when i try to save all changes(newly added entities or edited ones) with the second short-lived context, it complains about "two different object scopes" for entities already loaded from db Not the new ones.
    I think there should be a way to really dispose the short-lived context and release the loaded entities by that context.
    Please help me!
  6. Answer
    Yordan
    Admin
    Yordan avatar
    39 posts

    Posted 11 Apr 2013 Link to this post

    Hello Mehdi,

    If you want to use short-lived dbcontext objects you have to use the CreateDetachedCopy() and AttachCopy() methods . You should call those methods because you save a collection on a context other than the context that created the collection. You can use code similar to the following:
    List<Customer> customers;
    using (EntitiesModel context = new EntitiesModel())
    {             
        FetchStrategy fetchStrategy = new FetchStrategy();
        fetchStrategy.LoadWith<Customer>(c => c.Orders);
        customers = context.CreateDetachedCopy(context.Customers.ToList(), fetchStrategy);
    }
     
    // do operations with customers ...
     
    using (EntitiesModel context2 = new EntitiesModel())
    {              
        customers.ForEach(customer => context2.AttachCopy(customer));
        // Do not use this method for attaching objects, use it only for attaching one object
        // context2.AttachCopy(customers);
        context2.SaveChanges();                   
    }

    Please try if this way of managing objects works for you. If you encounter any issues contact us again for assistance.

    Greetings,
    Yordan
    the Telerik team
    Using Encrypted Connection Strings with Telerik OpenAccess ORM. Read our latest blog article >>
  7. Mehdi
    Mehdi avatar
    9 posts
    Member since:
    Jun 2012

    Posted 13 Apr 2013 Link to this post

    Thanks, using CreateDetachedCopy with short-lived context works!
    But i wonder why the "OpenAccessEnhancedStateManager" of newly added entity to the detached-copy collection (i.e. a new customer was added to detached-copy of customers collection) is always null, even after multiple SaveChanges?  In other words the new entity doesn't update by context.SaveChanges(). so the auto-increment primary keys doesn't get new values from db. is that right?
    In this scenario, i think after every context.SaveChanges(), the detached copy of entities should recreate to get updates from database.
  8. Yordan
    Admin
    Yordan avatar
    39 posts

    Posted 17 Apr 2013 Link to this post

    Hi Mehdi,

    Yes you are right. The detached collection should be recreated after SaveChanges() is called. When saving objects to the database you have to fetch those objects again to access the updated fields. For example consider Category object. Category has an auto increment primary key. When you save new category in the database you have to re-fetch it to access the value of the primary key. The following code is an example how to do it:

    static void Main(string[] args)
    {
        List<Category> categories;
        FetchStrategy fetchStrategy = new FetchStrategy();
        fetchStrategy.LoadWith<Category>(c => c.Products);
     
        using (EntitiesModel context = new EntitiesModel())
        {
     
            categories = context.CreateDetachedCopy(context.Categories.ToList(), fetchStrategy);
        }
     
        // do operations with categories ...
     
        using (EntitiesModel context2 = new EntitiesModel())
        {
            Category newCategory = new Category() { CategoryName = "new category", Description = "test description" };
            categories.Add(newCategory);
     
            categories.ForEach(cat => context2.AttachCopy(cat));
     
            context2.SaveChanges();
     
            categories = context2.CreateDetachedCopy(context2.Categories.ToList(), fetchStrategy);
            // Here we have the new primary key of the category
            int lastCategoryNumber = categories.Last().CategoryID;
        }
    }

    Please use this approach in your scenario. If you have any further questions about managing objects in detached state do not hesitate to contact us.
     
    Greetings,
    Yordan
    the Telerik team
    Using Encrypted Connection Strings with Telerik OpenAccess ORM. Read our latest blog article >>
  9. Jeff
    Jeff avatar
    10 posts
    Member since:
    Oct 2010

    Posted 09 Jul 2013 Link to this post

    Hi Yordan!

    I am running into a variation of Mehdi's problems.  Except, what I am finding is a total lack of documentation of how to accomplish the following:

    Suppose in the code sample that you provided, I wanted to create a new Categories object, and keep it detached from the Model until the user had indicated that they wanted to save their changes.  In other words, I want the model to a) be short-lived, b) the persistant types to behave as Plain Old CLR Objects, and c) be able to add the persistant type instance to the model when I want to commit the changes to the database.

    I tried to add my object to the Model and then create a detached copy, but the Model throws an exception that only clean and modified objects can be detached (despite the documentation's reference to ObjectState.DetechedNew).

    Let me know what you suggest.

    j.
  10. Yordan
    Admin
    Yordan avatar
    39 posts

    Posted 11 Jul 2013 Link to this post

    Hello Jeff,

    When you deal with a new entity there is no need for attached/detached copies for that entity. You could just create the new Category (as shown in the example) and on some later stage you may attached it and save it. The code could be similar to the following:
    // You can get a new category from some custom place, a session for example
    Category category = new Category() { CategoryName = "newCatName" };
     
    // When you finished editing the new category just attach it and save it to 
    // this short lived context - there is no need for detached object
    using (EntitiesModel dbContext = new EntitiesModel())
    {
        dbContext.Add(category);
        dbContext.SaveChanges();
    }

    If the proposed approach is not suitable in your scenario please let us know and we'll find more appropriate way of dealing with the new entities.

    Regards,
    Yordan
    Telerik
    OpenAccess ORM Q2 2013 brings you a more powerful code generation and a unique Bulk Operations support with LINQ syntax. Check out the list of new functionality and improvements shipped with this release.
  11. Farhad
    Farhad avatar
    1 posts
    Member since:
    Sep 2016

    Posted 23 Sep in reply to Mehdi Link to this post

    In my opinion it is worth to say that with the newly released EntityGraphOperations for Entity Framework Code First you can save yourself from writing some repetitive codes for defining the states of all entities in the graph. You can learn how to uses package from Github page.
    It will automatically set the state of the entities to Added or Modified. And you will manually choose which entities must be deleted if it is not exist anymore.
    The example:
    Let’s say I have get a Person object. Person could has many phones, a Document and could has a spouse.
    public class
    public class Person
    {
         public int Id { get; set; }
         public string FirstName { get; set; }
         public string LastName { get; set; }
         public string MiddleName { get; set; }
         public int Age { get; set; }
         public int DocumentId {get; set;}
     
         public virtual ICollection<Phone> Phones { get; set; }
         public virtual Document Document { get; set; }
         public virtual PersonSpouse PersonSpouse { get; set; }
    }

    I want to determine the state of all entities which is included in the graph.
    context.InsertOrUpdateGraph(person)
           .After(entity =>
           {
                // Delete missing phones.
                entity.HasCollection(p => p.Phones)
                   .DeleteMissingEntities();
     
                // Delete if spouse is not exist anymore.
                entity.HasNavigationalProperty(m => m.PersonSpouse)
                      .DeleteIfNull();
           });

    Also as you know unique key properties could play role while defining the state of Phone entity. For such special purposes we have ExtendedEntityTypeConfiguration<> class, which inherits from EntityTypeConfiguration<>. If we want to use such special configurations then we must inherit our mapping classes from ExtendedEntityTypeConfiguration<>, rather than EntityTypeConfiguration<>.

     

    For example:

    public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
        {
            public PhoneMap()
            {
                 // Primary Key
                 this.HasKey(m => m.Id);
                  
                 // Unique keys
                 this.HasUniqueKey(m => new { m.Prefix, m.Digits });
            }
        }

    That’s all.
Back to Top
DevCraft banner