Refetching EntityCollections

Posts   
 
    
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 28-Oct-2005 18:34:47   

I was wondering if there was a way to re-fetch entity collections with additional prefetches using the adaptor. For example:

  • I have an entity collection of 5 Company Entities.
  • The user has modified the company name field of one of the entities
  • Now I want to go to the database and fetch an Office Entity for each Company Entity within the collection, without changing the modified name field of the one entity.

I would like run the following code, having the result be the same entity collection, with all non-changed data re-loaded from the database and the additional office prefetches.

EntityCollection companies = new EntityCollection(new CompanyEntityFactory());
IRelationalPredicateBucket bucket = new RelationalPredicateBucket();
bucket.PredicateExpression.Add( some filter that happens to return 5 entities);

adaptor.FetchEntityCollection(companies);   // returns 5 companies

companies[3].Name = “new name”;

// New function, we don’t know the bucket that was used to retrieve the initial collection
// Retrieve All Company Offices
IPrefetchPath2 fetchCompany = new PrefetchPath2((int)EntityType.Company);
fetchCompany.Add(CompanyEntity.PrefetchPathOffice);

adaptor.FetchEntityCollection(companies);

result = the same companies EntityCollection with company 3's name unchanged, and all the other data and offices re-loaded from the database

I don’t think this functionality exists, so i've been loading a new collection for the second fetch using a compare range predicate with a list of company ids to make sure I only retrieve the same entities.

bclubb
User
Posts: 934
Joined: 12-Feb-2004
# Posted on: 29-Oct-2005 02:48:32   

You will want to use a context to do what you are describing. You will add a collection to the context then on refetch use the prefetch path and pass the context. This will return the collection that is already in the context.

ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 01-Nov-2005 00:07:18   

bclubb wrote:

You will want to use a context to do what you are describing. You will add a collection to the context then on refetch use the prefetch path and pass the context. This will return the collection that is already in the context.

I tried doing that, and it doesn't add the new prefetches to the existing entitities. Also, you can't pass in the context into the adaptor.FetchEntityCollection function. I just add the collection to a new Context object, then refetch the entity collection.

acradyn
User
Posts: 57
Joined: 03-Apr-2004
# Posted on: 01-Nov-2005 01:15:25   

More on the issue:

It appears that when loading data into an existing and loaded EntityCollection, LLBLGen will not add more data from a new prefetch path to an existing entity if the entity already exists in the EntityCollection.

For example, with a single entity, let's say ContactEntity, I first load the ContactEntity. Then later in another query, I Fill the same ContactEntity with a couple prefetchpaths to get the Office and Address information. This will work fine, but the same does not appear to work if I load an EntityCollection of ContactEntities, then later try to fill in more information like all the ContactEntity's Offices and Addresses.

I think this is similar to http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=2365, but in this case we are using and EntityCollection and the prefetches on the subsequent queries are new and different from the first query.

Z, correct me if I'm wrong.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 01-Nov-2005 11:33:48   

ZaneZ wrote:

bclubb wrote:

You will want to use a context to do what you are describing. You will add a collection to the context then on refetch use the prefetch path and pass the context. This will return the collection that is already in the context.

I tried doing that, and it doesn't add the new prefetches to the existing entitities. Also, you can't pass in the context into the adaptor.FetchEntityCollection function. I just add the collection to a new Context object, then refetch the entity collection.

That's indeed how you should do it: - create a new context object - add the collection to the context - create teh prefetch path - fetch the entity collection again with the prefetch path. Because the entity collection is added to the context, the fetches will be done inside that context.

You tried this and it didn't work? Could you please paste some code to illustrate what you tried which failed?

Frans Bouma | Lead developer LLBLGen Pro
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 01-Nov-2005 18:35:26   

Thanks for your help. It appears that the related entities that I was after didn't have their prefetches set correctly. Adding the collection to a Context before fetching is working now. Thanks!

ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 11-Nov-2005 03:08:33   

I'm still having trouble refetching an entity collection with additional prefetches. It could be something i'm doing outside of my llblgen fetches, so maybe I'm just looking for a better understanding of how using the context during a FetchEntityCollection works.

Basically I have an entity collection that i've fetched from the database. For the refetch, I create a RelationalPredicateBucket, and add a CompareRange predicate containing each PK ID of each entity within the collection to make sure get the same base entities back. I then add additional prefetch paths and do the following as you suggested.

Otis wrote:

That's indeed how you should do it: - create a new context object - add the collection to the context - create teh prefetch path - fetch the entity collection again with the prefetch path. Because the entity collection is added to the context, the fetches will be done inside that context.

You tried this and it didn't work? Could you please paste some code to illustrate what you tried which failed?

For some reason, some of the related entitie collections that were originally there are completely removed during the refetch. Some of the entities in the collection have them, and some don't. This also varies each time I refetch the collection. Does anyone have any ideas on what could make this happen?

I was also wondering if I have to add all of the prefetch paths that were included in the initial fetch? Or can I just add the new prefetch paths that I want?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 11-Nov-2005 11:17:32   

Strange. I've done this to try to reproduce what you're running into. Perhaps it doesn't do what you're doing, if so, please explain simple_smile I use the shortcuts for predicates, though that doesn't make a difference under the hood, if you're using predicatefactory shortcuts for example.


[Test]
public void ContextCollectionRefetchPrefetchPath()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        EntityCollection customers = new EntityCollection(new CustomerEntityFactory());
        // first fetch all customers which have orders
        RelationPredicateBucket filter = new RelationPredicateBucket(
                new FieldCompareSetPredicate(CustomerFields.CustomerId, null,
                    OrderFields.CustomerId, null, 
                    SetOperator.In, null));
        adapter.FetchEntityCollection(customers, filter);
        Assert.AreEqual(89, customers.Count);

        // create range filter for all the customers.
        ArrayList customerIDs = new ArrayList(89);
        Hashtable objectIDs = new Hashtable(89);
        foreach(CustomerEntity customer in customers)
        {
            customerIDs.Add(customer.CustomerId);
            objectIDs.Add(customer.ObjectID, null);
        }

        PrefetchPath2 path = new PrefetchPath2((int)EntityType.CustomerEntity);
        path.Add(CustomerEntity.PrefetchPathOrders);
        path.Add(CustomerEntity.PrefetchPathEmployees);

        filter = new RelationPredicateBucket((CustomerFields.CustomerId == customerIDs));
        // refetch collection
        Context c = new Context();
        c.Add(customers);
        adapter.FetchEntityCollection(customers, filter, path);

        foreach(CustomerEntity customer in customers)
        {
            // check if this instance is the same as we had before
            Assert.IsTrue( objectIDs.ContainsKey(customer.ObjectID));
            // orders and employees have to be filled.
            Assert.IsTrue( (customer.Orders.Count>0));
            Assert.IsTrue( (customer.Employees.Count > 0));
        }
    }
}

Frans Bouma | Lead developer LLBLGen Pro
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 11-Nov-2005 15:58:52   

Thanks for your help. For my problem it was something more like the code I edited below. Again, it could be something else that i'm doing, but i'm definitely seeing some of the related entity collections go into the fetch and not come out.

To try and replicate my problem, it was more like this. You'll see that I added the orders and employees to the initial fetch, then I retrieve more information about those related entities during the second fetch. If the problem occurs, the orders and employees even if they came back in the initial fetch, won't allways come back for all of the orders in the second fetch.


[Test]
public void ContextCollectionRefetchPrefetchPath()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        EntityCollection customers = new EntityCollection(new CustomerEntityFactory());
        // first fetch all customers which have orders
        RelationPredicateBucket filter = new RelationPredicateBucket(
                new FieldCompareSetPredicate(CustomerFields.CustomerId, null,
                    OrderFields.CustomerId, null,
                    SetOperator.In, null));

        PrefetchPath2 path = new PrefetchPath2((int)EntityType.CustomerEntity);
        path.Add(CustomerEntity.PrefetchPathOrders);
        path.Add(CustomerEntity.PrefetchPathEmployees);


        adapter.FetchEntityCollection(customers, filter);
        Assert.AreEqual(89, customers.Count);

        // create range filter for all the customers.
        ArrayList customerIDs = new ArrayList(89);
        Hashtable objectIDs = new Hashtable(89);
        foreach(CustomerEntity customer in customers)
        {
            customerIDs.Add(customer.CustomerId);
            objectIDs.Add(customer.ObjectID, null);
        }

        PrefetchPath2 path2 = new PrefetchPath2((int)EntityType.CustomerEntity);
        PrefetchPath2 pathOrders = path2.Add(CustomerEntity.PrefetchPathOrders).Subpath;
        PrefetchPath2 pathOrderDetails = pathOrders.Add(OrdersEntity.PrefetchPathOrderDetails).Subpath;
        pathOrderDetails.Add(OrderDetailsEntity.PrefetchPathProducts);

        PrefetchPath2 pathEmployees = path2.Add(CustomerEntity.PrefetchPathEmployees).Subpath;
        pathEmployees.Add(EmployeesEntity.PrefetchPathEmployeeTerritories);

        filter = new RelationPredicateBucket((CustomerFields.CustomerId == customerIDs));
        // refetch collection
        Context c = new Context();
        c.Add(customers);
        adapter.FetchEntityCollection(customers, filter, path2);

        foreach(CustomerEntity customer in customers)
        {
            // check if this instance is the same as we had before
            Assert.IsTrue( objectIDs.ContainsKey(customer.ObjectID));
            // orders and employees have to be filled.
            Assert.IsTrue( (customer.Orders.Count>0));
            Assert.IsTrue( (customer.Employees.Count > 0));
        }
    }
}


Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 12-Nov-2005 17:59:15   

The code you provided succeeds here, so I assume I have to add more checks at the bottom, which checks should I add precisely, could you elaborate on that abit please?

Frans Bouma | Lead developer LLBLGen Pro
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 14-Nov-2005 16:13:51   

I figured out the problem I was having. I really appreciate you looking into this. Thanks! Basically my problem was that in my second fetch I was adding a prefetch for a table that was already fetched and needed to use an alias for that prefetch.

The example below shows where I went wrong. Again, I haven't compiled it, but its just used to explain the problem I was having.


[Test]
public void ContextCollectionRefetchPrefetchPath()
{
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
EntityCollection customers = new EntityCollection(new CustomerEntityFactory());
// first fetch all customers which have orders
RelationPredicateBucket filter = new RelationPredicateBucket(
new FieldCompareSetPredicate(CustomerFields.CustomerId, null,
OrderFields.CustomerId, null,
SetOperator.In, null));

        PrefetchPath2 path = new PrefetchPath2((int)EntityType.CustomerEntity);
        path.Add(CustomerEntity.PrefetchPathOrders);
        path.Add(CustomerEntity.PrefetchPathEmployees);


adapter.FetchEntityCollection(customers, filter);
Assert.AreEqual(89, customers.Count);

// create range filter for all the customers.
ArrayList customerIDs = new ArrayList(89);
Hashtable objectIDs = new Hashtable(89);
foreach(CustomerEntity customer in customers)
{
customerIDs.Add(customer.CustomerId);
objectIDs.Add(customer.ObjectID, null);
}

PrefetchPath2 path2 = new PrefetchPath2((int)EntityType.CustomerEntity);
PrefetchPath2 pathOrders = path2.Add(CustomerEntity.PrefetchPathOrders).Subpath;
        PrefetchPath2 pathOrderDetails = pathOrders.Add(OrdersEntity.PrefetchPathOrderDetails).Subpath;
        pathOrderDetails.Add(OrderDetailsEntity.PrefetchPathProducts);
        
        // HERE Was where I was going WRONG, Adding another prefetch containing a table that was already fetched without an alias
        pathOrderDetails.Add(OrderDetailsEntity.PrefetchPathOrders);

PrefetchPath2 pathEmployees = path2.Add(CustomerEntity.PrefetchPathEmployees).Subpath;
        pathEmployees.Add(EmployeesEntity.PrefetchPathEmployeeTerritories);

filter = new RelationPredicateBucket((CustomerFields.CustomerId == customerIDs));
// refetch collection
Context c = new Context();
c.Add(customers);
adapter.FetchEntityCollection(customers, filter, path2);

foreach(CustomerEntity customer in customers)
{
// check if this instance is the same as we had before
Assert.IsTrue( objectIDs.ContainsKey(customer.ObjectID));
// orders and employees have to be filled.
Assert.IsTrue( (customer.Orders.Count>0));


Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 14-Nov-2005 18:16:31   

Glad it's solved! simple_smile

Frans Bouma | Lead developer LLBLGen Pro