- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Janus Hierarchical Grid - Problem Adding or Deleting Child Records
Joined: 07-Jun-2006
I am having problems getting my hierarchical janus grid to add or delete records from a child table.
Here the background info:
I am using a LLBLGen 2.0 C# based DataSource (chopHeaderCollection1). The chopHeaderCollection also has a related chopDetailCollection with child rows as well as some other children collections, but I am only using the chopDetail relationship for this form.
The chopHeaderCollection1 is bound to a bindingSource and the grid is bound to the bindingsource. The grid is configured via a design time click on the Retrieve Hiearchical Structure button. There is one child table. HierarchicalMode = UseChildTables. The grid shows the hiearchical structure fine.
I have the AllowAddNew, AllowDelete, AllowEdit set to true on the main gridEX1 object, as well as the RootTable and ChildTable[0].
I have event handlers tied to the grid's RecordAdded, RecordsDeleted, and RecordUpdated tied to the following event handler:
private void gridEX1_RecordUpdated(object sender, EventArgs e) { _uow.AddCollectionForSave(chopHeaderCollection1, true); CommitChanges(); }
as well as a handler for the chopHeaderCollection's EntityRemoved event:
private void CollectionGeneric_EntityRemoved(object sender, CollectionChangedEventArgs e) { _uow.AddForDelete((EntityBase)e.InvolvedEntity); }
I have used LLBL objects and gridEX on numerous other forms with full update/delete/insert working. I also have numerous hierarchical grids working with LLBL in the project. However, this is my first attempt at allow CRUD operations on the child records of a hierarchical grid.
Here is what is working fine:
All CRUD operations on the root table grid rows works fine. I can add rows, edit rows, and delete rows, and they are persisted to the datasource and on to the database correctly.
The expand buttons per roottable row properly expand and show the child table rows and LLBL's lazy loading works fine.
If I edit a child row, the changes are persisted to the datasource/database correctly.
However if I add a child row it does not work. It lets me type in the child row, but upon leaving the row the row just disappears on the screen.
If I delete a child row the child row deletes but it doesn't occure in the datasource.
Some observations:
If I add a root table row the data sources EntityAdded event is called telling me the datasoure is aware of the change. However, if I add a child row this event handler is never called. Basically adding rows to chopHeaderCollection works, but no if I add to the related "linked" chopDetailCollection.
Same for deleting a row, the datasource EntityRemoved handler is called when root table rows are deleted but not child rows.
If I configure the janus grid to show the foreign key column in the child table and I hit add new row, I notice the row isn't filled in with the parent row primary key. Should it be visable in the grid at the time you add the row or is this done later in the execution process?
I am most confused because filling the hiearchical data works and updates on the child rows work, but child add and delete don't.
It seems that I am missing some step, perhaps this method:
private void gridEX1_RecordUpdated(object sender, EventArgs e) { _uow.AddCollectionForSave(chopHeaderCollection1, true); CommitChanges(); }
needs to somehow reference the "internal" chopDetailCollection that is actually being added to, but that is what I thought the recurse=true in the above method call did.
Can you point me to my flaw or any times on how to debug this.
Thanks,
Brett
If I add a root table row the data sources EntityAdded event is called telling me the datasoure is aware of the change. However, if I add a child row this event handler is never called. Basically adding rows to chopHeaderCollection works, but no if I add to the related "linked" chopDetailCollection.
Same for deleting a row, the datasource EntityRemoved handler is called when root table rows are deleted but not child rows.
I haven't used Janus grid before but the above scenario suggests that it is a Janus Grid bug. Since the appropriate event handlers aren't called. Did you try to contact them for this issue?
Joined: 07-Jun-2006
Yes, I have posted a similar note to the janus GridEX forum.
However, I am not so sure its a bug, I personally think it something I am doing wrong.
First, an event does get called. The following event does get called by the grid for both the root and child tables. If the grid actually added or deleted rows to the internal "chopDetailCollection" inside of chopHeaderCollection1, would my code below persist those changes?
There appear to be a lot of people who use LLBL and Janus together and I have yet to find a post of anyone saying child add/deletes don't work.
private void gridEX1_RecordUpdated(object sender, EventArgs e) { _uow.AddCollectionForSave(chopHeaderCollection1, true); CommitChanges(); }
First, an event does get called. The following event does get called by the grid for both the root and child tables. If the grid actually added or deleted rows to the internal "chopDetailCollection" inside of chopHeaderCollection1, would my code below persist those changes? private void gridEX1_RecordUpdated(object sender, EventArgs e) { _uow.AddCollectionForSave(chopHeaderCollection1, true); CommitChanges(); }
It looks like it would persist your changes, but only the Inserts & Updates. (I hoped you would tell me if it works or not, I don't have the Janus grid to test it).
Still you need to explicitly specify the entities to be deleted by using _uow.AddForDelete() or _uow.AddCollectionForDelete().
Joined: 07-Jun-2006
It only persists the updates, not the inserts. That is my problem the inserts. I know deletes have to be handled differently, but in the child case, if a delete occurs on the child it doesn't call the chopHeaderCollection1.EntityRemoved event so I don't no how to handle that either (if I delete a parent row that is actually an chopHeaderEntity it calls the event, but not a child chopDetailEntity).
There is no reply to my post on Janus yet from yesterday, but I did find this response to another persons post about a different issue. They say they don't use a CurrencyManager for child rows for performance reasons, could this be related to my insert/delete issue (again, updates work find on the child rows).
Here is a response to another note on the Janus forum from a Janus support person. The original post was about v2 of the Janus Grid which does use the CurrencyManager for child rows, I am using the V3 grid and the note says that the CurrencyManager is not used for child rows:
Hi,
Those calls to BeginEdit are not done by GridEX control they are done by the CurrencyManager. When you have hierarchical data, there is a CurrencyManager for each level in the hierarchy and, there is a Current data object for each CurrencyManager. When the control loads the child rows, the currencymanager is created and the Current property is set to the first row in the child list (when a data row is set current, the CurrencyManager will automatically call BeginEdit method even if no edit is done). Unfortunately, we can not do anything to fix this problem in version 2.0.
In Version 3.0, we changed the way of how child lists are retrieved because of some performance problems with ADO .Net and other problems when dealing with child currencymanagers. In version 3.0, the control does not use a CurrencyManager for child lists and you won't have this problem for the child rows.
However, for root table, the control still uses the CurrencyManager because it needs to do it, In this case, you will have to call CancelCurrentEdit method in the CurrencyManager in the Closing event of the form. i.e.
protected override void OnClosing(CancelEventArgs e)
{
CurrencyManager cm = (CurrencyManager)BindingContext[gridEX1.DataSource, gridEX1.DataMember];
cm.CancelCurrentEdit();
base.OnClosing(e);
}
I think the misteriously missing new rows is a thing with the entityviews. If you read the section Using the generated code -> Selfservicing -> Using entityviews with entity collections, you'll see a remark about view behavior on collection changes.
Do you set a filter on the views when you bind the childcollection? Or is this automated through the grid?
Also, if this is something with the grid, janus has a license, so they can test it locally.
Joined: 07-Jun-2006
The binding to the child collection is automated through the grid (I assume) as this is exactly what I have done (across a number of methods, but they run in this order):
In designer:
chopHeaderCollection1 (dragged on to the designer surface) bindingSource1 = chopHeaderCollection1 gridEX1.DataSource = bindingSource1
Then in GridEX's designer I click "Retrieve Hiearchy" and it creates a hierarchical grid with the chopHeaderCollection1 as the parent rows, and then 5 child tables under each row for all the related collections. I then go into the grid and remove all the child tables except the one I want which is chopDetail (which I assume is a chopDetailCollection within LLBL).
the on form load event: chopHeaderCollection1.GetMulti(null);
// Here are the events I have wired up (in InitializeComponent()) -- these are done via the GUI properties and are in the Designer.cs file.
this.chopHeaderCollection1.EntityRemoved += new System.EventHandler<SD.LLBLGen.Pro.ORMSupportClasses.CollectionChangedEventArgs>(this.CollectionGeneric_EntityRemoved);
this.gridEX1.RecordsDeleted += new System.EventHandler(this.gridEX1_RecordUpdated);
this.gridEX1.RecordUpdated += new System.EventHandler(this.gridEX1_RecordUpdated);
this.gridEX1.RecordAdded += new System.EventHandler(this.gridEX1_RecordUpdated);
// Here are the methods they call:
private void CollectionGeneric_EntityRemoved(object sender, CollectionChangedEventArgs e)
{
_uow.AddForDelete((EntityBase)e.InvolvedEntity);
}
private void gridEX1_RecordUpdated(object sender, EventArgs e)
{
_uow.AddCollectionForSave(chopHeaderCollection1, true);
CommitChanges();
}
private void CommitChanges()
{
try
{
_uow.Commit(new Transaction(IsolationLevel.ReadCommitted, "UOW"), true);
}
catch (ORMQueryExecutionException ex)
{
ExceptionTool.GenericExceptionHandler(ex, true, ExceptionHandling.MyStrings.C001_Error, ExceptionHandling.MyStrings.C001_Scope, ExceptionHandling.MyStrings.C001_Tips, ExceptionHandling.MyStrings.C001_Detail);
_uow.Reset();
GatherData();
}
}
For now I have added events to the GridEX Adding and Deleting records and I am handling the child entities directly, but my understanding of LLBL and GridEX would be that this shouldn't be necessary (and it requires a ugly data reload for the add). This has allowed me to move on for now but is not my long term goal for a solution for editing child records (note as this is temporary just to see if it worked, I don't have try/catch around my casts yet!)
private void gridEX1_AddingRecord(object sender, CancelEventArgs e)
{
// This entire section is temporary until I figure out to make Janus/LLBL work right automatically w/ Child Rows.
GridEXRow row = gridEX1.GetRow();
if (row.Table == gridEX1.RootTable.ChildTables["ChopDetail"])
{
GridEXRow parentRow = row.Parent;
ChopDetailEntity de = new ChopDetailEntity();
de.ChopCode = parentRow.Cells["ChopCode"].Value.ToString(); ;
de.Qty = (int)row.Cells["Qty"].Value;
de.DynItemId = (int)row.Cells["DynItemId"].Value;
de.Save();
GatherData();
}
}
private void gridEX1_DeletingRecords(object sender, CancelEventArgs e)
{
// This entire section is temporary until I figure out to make Janus/LLBL work right automatically w/ Child Rows.
GridEXRow row = gridEX1.GetRow();
if (row.Table == gridEX1.RootTable.ChildTables["ChopDetail"])
{
string chopCode = row.Cells["ChopCode"].Value.ToString();
int DynItemId = (int)row.Cells["DynItemId"].Value;
ChopDetailEntity de = new ChopDetailEntity(chopCode,DynItemId);
if (!de.IsNew) _uow.AddForDelete(de);
}
}
It's strange because the child collection then isn't using a filter on the entityview it uses to bind to the control/bindingsource (same mechanism is used by datatables for example: the datasource binds to the defaultview instance provided by the object you bind to the control/bindingsource).
So any new rows should be viewable in the view.
Now, what I'd like to know is that after you add a new row and it dissapears, does the collection contain the new row? Or is it simply never added to the collection?
What can be a problem is that the grid itself doesn't use a currencymanager to keep parent and child in sync, which thus is different from a parent/child grid on a form which does use a currencymanager.
If you can confirm to me the row isn't added, I know what code I have to look at so I can monitor some IBindingList methods in the entityview class to see what the janus grid does. Without this information it's a bit hard to track down what's called in what order, because the entityview - entitycollection combination is just sitting there waiting to get a call from the grid/bindingsource so anything can be called.
The grid has to call IBindingList.AddNew on the entity view and also EndEdit when you move away from the row so the new entity knows it's accepted and the collection can accept the new entity. (it's a complicated system, this IBindingList + IEditableObject stuff, I have no idea who cooked it up inside Microsoft but s/he must like complex solutions to simple problems )
Did you hear anything from the Janus boys yet? Have you also tried mailing support AT janusys.com ? I found that to be responsive as well.
Joined: 07-Jun-2006
Otis,
I don't think the child collection does contain the record after adding. I have the LLBL debug visualizers installed but to be honest, I am not quite sure how to properly test this. I have no problem when I have instances of the collections or TypedViews, but have not been as successful when its a related internal collection/view of the main collection. Any tips?
Janus has only responded once and the guy that responded said he doesn't know much about LLBL and asked the same question about if the child row was added to my class. He also asked for a sample project which I have created.
Provided you have Janus v3.0 installed in the default location the project should work for you too (it uses their demo MDB file). The project is here: http://www.legendslp.com/public/dl/sampleproject.zip
Just open the Testing.GUI and open the .sln file there.
The project boils down my issue to the bare minimum code and exibits the exact same behaviour as my real project.
Thanks, Brett
Joined: 07-Jun-2006
Otis,
Note that you will also see a products collection in the designer. That is only there because I wanted to duplicate my project as much as possible and in my project the child tables have a GridEX DropDown assigned to one of the columns and it pulls data from another datasource. In case this was somehow related, I added it to this project too. So the ProjectID column in the child table has a GridEX DropDown (assigned in the GridEX designer). This drop down uses the productCollection1 as the datasource.
Thanks, Brett
Joined: 07-Jun-2006
Otis,
The guys over at Janus opened the project and posted their findings. I am sharing it here for others and also in case you had any insights and if you agreed with their solution.
From Janus Support:
Hi,
Thanks for the sample project. It helped a lot to understand the problem you are experiencing.
1) The problem with adding child rows is because the child collection does not implement IBindingList interface and the control has no way of creating a new row this way. It works in the root table because the root table is bound to a BindingSource and it is the BindingSource the one that creates the new row but, with child lists, the control binds directly to the list and that list does not implement IBindingList. To fix this problem you have 2 choices:
a) Implement IBindingList interface in your collection (OrderDetailsCollection)
b) Handle the GetNewRow event of the grid and create the new entity for the child table there and pass the new entity to the grid using e.NewRow property. i.e.
void gridEX1_GetNewRow(object sender, GetNewRowEventArgs e)
{
if (gridEX1.CurrentTable.Key == "OrderDetails")
{
e.NewRow = new OrderDetailsEntity();
}
}
2) The problem with delete, is because the EntityRemoved event is only fired when the entity belongs to the OrdersCollection, this event is not fired when a child entity is removed. You could add an EntityRemoved handler for each of the child list to handle this event but this could be very slow. The easiest way of handling this is, in the DeletingRecord event, use the AddForDelete method in the entity being removed. i.e.
void gridEX1_DeletingRecord(object sender, RowActionCancelEventArgs e)
{
EntityBase entity = e.Row.DataRow as EntityBase;
if (entity != null)
{
_uow.AddForDelete(entity);
}
}
I hope this helps.
Brett wrote:
Otis,
The guys over at Janus opened the project and posted their findings. I am sharing it here for others and also in case you had any insights and if you agreed with their solution.
From Janus Support:
Hi,
Thanks for the sample project. It helped a lot to understand the problem you are experiencing.
1) The problem with adding child rows is because the child collection does not implement IBindingList interface and the control has no way of creating a new row this way.
This is nonsense from their part, they should have seen that the collection implements IListSource and thus that the real object bound to the grid is an EntityView object, which implements IBindingList. So this answer from them is not helping.
2) The problem with delete, is because the EntityRemoved event is only fired when the entity belongs to the OrdersCollection, this event is not fired when a child entity is removed. You could add an EntityRemoved handler for each of the child list to handle this event but this could be very slow. The easiest way of handling this is, in the DeletingRecord event, use the AddForDelete method in the entity being removed. i.e.
void gridEX1_DeletingRecord(object sender, RowActionCancelEventArgs e) { EntityBase entity = e.Row.DataRow as EntityBase; if (entity != null) { _uow.AddForDelete(entity); } }
I hope this helps.
Ok, I guess this solves one of the problems you had. I'll look into the add row stuff, as that clearly should work properly and there's DEFINITELY IBindingList and what have you implemented on the bound object, which is an EntityView, returned by the IListSource implementation of the collection (the base class of an entity collection, EntityCollectionBase<T>)
Well, the grid just bugs. Here's the deal: it calls IListSource.GetList() a couple of times (should be once, but I don't know why Janus does this) on the OrdersCollection, the one bound to the control. It also does this on the ProductsCollection, for the combobox data I pressume.
It DOESN'T do this on the OrderDetailsCollection.
I have no idea why it doesn't do this, but it should do this. Because as all entity collections, the OrderDetailsCollection also implements IListSource (because its base class does) and thus would have returned the EntityView object of the collection which does implement IBindingList, ITypedList etc.
So IMHO it's a bug in their code. If they need information from me, let one of their guys contact me directly and I can help them with compiling the runtimes in debug build so they can check exactly if the code calls IListSource properly etc.
Joined: 07-Jun-2006
Otis,
Thank you for looking into it. I have posted to my thread over at Janus's forum and shared you findings. Hopefully they will take you up on your offer to discuss it with them directly.
I was not expecting this outcome, I thought for sure I was going to get a quick two line response says "you just need to do this...". I am glad however it wasn't something simple because I spent over 10 hours trying to figure out why it wasn't working.
Thanks, Brett
Joined: 07-Jun-2006
Otis,
Janus has responded to my post that contained you conclusion and they have agreed it is a bug on their end. Below is their response. Thanks for your help. The Janus Grid + LLBLGen Pro combination makes for a very powerful client and I am looking forward to the fix from Janus.
Janus Forum Post from Janus Support:
Hi,
Thanks for letting us know. We will fix this in the next build and now the control will use the list returned by the IListSource interface. This will fix the problem with the new rows in child lists. However, the problem with the delete of child rows will remain since the EntityRemoved event is not fired in the ordersCollection when a child entity is removed. So, in this case, you will have to continue using the DeletingRecord event to add the entity for remove.