RefetchAfterSave is not quite doing what I want

Posts   
 
    
mkamoski avatar
mkamoski
User
Posts: 116
Joined: 06-Dec-2005
# Posted on: 20-Jun-2006 18:59:38   

All--

Please help.

RefetchAfterSave is not quite doing what I want.

SITUATION...

Here is the workflow of a basic "SmartRefresh()" method, which should create an entity if it does not exist or update an entity if it does exist.

  1. Fetch an entity collection of EntityA based on a filter "where NameText=X" knowing that the NameText is an alternate key, that is a unique contrained column, that is a natural key of sorts. Note that there is also a PreFetch path because EntityA has a contianed object EntityB whose properties we will need to read later.

  2. Given the above, the collection should have Count=1 or Count<1, all other values of Count are invalid and throw an error. Great.

  3. Look at CollectionCount and do 3-A or 3-B or 3-C.

3-A. If CollectionCount=1, then get a reference to Item(0) of the collection and cast it to type EntityA and set properties and SaveEntity() with RefetchAfterSave=True.

3-B. If CollectionCount<1, then create a new object of type EntityA and set the properties and SaveEntity with RefetchAfterSave=True.

3-C. If CollectionCount>1, then throw an error because the DB is not correct and somehow the unique contraint, which we rely on, is not there. FailFast because we have to fix that DB schema error or nothing else will work.

  1. Now, try to read a property from EntityB (which is contained in EntityA). Boom. RTE of NullReference.

CONCLUSIONS...

What did happen (apparently)? The RefetchAfterSave=True was not able to reapply the PrefetchPath that was used when we got the entity via the EntityCollection fetch.

What did I want to happen? I wanted the RefetchAfterSave to re-apply the PrefetchPath that was used when we got the entity via the EntityCollection fetch.

WORKAROUND...

So, the workaround is to, after the SaveEntity(), do a FetchEntity(), this time passing in the Entity and the same PreFetchPath that was used when we did the orginal FetchEntityCollection. This seems to work.

THOUGHTS...

Maybe I am doing something wonky somewhere and this is supposed to work as I want it to work but my code is broken somewhere else. I don't know. Maybe someone can verify that?

Maybe there is a better way to do this? I don't know.

Maybe it is expecting too much for the Entity that was pulled out of an EntityCollection to "remember" the PreFetchPath that was used when the FetchEntityCollection was run. I suspect that this is too much state to manage and probably does not make sense.

Maybe SaveEntity needs another overload, that allows one to pass in a PreFetchPath. Maybe. Just a thought. I don't know what's involved; but, it might be a good idea. (Then again, it might be a bad idea.) Hmmm.

Maybe I should just continue and use the workaround, as noted above, because it works and it is hard to argue with that.

QUESTION...

What do you think?

Please advise.

Thank you.

--Mark Kamoski

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 21-Jun-2006 07:59:50   

Refetch == true makes the save routine to refetch all SAVED entities again. So every entity that's not SAVED is not refetched because it's untouched. A refetch means: it selects the fields again and fills the fields object again, nothing further.

So if you have a graph like 1 customer, 2 orders, and 2 order detail entities, and they're all dirty and get saved, they all get refetched when you specify refetch = true.

Please refer to the following threads: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=4698 http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=3635

arschr
User
Posts: 893
Joined: 14-Dec-2003
# Posted on: 21-Jun-2006 14:21:40   

I don't think the original message is questioning what refetch = true does. They are describing a different refetch method, one that I and others asked for long ago, and that I hope is noted in the list of features that people want, and that may be implemented in a future version.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 21-Jun-2006 15:23:55   

What's proposed is nothing new. If I have EntityA which has an EntityB and I change EntityA and save the stuff and specify refetch, it refetches A** in-place**: it fetches the field values into the existing object EntityA.

This also means that the existing reference of EntityB, which was already there, will still be there.

So it's a bit unclear to me what exactly is proposed as IMHO what's proposed is already there.

Frans Bouma | Lead developer LLBLGen Pro
mkamoski avatar
mkamoski
User
Posts: 116
Joined: 06-Dec-2005
# Posted on: 21-Jun-2006 15:57:13   

Otis wrote:

What's proposed is nothing new. If I have EntityA which has an EntityB and I change EntityA and save the stuff and specify refetch, it refetches A** in-place**: it fetches the field values into the existing object EntityA.

This also means that the existing reference of EntityB, which was already there, will still be there.

So it's a bit unclear to me what exactly is proposed as IMHO what's proposed is already there.

What is being proposed is that SaveEntity() has a new overload that includes a parameter for PrefetchPathToUse. This way if RefetchAfterSave=True and PrefetchPathToUse=SomeNonNullPrefetchPath, then the Refetch will use the PrefetchPath specified.

I don't think what is proposed is already there because the contained object after the SaveEntity is Nothing in my case.

That is, in my convoluted post above, basically I am saying happens is the following...

...I get an EntityCollection using a Prefetch path...

...I pull out one of the members of that collection and cast it to EntityTypeA (which happens to contain a member that is EntityTypeB)...

...I check at this point and find that (myEntityTypeA.EntityTypeB Is Nothing) is FALSE, which is good because that's what the Prefetch on the EntityCollection was supposed to do...

...I do a SaveEntity() on myEntityTypeA with RefetchAfterSave=True...

...I check at this point and find that (myEntityTypeA.EntityTypeB Is Nothing) is TRUE, which is not good because I need that inner object...

...I do a FetchEntity() and pass in the same PrefetchPath that was used in the FetchEntityCollection call to start...

...I check at this point and find that (myEntityTypeA.EntityTypeB Is Nothing) is FALSE, which is good because that's what I need...

...and so on.

Anyway, it is just a comment. I have a workaround. It looks like this has been discussed before and I am sorry to say that I missed those threads. I have read them now and it looks like some people want that "original" PrefetchPath preserved in some way and some people don't want that.

However, what I am suggesting is somewhat new and I have not seen it before-- adding an overload to FetchEntity to allow one to specify BOTH RefetchAfterSave=True AND PrefetchPathToUse=SomePrefetchPathToUseIfRefetchAfterSaveIsTrue

Adding this new overload to SaveEntity() would...

...not break the existing API...

...expand and make the API more flexible for those who want/need it...

...make both sides happy because everybody can still do as they please...

And so on.

Just a thought. I defer, as always, to the better judgement of the LLBLGen DevTeam.

Thank you.

--Mark Kamoski

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 21-Jun-2006 16:40:19   

mkamoski wrote:

Otis wrote:

What's proposed is nothing new. If I have EntityA which has an EntityB and I change EntityA and save the stuff and specify refetch, it refetches A** in-place**: it fetches the field values into the existing object EntityA.

This also means that the existing reference of EntityB, which was already there, will still be there.

So it's a bit unclear to me what exactly is proposed as IMHO what's proposed is already there.

What is being proposed is that SaveEntity() has a new overload that includes a parameter for PrefetchPathToUse. This way if RefetchAfterSave=True and PrefetchPathToUse=SomeNonNullPrefetchPath, then the Refetch will use the PrefetchPath specified.

So you want to combine SaveEntity with a new prefetch path fetch? Because: If I have a CustomerEntity, and I fetch its OrderEntities via a prefetch path, alter some and alter the CustomerEntity and save CustomerEntity recursively, and specify that they've to be refetched, no entities are removed from that graph, any entity which is saved gets refetched, into the same entity instance. Any entity which wasn't changed doesn't get saved nor refetched, which is ok, nothing was changed.

So I don't need a prefetch path when I save CustomerEntity, as the graph as I offer it to SaveEntity() will be there when SaveEntity is done. Nothing more, nothing less.

I don't think what is proposed is already there because the contained object after the SaveEntity is Nothing in my case.

you have an OrderEntity myOrder which references a CustomerEntity via: myOrder.Customer and if you save myOrder with refetch, myOrder.Customer is null after the save? I find that hard to believe, because SaveEntity's worker routine, PersistQueue will call FetchEntity(myOrder) if refetch is set to true, and FetchEntity will simply perform a fetch of the entity's fields, nothing more, nor will it overwrite anything else or remove references to related objects.

That is, in my convoluted post above, basically I am saying happens is the following...

...I get an EntityCollection using a Prefetch path...

...I pull out one of the members of that collection and cast it to EntityTypeA (which happens to contain a member that is EntityTypeB)...

...I check at this point and find that (myEntityTypeA.EntityTypeB Is Nothing) is FALSE, which is good because that's what the Prefetch on the EntityCollection was supposed to do...

...I do a SaveEntity() on myEntityTypeA with RefetchAfterSave=True...

...I check at this point and find that (myEntityTypeA.EntityTypeB Is Nothing) is TRUE, which is not good because I need that inner object...

Impossible. It's also completely not related to a prefetch path, because that's just a fetch. After the path has been fetched, you have an object graph which you could have created in memory by hand as well.


[Test]
public void InsertCustomerTestWithRefetch()
{
    DataAccessAdapter adapter = new DataAccessAdapter();

    try
    {
        CustomerEntity newCustomer = EntityCreator.CreateNewCustomer(1);
        newCustomer.TestRunId = _testRunID;
        AddressEntity newAddress = EntityCreator.CreateNewAddress(1);
        newAddress.TestRunId = _testRunID;
        newCustomer.VisitingAddress = newAddress;
        newCustomer.BillingAddress = newAddress;
        bool result = adapter.SaveEntity(newCustomer, true);
        Assert.AreEqual(true, result);
        Assert.AreEqual(EntityState.Fetched, newAddress.Fields.State);
        Assert.AreEqual(EntityState.Fetched, newCustomer.Fields.State);
        Assert.IsTrue((((int)newAddress.Fields[(int)AddressFieldIndex.AddressId].CurrentValue)>0));
        Assert.IsTrue((((int)newCustomer.Fields[(int)CustomerFieldIndex.CustomerId].CurrentValue)>0));
        int addressID = newAddress.AddressId;
        int customerID = newCustomer.CustomerId;
        Assert.IsTrue((newCustomer.VisitingAddressId==addressID));
        Assert.AreEqual(newAddress, newCustomer.BillingAddress);
        Assert.IsTrue((newAddress==newCustomer.BillingAddress));
        Assert.AreEqual(newAddress.ObjectID, newCustomer.VisitingAddress.ObjectID);
        Assert.AreEqual(newAddress.ObjectID, newCustomer.BillingAddress.ObjectID);

        Assert.IsNotNull(newCustomer.VisitingAddress);
        Assert.IsNotNull(newCustomer.BillingAddress);

        PrefetchPath2 path = new PrefetchPath2((int)EntityType.CustomerEntity);
        path.Add(CustomerEntity.PrefetchPathBillingAddress);
        CustomerEntity fetchedCustomer = new CustomerEntity(customerID);
        adapter.FetchEntity(fetchedCustomer, path);
        Assert.IsNotNull(fetchedCustomer.BillingAddress);
        // change customer
        fetchedCustomer.CompanyEmailAddress+=" ";
        Assert.IsTrue(adapter.SaveEntity(fetchedCustomer, true));
        Assert.IsNotNull(fetchedCustomer.BillingAddress);
    }
    finally
    {
        adapter.Dispose();
    }
}

Insert of graph, with refetch, checks if it's a correct graph, if the related entities are there, fetch of graph (no visiting address, just billing address) with path. Alternation of customer, recursive save with refetch, billingaddress still there.

Which is logical, as there's no code available to the DataAccessAdapter to clear the reference inside Customer, and why should it do that? It just fills an EntityFields2 object.

...I do a FetchEntity() and pass in the same PrefetchPath that was used in the FetchEntityCollection call to start...

...I check at this point and find that (myEntityTypeA.EntityTypeB Is Nothing) is FALSE, which is good because that's what I need...

...and so on.

Anyway, it is just a comment. I have a workaround. It looks like this has been discussed before and I am sorry to say that I missed those threads. I have read them now and it looks like some people want that "original" PrefetchPath preserved in some way and some people don't want that.

It's been discussed in different contexts, not what you propose because that's already implemented. If you discover a null after a save, it seems there's something else going on.

What's been discussed is that if you have a graph, and you save a new entity outside a graph, but which is related to that graph, you probably want to fetch it 'into' the graph. However, you can already do that: add it to the graph first, save the graph.

're-applying' a prefetch path is not possible, as the graph at hand could have been altered after the path fetch.

But again, I have no clue what's the proposed topic simple_smile , as what I think what you want is already there: an existing graph is NEVER destroyed during a save action. It's just saved, and handed to you back, as it was, no entities removed.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 21-Jun-2006 17:05:23   

Ok I think what's being meant, but it's highly inefficient.

Say I have 10 entities of type FooEntity which have a reference to a StatusEntity. Then I change one of these FooEntity's StatusID to a different one. This means that the Status reference in that FooEntity becomes null.

If I understand it correctly, you want to refetch the StatusEntity related to the NEW ID into the FooEntity, using a prefetch path for the complete graph?

That's inefficient. Simply because you're going to refetch every entity in the graph.

You also could do: FooEntity myFoo = myFoos[index]; StatusEntity newStatus = new StatusEntity(2); adapter.FetchEntity(newStatus); // instead of setting statusID of myFoo to 2, I can do this: myFoo.Status = newStatus; // old one is now dereferenced.

when I now save myFoos, the statusentity reference is already in the graph.

Frans Bouma | Lead developer LLBLGen Pro
mkamoski avatar
mkamoski
User
Posts: 116
Joined: 06-Dec-2005
# Posted on: 21-Jun-2006 19:58:03   

Otis wrote:

Ok I think what's being meant...

OK.

I think that I understand and it makes sense.

No problem. I was just curious.

Things work fine as-is.

Thank you.

--Mark Kamoski

arschr
User
Posts: 893
Joined: 14-Dec-2003
# Posted on: 22-Jun-2006 01:23:27   

I can't speak for Mark, here are my thoughts.

Sample 1: CustomerEntity with prefetch of CountryEntity. Customer.CountryCode M:1 Country.CountryCode

I change a customer's country code, save it; I would like the refetch to return the CustomerEntity and the countryEntity that is now referenced.

Sample 2: OrderEntity with prefetch of OrderdetailEntities. Order.OrderId 1:N Orderdetail.OrderId

I fetch order and details, there are 4 order details. I change the order.ordertype, but make no changes to order details, meanwhile another user has added 2 more order details to this order. I save order with refetch, I would like the 6 order detail entities to be returned.

What am I missing?

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 22-Jun-2006 07:34:00   

While SelfServicing load on demand will solve your issues.

In Adapter you just have to re-use (re-do) the original fetch routine. i.e. fetch again with the prefetchPaths.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 22-Jun-2006 09:00:33   

arschr wrote:

I can't speak for Mark, here are my thoughts.

Sample 1: CustomerEntity with prefetch of CountryEntity. Customer.CountryCode M:1 Country.CountryCode

I change a customer's country code, save it; I would like the refetch to return the CustomerEntity and the countryEntity that is now referenced.

This I explained above, you can do that now with a different mechanism.

Sample 2: OrderEntity with prefetch of OrderdetailEntities. Order.OrderId 1:N Orderdetail.OrderId

I fetch order and details, there are 4 order details. I change the order.ordertype, but make no changes to order details, meanwhile another user has added 2 more order details to this order. I save order with refetch, I would like the 6 order detail entities to be returned.

What am I missing?

I would always do this in a separate step. The problem is that the effects could be bad for some situations and you can't predict when these happen. Saving an entity has a refetch flag to refetch the saved entity so it's up to date, but it's related entities are a different thing, that's not the concern of the save routine, it's not a graph manager.

So I then would do what I explained to you earlier: use a context, and refetch the whole graph with a prefetch path. simple_smile Then you have full control over when you update the graph in memory and how you're doing that.

Frans Bouma | Lead developer LLBLGen Pro
mkamoski avatar
mkamoski
User
Posts: 116
Joined: 06-Dec-2005
# Posted on: 22-Jun-2006 13:31:32   

Otis wrote:

...Saving an entity has a refetch flag to refetch the saved entity so it's up to date, but it's related entities are a different thing, that's not the concern of the save routine, it's not a graph manager...

FWIW, that says it all.

When I finally saw this point...

"...the save routine... [is]... not a graph manager..."

...the matter made sense to me.

It is a matter of dividing the responsibility, preserving those boundaries, keeping the design legible, and making it clear "who does what".

When things make sense, then the development process is manageable.

IMHO.

Thank you.

--Mark Kamoski