Refetch Related "Child Collection" From Database With adapter

Posts   
 
    
Sam avatar
Sam
User
Posts: 95
Joined: 30-Jun-2004
# Posted on: 22-Feb-2006 23:46:53   

How do I refetch entity collections that are part of an object graph vai relationships? Say I have the following.


            using (DataAccessAdapter adapter = DataAdapter)
            {
                myContext.Add(myEntity.SomeCollectionViaSomeTable);
                adapter.FetchEntityCollection(myEntity.SomeCollectionViaSomeTable, myEntity.GetRelationInfoSomeCollectionViaSomeTable(), 0, sorter, path);
            }

So now I have an object graph with myEntiy say Customer and a Collection of cutomer say Customer.OrdersViaCustomerOrders. Now I some form but if the user hits cancel I want to refetch the entity so I try:


            myContext.Remove(entity);
            using (DataAccessAdapter adapter = DataAdapter)
            {
                adapter.FetchEntity(myEntity, filter, 0, sorter, path);
            }

Now the entity is reset just as I would expect. However the collection is still "dirty". Even if I now if I recall:


            foreach (IEntity2 entity in myEntity.SomeCollectionViaSomeTable)
                myContext.Remove(entity);
            using (DataAccessAdapter adapter = DataAdapter)
            {
                adapter.FetchEntityCollection(myEntity.SomeCollectionViaSomeTable, myEntity.GetRelationInfoSomeCollectionViaSomeTable(), 0, sorter, path);
            }

I get old, dirty, mucked up by user collection Cutomer.OrdersViaCustomerOrders that I wanted to refetch from the database. It is like it sees the entity in the collection and doesnt' refetch it? I probably have something messed up with my context. this is my first stab at it.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39616
Joined: 17-Aug-2003
# Posted on: 23-Feb-2006 10:43:54   

A context is used to re-use existing entity OBJECTs, so if you don't need the exact same instance, don't use a context. Often you can simply refetch the data in a new instance and be done with it, only in the situations where you need to re-use an existing object because it is bound to a control or other objects you keep alive have references to it, you need to refill that object.

What I don't get is when you refetch an entity which you removed from a context, the entity's active context object is reset and also its contained objects have their active context being reset so if you then refetch the same instance, no context is consulted to see if it contains an instance with that data...

UNLESS, you keep the dirty data in the collection. Do a clear of the child collection (customer.Orders.Clear()) before the fetch.

Frans Bouma | Lead developer LLBLGen Pro
Sam avatar
Sam
User
Posts: 95
Joined: 30-Jun-2004
# Posted on: 23-Feb-2006 17:13:22   

I tried that but get an InvalidOperationException: "This collection is read-only." when I try: customer.OrdersViaOrderCustomer.Clear(). I have also done: customer.OrdersViaOrderCustomer.AllowRemove = true;

If it helps this code does work:

            
foreach (IEntity2 entity in entityCollectionToFill)
            using (DataAccessAdapter adapter = DataAdapter)
            {
                adapter.FetchEntity(entity, path, myContext);
            }
            return entityCollectionToFill;

Obviously I don't want to iterate through each entity and re fetch it individually.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39616
Joined: 17-Aug-2003
# Posted on: 23-Feb-2006 17:56:52   

though how can there be dirty data in the m:n collection? You bind that collection to the grid? It's unclear to me what exactly you're doing (the steps, and to which state you want to roll back). are the order objects also loaded in another collection? (thus via teh intermediate entities?)

Frans Bouma | Lead developer LLBLGen Pro
Sam avatar
Sam
User
Posts: 95
Joined: 30-Jun-2004
# Posted on: 23-Feb-2006 22:37:09   

Thanks for the response

I oversimplified the example a little bit. I am dealing with customers and contacts. Contacts are a generic entity that can be assigned to customers (a contact at the customer) or the contact could be assigned to an order or something. So I have a table/entity Contact. Then I have tables like CustomerContact and OrderContact to join Contacts to Customers and/or Orders. That is the reason for the intermediate entity.

What I have is a grid of customers. When you select a customer you get a dialog to edit details. Within this Dialog is a grid of contacts. What I would like is that if a user clicks cancel I want to refetch the entity from persistent storage (works great) additionally discard any changes/additions/deletes that may have been made to any contact for that customer. How can I achieve this behavior? I have tried Customer.CutomerContact.Clear() and refetching the Contents collection using: Customer.GetRelationInfoContactCollectionViaSalesSubjectContact() but I get back the modified collection. I know that I am being an idiot but I am really struggling with this. Right now I'm looping through and refetching each entity in the collection frowning on cancel. Thanks again for your help.

bclubb
User
Posts: 934
Joined: 12-Feb-2004
# Posted on: 24-Feb-2006 04:07:48   

Can you post the exact code that is executed when cancel is clicked.

bclubb
User
Posts: 934
Joined: 12-Feb-2004
# Posted on: 24-Feb-2006 05:35:59   
foreach (IEntity2 contact in customer.CustomerContact.DirtyEntities)
{
           adapter.FetchEntity(contact);
}

Try this instead of the clear.

Sam avatar
Sam
User
Posts: 95
Joined: 30-Jun-2004
# Posted on: 27-Feb-2006 22:52:43   

It must be maintaining an list internally cause I cannot shake my reference to these objects. I have the problem in a m:n situations. take the following code to fetch an object graph:


            PrefetchPath2 path = new PrefetchPath2((int)EntityType.SalesSubjectEntity);
            path.Add(SalesSubjectEntity.PrefetchPathSalesSubjectContact).SubPath.Add(SalesSubjectContactEntity.PrefetchPathContact);
            //My helper method to fetch collections: very simple wrapper
            FetchEntityCollection(salesSubject.SalesSubjectContact, null, salesSubject.GetRelationInfoSalesSubjectContact(), path);

Now say on a grid I change salesSubject.SalesSubjectContact[0].Contact.Name = "Frans";

Now the user cancels cause they don't want the name to be "Frans".

I recall the above code "fetch" code, but, salesSubject.SalesSubjectContact[0].Contact.Name will still = "Frans" even if a call salesSubject.SalesSubjectContact.Clear(); and then the above "etch" code it still = "Frans". The only way I can get it reloaded from persistent storage is code like:

foreach (IEntity2 contact in customer.CustomerContact.DirtyEntities) { adapter.FetchEntity(contact); }

However as the object graph gets larger this becomes impractical as you have any queries going on as well as many nested loops to traverse the entire object graph. Is there an easy way to at leas traverse the object graph and re-fetch dirty entities?

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 28-Feb-2006 07:29:14   

I think you should do the following in your "Cancel" logic:

1- Refetch the entire graph using a NEW Root Entity & prefetchpaths, just as you fetched the graph the first time.

2- Re-bind to the newly fetched Objects.

Sam avatar
Sam
User
Posts: 95
Joined: 30-Jun-2004
# Posted on: 28-Feb-2006 22:56:54   

Tried that, it didn't work. I have no clue where these dirty, rogue entities are coming fromconfused Do I need to flush some sort of internal cache?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39616
Joined: 17-Aug-2003
# Posted on: 01-Mar-2006 10:25:10   

I'm setting up a testcase now, but your information is very confusing. You started with an M:N fetch but the last code you posted isn't about an m:n relation. I assume your last code is the ACTUAL code which runs and fails? Please understand that if we have to 'assume' things, we likely will end up having to guess what's going on, and this is both time consuming and frustrating for both you and us simple_smile .

I'll test with Customer.Orders.OrderDetails, change an OrderDetails row and go from there.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39616
Joined: 17-Aug-2003
# Posted on: 01-Mar-2006 11:38:46   

Ok, it's related to the context.

I have: customer.Orders.OrderDetails, filled for the customer "CHOPS". I add customer to a context c. This means that all entities in the graph are added to the context as well. (this is important)

When I alter: customer.Orders[0].OrderDetails[0].Quantity, I get a dirty entity, namely that OrderDetails entity of which I altered the quantity. This entity is in the context customer is in too.

Doing: c.Remove(customer), will remove customer from the context, the containing collections but not the entities inside these collections. This is by design, as adding an entity to a context makes the entity be usable through uniqing so another piece of code could have obtained a reference to that entity through the context and therefore removing it from that same context again, indirectly, isn't what a developer would expect, IMHO.

So, what happens? Well, even though customer has been removed from the context, the order and orderdetail objects aren't. So when I refetch customer, using the same context, I'll get the dirty entity back, as that's in the context used and fetched data is always overruled by dirty data for an entity (to mitigate the chance of ruining edits)

This can be what you want, so if you want this, the context works for you, as it provides the same instances you had, including dirty data.

If you DON'T want this, and you made it clear this is the case for you, the context doesn't work for you, as it gives back entities you want to get rid of.

Taking a step back, what you want is actually rolling back changes to entity fields. So, you want to to go back to a previous state, not to the actual state of a context. So either do one of the following: 1) don't use a context in this situation. The context gives you unique entities, but as I explained above, the unique entities are sometimes altered in your situation and if you don't want these changes to take effect, you have to manually remove them from the context. This isn't that hard, loop through the dirty entities and remove them from the context, but a little illogical. 2) the actual right thing to do here is to use SaveFields(name) on the entities being edited. So in my case, I was going to edit orderdetail entities. Say I do this in a grid (so I bound Orders[0].OrderDetails to a grid), then I first should do:


foreach(OrderDetailsEntity orderDetails in myOrder.OrderDetails)
{
    orderDetails.SaveFields("BeforeEdit");
}

then proceed and let the user edit the fields. If the user cancels, and I want to re-use the state before the edit, (and only if I want to reuse the state before the edit), I have to rollback the fields:


foreach(OrderDetailsEntity orderDetails in myOrder.OrderDetails)
{
    orderDetails.RollbackFields("BeforeEdit");
}

and I'm back to the previous state. I don't have to re-fetch data (saves you roundtrips to the DB), and I also don't have dirty entities anymore as the data has been rolled back.

Frans Bouma | Lead developer LLBLGen Pro
Sam avatar
Sam
User
Posts: 95
Joined: 30-Jun-2004
# Posted on: 01-Mar-2006 18:06:21   

Sorry about providing inconsistant examples. I was trying to provide a simpler generic sample to illustrate the issue. And then got lazy and wanted to copy and paste the real code...

I have tried the SaveFields/RollbackFields before but have found that it is quite a bit faster to just fetch from the database. In fact the rollback was uncomfortably slow. Is this your experience as well?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39616
Joined: 17-Aug-2003
# Posted on: 01-Mar-2006 19:29:31   

Sam wrote:

Sorry about providing inconsistant examples. I was trying to provide a simpler generic sample to illustrate the issue. And then got lazy and wanted to copy and paste the real code...

heh simple_smile . It's ok simple_smile

I have tried the SaveFields/RollbackFields before but have found that it is quite a bit faster to just fetch from the database. In fact the rollback was uncomfortably slow. Is this your experience as well?

The fields are serialized binary which can be slow if you have to rollback a LOT. Though it shouldn't take minutes... just a couple of seconds. HOWEVER, keep in mind that as soon as you call RollbackFields, events will fire as fields have been changed. If the entities are still bound to grids etc., this can severily hurt performance.

Frans Bouma | Lead developer LLBLGen Pro
Sam avatar
Sam
User
Posts: 95
Joined: 30-Jun-2004
# Posted on: 02-Mar-2006 16:16:14   

Ahh thanks. It was certainly not minutes but just a slight few second pause. No real big deal. I will try to implement this today.

Thanks for all your help!!!