GetMemberEntityCollections() Version 1.x vs Version 2.x

Posts   
 
    
clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 13-May-2009 01:12:58   

LLBLGen Prop Version + buildnr = Version 2.0.0.0 Final (November 6th, 2006) Template = Adapter .NET Version = 2.0

I'm having trouble porting some code that used LLBLGen version 1.x to use version 2.0.

The code I'm trying to port calls IEntity2.GetMemberEntityCollections(). It cycles through the list of collections returned and tries to treat each collection as a xxx.HelperClasses.EntityCollection. The reason we want to do that is because we want to use some custom methods and properties that were added to the EntityCollection.

I've tried lots of different methods, but I keep getting type casting errors at runtime. I've tried using both the generic and non-generic versions of EntityCollections. So I'm curious how others would port the following code that uses LLBLGen version 1.x.

In the code below, the method calls IEntity2.GetMemberEntityCollections() and tries to treat each collection returned as an EntityCollection. In version 1.x of LLBLGen, GetMemberEntityCollections() used to return an ArrayList. In version 2.0, it returns List<IEntityCollection2>.


private void RecurseCollectionsOnEntityForDeleteAndSoftDelete(IEntity2 entity, Hashtable itemsRecursed)
{
    itemsRecursed.Add(entity, true);

    foreach (EntityCollection c in entity.GetMemberEntityCollections())
    {
        DeleteEntityCollection(c);
        SoftDeleteEntityCollection(c);

        foreach (IEntity2 subEntity in c)
        {
            if (!itemsRecursed.ContainsKey(subEntity))
                RecurseCollectionsOnEntityForDeleteAndSoftDelete(subEntity, itemsRecursed);
        }
    }
}

In DeleteEntityCollection(), the parameter is an EntityCollection. It tries to call the custom DeletedEntities property that was added to EntityCollection.


private void DeleteEntityCollection(EntityCollection collection)
{
    ArrayList entitiesToDelete = collection.DeletedEntities;

    try
    {
        for (int i = 0; i < entitiesToDelete.Count; i++)
        {
            IEntity2 entityToDelete = (IEntity2) entitiesToDelete[i];

            DeleteEntity(entityToDelete, entityToDelete.GetConcurrencyPredicate(ConcurrencyPredicateType.Delete));
        }
    }
    catch
    {
        // throw the exception further
        throw;
    }

    entitiesToDelete.Clear();
}

Here is snippets of the custom code in EntityCollection:


public partial class EntityCollection : EntityCollectionBase2
{
    // lots of generated code snipped

    // __LLBLGENPRO_USER_CODE_REGION_START CustomEntityCollectionCode

    private ArrayList _deletedEntities = new ArrayList();

    public ArrayList DeletedEntities
    {
        get { return _deletedEntities; }
    }

    // __LLBLGENPRO_USER_CODE_REGION_END

    // more generated code snipped

}


Here is one way I tried porting the code. First I treated each collection returned as IEntityCollection2, since IEntity2.GetMemberEntityCollections() returns List<IEntityCollection2>.


private void RecurseCollectionsOnEntityForDeleteAndSoftDelete(IEntity2 entity, Hashtable itemsRecursed)
{
    itemsRecursed.Add(entity, true);

    foreach (IEntityCollection2 c in entity.GetMemberEntityCollections())
    {
        // delete or soft delete the entities in the collection
        DeleteEntityCollection(c);
        SoftDeleteEntityCollection(c);

        // Now recursively call this function again for each entity in the collection
        // since those entities may have collections themselves.
        foreach (IEntity2 subEntity in c)
        {
            // if the entity hasn't already been processed, then process it.
            if (!itemsRecursed.ContainsKey(subEntity))
                RecurseCollectionsOnEntityForDeleteAndSoftDelete(subEntity, itemsRecursed);
        }
    }
}

Then I changed DeleteEntityCollection() to take a parameter of type IEntityCollection2. I'm trying to use the generic version of EntityCollection that is generated by LLBLGen version 2.0. I try casting the IEntityCollection2 to EntityCollection<EntityBase2> so I can use the DeletedEntities property that we added.


private void DeleteEntityCollection(IEntityCollection2 collection)
{
    List<EntityBase2> entitiesToDelete = ((EntityCollection<EntityBase2>)collection).DeletedEntities;
    try
    {
        for (int i = 0; i < entitiesToDelete.Count; i++)
        {
            IEntity2 entityToDelete = entitiesToDelete[i];
            DeleteEntity(entityToDelete, entityToDelete.GetConcurrencyPredicate(ConcurrencyPredicateType.Delete));
        }
    }
    catch
    {
        // throw the exception further
        throw;
    }

    entitiesToDelete.Clear();
}

Here is the custom code I added to the generic version of the EntityCollection class. Noticed that I changed all the ArrayList code to List<TEntity>.


public partial class EntityCollection<TEntity> : EntityCollectionBase2<TEntity>
    where TEntity : EntityBase2, IEntity2
{
    private List<TEntity> _deletedEntities = new List<TEntity>();
    private List<TEntity> _softDeletedEntities = new List<TEntity>();

    public List<TEntity> DeletedEntities
    {
        get { return _deletedEntities; }
    }
}

I get a runtime casting error in DeleteEntityCollection() when I try casting IEntityCollection2 to EntityCollection<EntityBase2>. It appears the collection passed in contained entities called ApplicationTypeEntity.

Unable to cast object of type 'GCS.Data.HelperClasses.EntityCollection1[GCS.Data.EntityClasses.ApplicationTypeEntity]' to type 'GCS.Data.HelperClasses.EntityCollection1[SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2]'.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 13-May-2009 11:00:38   

You should keep working with IEntityCollection2, instead of the generic class, as that will otherwise create casting problems.

The main thing is though: do you want to obtain a list of entities from the graph ? If so, please look at the ObjectGraphUtils which can traverse the complete graph for you and group entities per type for example. I also would advise you to upgrade to v2.6 instead, as you're porting code anyway, so IMHO it's best to port to v2.6 instead of porting to v2.0, also because v2.6 contains more utils for this.

It also would be great if you could describe briefly why you want to traverse the elements so we might be able to point you to classes/code which offers traversal logic for you so you can directly obtain a list of all entities of a given type for example to process them (I have the feeling you're trying to do the same thing here, hence my remark simple_smile )

Frans Bouma | Lead developer LLBLGen Pro
clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 13-May-2009 16:51:46   

Thanks for the help Otis.

You should keep working with IEntityCollection2, instead of the generic class, as that will otherwise create casting problems.

The whole point of using the generic class is so I can use the custom code that was added to the EntityCollection class to track entities deleted from the collection.

It also would be great if you could describe briefly why you want to traverse the elements so we might be able to point you to classes/code which offers traversal logic for you so you can directly obtain a list of all entities of a given type for example to process them (I have the feeling you're trying to do the same thing here, hence my remark Regular Smiley)

Actually our company didn't write the code that I'm porting. It is a project that was outsourced to a third party. But I think I know the purpose of the custom code that was added to EntityCollection. Note that I didn't give you all the custom code, I only gave you enough to demonstrate the casting issues, that's why you probably can't see the purpose of the code.

It appears that the purpose of the code is to track which items have been deleted from the EntityCollection, so when it is time to save changes to the database, it will iterate through the lists of entities deleted from the collection and delete the entities from the database.

Actually the custom code keeps two lists of deleted entities. One list is for entities we really want to delete from the database. The other list is for entities that we want to "soft delete". By "soft delete", I mean that we don't really delete the record from the database, we just set a database field on the record called "Deleted" to true. This is typically usually used in List tables that store a list of valid values to be used by other records. By marking a record as "deleted" without actually deleting it, old data can still refer to that obsolete list record but it will no longer be available for use by new data.

This issue of tracking entities deleted from a collection so they can later be deleted from the database is discussed in this thread: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=430&HighLight=1

It appears you may already have a feature in post 2.0 versions of LLBLGen to track entities deleted from a collection. But I'm assuming you don't have a way to track entities to "soft delete".

clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 13-May-2009 19:33:16   

It appears you may already have a feature in post 2.0 versions of LLBLGen to track entities deleted from a collection. But I'm assuming you don't have a way to track entities to "soft delete".

I saw that upgrading from Version 2.0 to 2.6 was free. I see the new RemovedEntitiesTracker. I will try incorporating that.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 14-May-2009 09:49:55   

clint wrote:

It appears you may already have a feature in post 2.0 versions of LLBLGen to track entities deleted from a collection. But I'm assuming you don't have a way to track entities to "soft delete".

I saw that upgrading from Version 2.0 to 2.6 was free. I see the new RemovedEntitiesTracker. I will try incorporating that.

Yes upgrading is free within a major version simple_smile

that tracker indeed will track whatever you are removing from the collections, exactly what you want.

Frans Bouma | Lead developer LLBLGen Pro
clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 14-May-2009 15:53:16   

Otis wrote:

clint wrote:

It appears you may already have a feature in post 2.0 versions of LLBLGen to track entities deleted from a collection. But I'm assuming you don't have a way to track entities to "soft delete".

I saw that upgrading from Version 2.0 to 2.6 was free. I see the new RemovedEntitiesTracker. I will try incorporating that.

Yes upgrading is free within a major version simple_smile

that tracker indeed will track whatever you are removing from the collections, exactly what you want.

I studied the code I'm trying to port some more.

The EntityCollection custom code has public methods called Delete(), DeleteAt(), DeleteAll(), SoftDelete(), and SoftDeleteAt(), SoftDeleteAll().

So instead of calling the EntityCollection.Remove() or EntityCollection.RemoveAt(), the caller calls those custom methods instead.

When you call one of the custom Delete() methods to remove an entity from the collection, it will first store the entity in a list of deleted entities and then call EntityCollection.Remove().

Similarly, when you call one of the custom SoftDelete() methods to remove an entity from the collection, it will first store the entity in a list of soft deleted entities and then call EntityCollection.Remove().

So the new EntityCollection.RemovedEntitiesTracker isn't really going to help since I need to separate the deleted entities into two lists: entities that the caller wants to really delete from the database and entities that the caller wants to "soft delete".

The custom code in the EntityCollection class does some other stuff with those lists of deleted entities too, but I'm not going to get into that.

This third party code also has a CustomDataAccessAdapter class that derives from DataAccessAdapter. It overrides the SaveEntity() method. As part of that override, it calls the RecurseCollectionsOnEntityForDeleteAndSoftDelete() method shown in my original post which calls an entity's GetMemberEntityCollections(). It cycles through the ArrayList returned and treats each item as an EntityCollection so the code can access the custom DeletedEntities and SoftDeletedEntities properties. You could do this in version 1.x. But now that GetMemberEntityCollections() returns List<IEntityCollection2> instead of ArrayList, I can't treat those list items as EntityCollection, so I can't access those custom DeletedEntities and SoftDeletedEntities properties. Otis' original response seems to indicate I'm out of luck.

So I'm wondering if there is an alternative to calling GetMemberEntityCollections() so I can get a list of EntityCollection instead of IEntityCollection2. If not, I'll have to rewrite a bunch of code so I can retain this functionality of maintaining lists of entities to delete and soft delete.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 14-May-2009 17:41:48   

Why not set a flag (enum typed) in the entity instead what to do with it and figure it out when the RemovedEntitiesTracker collection is processed? It's a bit overkill to store all these entities in different collections if all they do is simply reflect what to do with the deleted entities. You could add such a flag in a partial class of CommonEntityBase, a new class which is the base class of any generated entity class (so you can use that to extend all entity classes in one go)

Anyway, the ObjectGraphUtils class offers a set of utility methods to crawl over a graph and return lists of entities which you then can use again to traverse to obtain tracker collections for example or collections contained in them.

Frans Bouma | Lead developer LLBLGen Pro
clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 14-May-2009 17:59:13   

Otis wrote:

Why not set a flag (enum typed) in the entity instead what to do with it and figure it out when the RemovedEntitiesTracker collection is processed? It's a bit overkill to store all these entities in different collections if all they do is simply reflect what to do with the deleted entities. You could add such a flag in a partial class of CommonEntityBase, a new class which is the base class of any generated entity class (so you can use that to extend all entity classes in one go)

Thanks for the idea.

clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 15-May-2009 00:18:20   

Otis wrote:

Why not set a flag (enum typed) in the entity instead what to do with it and figure it out when the RemovedEntitiesTracker collection is processed? It's a bit overkill to store all these entities in different collections if all they do is simply reflect what to do with the deleted entities. You could add such a flag in a partial class of CommonEntityBase, a new class which is the base class of any generated entity class (so you can use that to extend all entity classes in one go)

I tried your idea. But I ran into problems setting the proposed flag for the entity.

I added a flag to the CommonEntityBase class called DeletionOperation that indicates what type of deletion should be done on the entity: a real database delete, or soft delete.

Before, the custom EntityCollection methods Delete() and SoftDelete() used to store the entity in a list of entities to delete from the database or soft delete. I changed those functions to instead mark the entity with a flag value indicating if it should be deleted or soft deleted.

But in this newer LLBLGen version, I'm using the generic version of EntityCollection that was generated.

    
public partial class EntityCollection<TEntity> : EntityCollectionBase2<TEntity>
    where TEntity : EntityBase2, IEntity2

So the custom methods are now Delete(TEntity entity) and SoftDelete(TEntity entity). Those methods set a flag for the entity describing the type of deletion to do. For example:


public void Delete(TEntity entity)
{
    // Mark entity to be deleted
    entity.DeleteOperation = CommonEntityBase.DeleteOperationType.Delete;

    // other code here

    // remove the entity from the collection
    Remove(entity);

    // other code here
}

But this code won't compile because TEntity doesn't have a DeleteOperation property, but CommonEntityBase does.

I tried casting the entity to CommonEntityBase so I could access the DeleteOperation property but you can't do that either.

I tried changing the entity parameter from TEntity to CommonEntityBase. That allowed me to access the DeleteOperation property, but now the call to EntityCollection.Remove(TEntity entity) won't work.

So in the end, I ended up changing the constraint of TEntity from EntityBase2 to CommonEntityBase.

    
public partial class EntityCollection<TEntity> : EntityCollectionBase2<TEntity>
    where TEntity : CommonEntityBase, IEntity2

This works, but I feel bad about changing the generated code.

I suppose the alternative to changing the code is to give up the idea of using the custom EntityCollection methods Delete() and SoftDelete() for setting the DeletionOperation property on the entity.

Right now we remove items from a collection like this using our custom Delete() method.


Customer.Orders.Delete(anOrder);

Now I'd have to change it to something like this:


anOrder.DeletionOperation = CommonEntityBase.DeletionOperationType.Delete;
Customer.Orders.Delete(anOrder);

I guess that wouldn't be the end of the world.

clint
User
Posts: 150
Joined: 15-Nov-2005
# Posted on: 15-May-2009 00:20:57   

I have a question about the EntityCollection.RemovedEntitiesTracker.

If you delete entities from the collection, that entity is added to the RemovedEntitiesTracker collection .

But what happens if you happen to add that entity back into the collection. Is it removed from the RemovedEntitiesTracker collection too?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 15-May-2009 04:56:48   

clint wrote:

But what happens if you happen to add that entity back into the collection. Is it removed from the RemovedEntitiesTracker collection too?

No. As you maybe are adding it with some differences. If you want such behavior, trap the EntityAdded collection's event and do it there.

David Elizondo | LLBLGen Support Team