This is a migrated thread and some comments may be shown as answers.

Add-or-Update entity with its associations

9 Answers 552 Views
Getting Started
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Mehdi
Top achievements
Rank 1
Mehdi asked on 04 Apr 2013, 10:08 PM
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.

9 Answers, 1 is accepted

Sort by
0
Mehdi
Top achievements
Rank 1
answered on 07 Apr 2013, 09:58 AM
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 .
0
Yordan
Telerik team
answered on 09 Apr 2013, 03:46 PM
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 >>
0
Mehdi
Top achievements
Rank 1
answered on 09 Apr 2013, 08:25 PM
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!
0
Accepted
Yordan
Telerik team
answered on 11 Apr 2013, 04:11 PM
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 >>
0
Mehdi
Top achievements
Rank 1
answered on 13 Apr 2013, 06:27 PM
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.
0
Yordan
Telerik team
answered on 17 Apr 2013, 04:16 PM
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 >>
0
Jeff
Top achievements
Rank 1
answered on 09 Jul 2013, 03:41 PM
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.
0
Yordan
Telerik team
answered on 11 Jul 2013, 03:49 PM
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.
0
Farhad
Top achievements
Rank 1
answered on 23 Sep 2016, 07:25 AM
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.
Tags
Getting Started
Asked by
Mehdi
Top achievements
Rank 1
Answers by
Mehdi
Top achievements
Rank 1
Yordan
Telerik team
Jeff
Top achievements
Rank 1
Farhad
Top achievements
Rank 1
Share this question
or