entity adding/ removing from m-n relations

Posts   
 
    
HcD avatar
HcD
User
Posts: 214
Joined: 12-May-2005
# Posted on: 22-May-2005 02:47:38   

Hi, i have the following setup: A Supplier entity with an m-n relation to SupplierTypeCode entity (using the jointable SupplierType) I also have a form with a checked listbox, in which i represent the m-n relation. The listbox shows all the possible TypeCode's , but only the ones used in the supllier are checked.

When the users checks an item, the following code is called:

    //stc is the correct SupplierTypeCodeEntity
    SupplierTypeEntity st = new SupplierTypeEntity();
    st.SupplierTypeCode = stc;
    m_currentEntity.SupplierType.Add(st); //m_currentEntity is a SupplierEntity

This works, i can later on check m_currentEntity.SupplierType.ContainsDirtyContents (why can't i just use m_currentEntity.IsDirty ??) and do a m_currentEntity.Save(true)

Although the code i REALLY would like to call is :

 m_currentEntity.SupplierTypeCodeCollectionViaSupplierType.Add(stc);

This doesn't give an error, but also doesn't do anything when doing m_currentEntity.Save(true);

Now, my real question lies in the removing of relations, when a user deselects an item in the checkedlistbox the following code is called:

                //fetch the existing SupplierTypeEntity and remove it from the SupplierEntity
                SupplierTypeEntity st = new SupplierTypeEntity(m_currentEntity.SupId,stc.SuptypeId);
                m_currentEntity.SupplierType.Remove(st);

But then later on, the ContainsDirtyContents is false and a Save(true) does nothing .. how come ?? I could ofcourse call a st.Delete at this point, but this is not my intention, the user must still be able to cancel after his actions, and saving is only done when he presses the save button.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 22-May-2005 11:57:45   

HcD wrote:

Hi, i have the following setup: A Supplier entity with an m-n relation to SupplierTypeCode entity (using the jointable SupplierType) I also have a form with a checked listbox, in which i represent the m-n relation. The listbox shows all the possible TypeCode's , but only the ones used in the supllier are checked.

When the users checks an item, the following code is called:

    //stc is the correct SupplierTypeCodeEntity
    SupplierTypeEntity st = new SupplierTypeEntity();
    st.SupplierTypeCode = stc;
    m_currentEntity.SupplierType.Add(st); //m_currentEntity is a SupplierEntity

This works, i can later on check m_currentEntity.SupplierType.ContainsDirtyContents (why can't i just use m_currentEntity.IsDirty ??) and do a m_currentEntity.Save(true)

Because SupplierType doesn't belong to the data of currentEntity. IsDirty is the flag to show that currentEntity's data is dirty.

Although the code i REALLY would like to call is :

 m_currentEntity.SupplierTypeCodeCollectionViaSupplierType.Add(stc);

This doesn't give an error, but also doesn't do anything when doing m_currentEntity.Save(true);

Correct, m:n relations are readonly. THis is because they need an intermediate entity. Sometimes this can be an entity with additional fields, like 'orderdetails' in the m:n relation between product and order. So this intermediate entity has to be filled with data as well. To keep the code consistent, this entity always has to be used, i.e.: it's not hidden for m:n relations with an intermediate entity without a non-PK field. Because: if you add a field to that intermediate entity, your code breaks.

Now, my real question lies in the removing of relations, when a user deselects an item in the checkedlistbox the following code is called:

                //fetch the existing SupplierTypeEntity and remove it from the SupplierEntity
                SupplierTypeEntity st = new SupplierTypeEntity(m_currentEntity.SupId,stc.SuptypeId);
                m_currentEntity.SupplierType.Remove(st);

But then later on, the ContainsDirtyContents is false and a Save(true) does nothing .. how come ?? I could ofcourse call a st.Delete at this point, but this is not my intention, the user must still be able to cancel after his actions, and saving is only done when he presses the save button.

Remove doesn't 'remove' something from the database. It removes something from the collection. If you want to delete rows from the database, add them to a collection for deletion and commit it all in a unitofwork for example.

Removing st from the collection sets the FK field in st to null. For the rest, nothing happens to currentEntity, as no data in that entity changes. So saving current entity again, isn't doing anything.

Deletes are not considered something like save. They're considered as irreversable actions and therefore have to be executed by yourself. that's also the reason why there aren't cascading deletes implemented (also because they can't work for 100% as not all objects are available in memory) or 'automatic deletes'.

Frans Bouma | Lead developer LLBLGen Pro
HcD avatar
HcD
User
Posts: 214
Joined: 12-May-2005
# Posted on: 22-May-2005 14:35:22   

Hi, thanks for the reply. I was busy untill 5am this morning and experimented indeed with the unit of work stuff. What i did was intercept the "ItemCheck" event on my checkedlistbox, and when an item is "checked", create my intermediate entity, and add it for Save to the unit of work. When an item is "unchecked" (this means it was allready in the m-n relation and must now be removed), add the intermediate entity for Delete to the unit of work. When the user clicks then the "Save" button in my main form, the main entity is Saved, and the unit of work is commited. This has some quirks though, what if a user clicks the checklistbox a few times, then the entity is added multiple times (eg. add,delete,add,delete) to the unit of work, and the commit fails (unique constraint violations). What i did as a solution : (and i just would like to know if it's a "good" solution, or am i missing things here ?) On the Itemcheck event, just mark the main entity as IsDirty = true. Then on the Save action of the main form, iterate over the checkedlistbox, and Delete all unchecked existing relations, and Insert all checked unexisting relations. I guess this is "the" way to do it ?

/offtopic : Otis, do you remember i told you last week about that database with 1800 tables that generated 400mb code and couldn't compile in VS.Net ? Well on the command-line i couldn't get it compiled either, still running out of memory cry Maybe i'll put in a fex extra gigs, because i found an even larger DB (30.000 tables) on which i want to "test-drive" it simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39933
Joined: 17-Aug-2003
# Posted on: 23-May-2005 11:29:58   

HcD wrote:

Hi, thanks for the reply. I was busy untill 5am this morning and experimented indeed with the unit of work stuff. What i did was intercept the "ItemCheck" event on my checkedlistbox, and when an item is "checked", create my intermediate entity, and add it for Save to the unit of work. When an item is "unchecked" (this means it was allready in the m-n relation and must now be removed), add the intermediate entity for Delete to the unit of work. When the user clicks then the "Save" button in my main form, the main entity is Saved, and the unit of work is commited. This has some quirks though, what if a user clicks the checklistbox a few times, then the entity is added multiple times (eg. add,delete,add,delete) to the unit of work, and the commit fails (unique constraint violations). What i did as a solution : (and i just would like to know if it's a "good" solution, or am i missing things here ?) On the Itemcheck event, just mark the main entity as IsDirty = true. Then on the Save action of the main form, iterate over the checkedlistbox, and Delete all unchecked existing relations, and Insert all checked unexisting relations. I guess this is "the" way to do it ?

These things are always a pain, as you have to keep the original state, and the new state and compare which ones were removed and which ones were added. I always remove them first then add the ones checked in 1 transaction. It's the most cleanest code.

/offtopic : Otis, do you remember i told you last week about that database with 1800 tables that generated 400mb code and couldn't compile in VS.Net ? Well on the command-line i couldn't get it compiled either, still running out of memory cry Maybe i'll put in a fex extra gigs, because i found an even larger DB (30.000 tables) on which i want to "test-drive" it simple_smile

30,000 tables??? frowning Did they create per record a new table? wink How on earth do you get 30,000 tables in a schema... I do think it won't work wink It will take ages to load a project of that magnitude simple_smile .

Frans Bouma | Lead developer LLBLGen Pro
HcD avatar
HcD
User
Posts: 214
Joined: 12-May-2005
# Posted on: 23-May-2005 13:36:07   

As a matter of fact, after my post i experimented a bit further (i'm getting used to seeing 5 am on the clock) and that's what i ended up with :


        private void PersistData()
        {
            using (new WaitCursor())
            {
                try
                {
                    m_currentEntity.Save(true);

                    //save m-n relations for SupplierType
                    //first delete all old ones, then insert new ones
                    PredicateExpression deleteFilter = new PredicateExpression();
                    deleteFilter.Add(PredicateFactory.CompareValue(SupplierTypeFieldIndex.SupId,ComparisonOperator.Equal,m_currentEntity.SupId));
                    SupplierTypeCollection stcoll = new SupplierTypeCollection();
                    stcoll.DeleteMulti(deleteFilter);
                    foreach (SupplierTypeCodeEntity stc in listTypes_nt.CheckedItems)
                    {
                        SupplierTypeEntity st = stcoll.AddNew() as SupplierTypeEntity;
                        st.SupId = m_currentEntity.SupId;
                        st.SuptypeId = stc.SuptypeId;
                    }
                    stcoll.SaveMulti();
                }
                catch(/*SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException*/Exception ex)
                {
                    Logger.Show(ex);
                }
            }


Maybe i'll just put the Entity.Save, Coll.DeleteMulti, and Coll.SaveMulti in one transaction, that's a good point.

It's good to know i'm on the right track, thanks smile