StrataFrame Forum

.NewRow() Error - An Item with the same key has already been added.

http://forum.strataframe.net/Topic33036.aspx

By jsantos - 8/25/2015

Hello,

I am having an issue when calling .NewRow I get an error in the CreateShareAndAddBusinessObject function in BusinessBindingSource.vb (see attached image)

Before calling the .NewRow in our code the CurrentRowIndex = -1 which I expect as the BO should be empty. Then as soon as I call .NewRow I get an exception. When the error is thrown index = 0 when it should be 1 according to loReturn where it looks like there is already one record and then the error makes sense.

Any ideas?

http://forum.strataframe.net/Uploads/Images/c855b057-bc96-4ba1-a242-0e50.png
By Edhy Rijo - 8/25/2015

Hi JSantos,

I your screenshot is not quite visible, it is always best to add it as attachment so we can actually see what you want us to see.

Now back to your issue, it looks like you are trying to add a new record to an SF BusinessBindingSource (BBS), if that is the case, you simply call the Add() method in the BO bind to the BBS and the new record will show up in your control.

If I am wrong, please clarify what you are trying to accomplish.
By jsantos - 8/25/2015

Sorry about the image. Added attachment

We are adding a new row programatically. As I understand .NewRow is recommended for programatically adding new rows.
By Edhy Rijo - 8/25/2015

Hi,

Thanks for the image and yes, NewRow() is recommended for adding rows programmatically.

Still, can you please explain in a wider scope what you are trying to do?  also it will help if you can post the code Sub or Function where you are trying to add the new record?

I use BBS with DevExpress all the time and have notice that when showing records in the DevEx grid using the BBS, and using the same source BO to modify the record, all kind of error show up.  My solution is to use 2 BO, one for the BBS to show in read only grid and one for make the changes.  I know this logic will require more code to keep the grid in synch with updates to 2nd BO, but that have worked for me all the time.
By jsantos - 8/25/2015

I have attached a screenshot of the code that I think you will see where it is happening.

Thanks for your help.
By jsantos - 8/25/2015

I have also just tried using .Add and I get the same error in the same location CreateShareAndAddBusinessObject.
By StrataFrame Team - 8/25/2015

Howdy J,

The CreateShareAndAddBusinessObject() method is called from within the Get of the Item() property on BusinessBindingSource.  In the Get, it calls Not _BusinessObjectList.TryGetValue(index, loReturn), which effectively tests whether the key is contained in the dictionary and returns the value.  So, if the Me.CreateShareAndAddBusinessObject(index) is being called, then the specified index does not exist within the dictionary.  

So, since the index isn't in the dictionary when CreateShareAndAddBusinessObject() is called, then something within CreateShareAndAddBusinessObject() is adding a record to the dictionary before the call to add the record to the dictionary.  There are 4 operations, you'll need to check into: 1) your BO's constructor 2) some logic happening when the current data table is shared 3) setting the parent relationship, and 4) setting the parent business object.

Something in there is adding to the dictionary before the code to add to the dictionary.  

Try putting a breakpoint on the line for Me._BusinessObjectList.Add(index, loReturn) and see if something hits that before you're expecting it to.

Also, do you have 2 business objects trying to use the same shared data table?  Through the SharedDataTableKey perhaps?
By StrataFrame Team - 8/25/2015

By the way, calling .Add() calls .NewRow() internally.  Calling .Add() just also goes through the logic of enabling the bound controls through the editing state.  NewRow() doesn't mess with any of the UI stuff, so that's why it's usually recommended for programmatically adding rows.
By jsantos - 8/25/2015

Hi Ben, 

Thanks for the information.

So I put a break point on Me._BusinessObjectList.Add(index, loReturn) and it gets hit once prior to error for the same child business object. So it goes into CreateShareAndAddBusinessObject 2 times on one call to .NewRow. The first time through no error.

I checked the SharedDataTableKey and from what I can tell the parent and child do not share the same key.
By StrataFrame Team - 8/25/2015

Yeah, that double call is what's causing the error.  Will you send me a screenshot of the Call Stack window on each of those 2 times that it hits the line to Me._BusinessObjectList.Add()?

That should give us a clue as to why it's hitting it twice.
By jsantos - 8/25/2015

I thought I would do one better and provide the 2 call stacks in one Excel file. I didn't want to exclude anything that may be helpful.

Thanks
Jeff
By StrataFrame Team - 8/25/2015

OK, it looks like setting a value on the row is causing the DefaultView to raise a ListChanged event, which is being nested into the middle of the check for whether the record exists in the dictionary and adding the record to the dictionary.

We can pause the changed events by using wrapping the call to NewRow() with bo.PauseChangedEvents() and bo.ResumeChangedEvents().

Give that a try and see how that works.
By Edhy Rijo - 8/25/2015

Hi JSantos and Ben,

One thing to keep in mind is that in your msd BO you have a filter condition but when you add call msd.NewRow() that new record will not show up due to the filter condition, so you should temporarily remove the filter, add the new row, add a value to msd_PrimaryRecID that match the filter then apply the filter.

Working with filtering in BO can be very tricky and cause a lot of headache if your code don't take then in consideration, specially when looping.

So I would code your loop like this:


Do
   msd.NewRow()
   msd._PrimaryRecID = Me.recid
   msd.Filter = String.Format("msd_PrimaryRecID = '{0}'", Me.recid)

   .... continue with your code


Again, I don't see your whole code, but the filter may be the one causing you problems.

Also to be honest, I don't quite understand why you are applying a filter to the msd BO since you are adding a new record inside the loop.  I am assuming that the BO you showing here is not msd.
By StrataFrame Team - 8/25/2015

Yep, Edhy, the WPF ICollectionView and IEditableCollectionViewAddNewItem models take care of adding/editing an item when the view is sorted/filtered by holding the item to the side until the add/edit of the item is finalized through CommitAdd/Edit().  There's even a NewItemPlaceholderPosition to allow you to specify whether you want the blank new-item row on the top or the bottom.

Sounds great in theory, but the implementation has been a bugger because you have to have a custom enumerator that return the "fake" or shifted items.
By Edhy Rijo - 8/25/2015

Yes, Ben you are right.

I remember long time ago when I was starting with SF having this sort of weird outcome when using filter/sorting on a BO and adding new rows or deleting current one.  Careful is a most during those situation, but I must also say that I love the BO Filter/Sorting features since they have allowed me to deal with some custom processing in an easy way, again as long as I remember that I using a filter or sort later on BigGrin
By jsantos - 8/26/2015

Thanks both of you your help.

Unfortunately neither suggestions worked. I even tried PausingChangedEvents for the parent as well as the business object that I am trying to add to. 

Edhy, to be honest I have no idea why the filter is there myself that's the way the code was when I picked up this issue. It is looping through the parent BO to get information and creating msd records (child BO).

OK while typing this message and based on your comments Edhy I decided you know what let me search the entire file where msd.Filter is being used maybe to comment them all out, like you I just don't understand the purpose of filtering the child object at the point while adding to it. There is 0 comments in this code except for this I just found. Look at the Try Catch error handler comment. Lovely. 

 If ptmd.MoveFirst Then

   Do
      msd.Filter = "msd_PrimaryRecID = '" & Me.recid & "'"
      Try
         msd.NewRow()
      Catch ex As ArgumentException
         'I32065, "An item with the same key has already been added." error
         'ArgumentException occurs when there is more than one predecessor coming from product template
         'This error does not seem to be causing functional instability, we can just ignore it for now
      End Try

      msd.msd_DependencyType = ptmd.DependencyType
      msd.msd_LagTime = ptmd.LagTime
      msd.msd_DependentTaskNumber = CShort(ptmd.DependentTaskNumber + Me._TaskDiff)

      '-- Dependent RecID will be set in milestone form update

   Loop While ptmd.MoveNext

End If


So what I am going to do right now is try commenting out all the filtering used on the msd BO and see what effect it has. I will be right back.
By jsantos - 8/26/2015

OK so when I commented all filtering of the msd BO I get no error at the .NewRow call.

But when I check any of the properties of the msd BO I get "MicroFour.StrataFrame.Business.BusinessLayerException: The CurrentRow for table '[dbo].[sch_MileStonesDependencies]' could not be evaluated because the CurrentRowIndex is out of range.  Business object record count: 0.  CurrentRowIndex: -1."

After .NewRow shouldn't there be at least one record in the BO?

Still investigating.
By StrataFrame Team - 8/26/2015

Yes, there should be at least 1 record in the BO after .NetRow() if there is no filter on it.

There is a property on the primary business object that will auto-filter child business objects.  You might check the value of the .Filter in the watch window before the call to .NewRow() and see if something else, like the parent BO, is setting it.
By jsantos - 8/26/2015

Ok more info.

I stepped through the code in .NewRow() and actually there is an error occurring in the procedure call OnSetDefaultValues, which raises the SetDefaultValues event.

Thanks Ben I will take a look at the filter at the .NewRow call and take a look at that property.
By jsantos - 8/26/2015

Awesome!!

So following both your comments it looks like this filtering is causing the issue. Just before the NewRow call I checked the filter and it was set so I set it to empty string at the beginning of the procedure and that seems to have done the trick. NO MORE ERRORS!!! (at least for now) , yippeee.

Thanks to you both Edhy and Ben. 
By StrataFrame Team - 8/26/2015

Woot!  Glad you're fixed.  Let us know when you need help on your next gremlin.
By Edhy Rijo - 8/26/2015

Hi JSantos, Ben,

Wow it must have been a long night for you JSantos thinking about this issue Hehe

I knew the combination of using filters in such a way had something to do with this issue after all and I am glad you got that figured out.

Now, it looks like you inherited this project and after this experience, I would be very careful with the miss-used of BO.Filter or child auto filtering in other part of the code.  As you have seen, the final exception being thrown by .Net not necessarily point you in the right track as to what is causing the error.

I learned over the years that StrataFrame design and logic used for the Business Object is rock solid since my applications never fail due to an internal SF error or memory leaks, it is always due to an improper used of a feature in the BO and 99% of the time a BO.Filter or BO.Sort is involve.

Enjoy!!!