UnitOfWork: Add + Delete in memory; then Save to DB fails

Posts   
 
    
hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 19-Nov-2008 23:48:41   

Hi all,

I have some entities with subentities (I have one AssessmentFile who has 1 or more AssessmentDamages. Each assessmentDamage has 1 or more AssessmentDamageChecks.)

This is the structure: ----- AssessmentFile ---------- AssessmentDamages ---------------AssessmentDamageChecks

Here is the actual issue: I add 2 AssessmentDamages to the AssessmentFile. Each AssessmentDamage also has 2 AssessmentDamageChecks. Then I remove the last AssessmentDamage added (including its AssessmentDamageChecks). (Note that all the steps taken until now is in memory!)

Now when I save my changes to the database, I get the following error:


SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException was unhandled by user code
  Message="An exception was caught during the execution of an action query: Cannot insert the value NULL into column 'AssessmentDamageId', table 'MDMMaster.Assessment.AssessmentDamageChecks'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception."
  Source="SD.LLBLGen.Pro.ORMSupportClasses.NET20"
  RuntimeBuild="09112008"
  RuntimeVersion="2.6.0.0"
  QueryExecuted="\r\n\tQuery: INSERT INTO [MDMMaster].[Assessment].[AssessmentDamageChecks] ([Data], [MeasurePointId])  VALUES (@Data, @MeasurePointId);SELECT @Id=SCOPE_IDENTITY()\r\n\tParameter: @Id : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Output. Value: <undefined value>.\r\n\tParameter: @Data : Xml. Length: 2147483647. Precision: 0. Scale: 0. Direction: Input. Value: .\r\n\tParameter: @MeasurePointId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1.\r\n"
  StackTrace:
       at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork2.Commit(IDataAccessAdapter adapterToUse, Boolean autoCommit)
       at Xiparo.Assessment.BL.Assessment.SaveAssessmentFile(AssessmentFilesEntity aAssessmentFilesEntity)
       at Xiparo.Assessment.BL.AssessmentFacade.SaveAssessmentFile(AssessmentFilesEntity aAssessmentFilesEntity)
       at Xiparo.Modules.Assessment.AssessmentService.SaveAssessmentFile(AssessmentFilesEntity aAssessmentFilesEntity) in C:\Projects\Xiparo.Assessment\Trunk\Sources\UI\Modules\Xiparo.Modules.Assessment\Services\AssessmentService.cs:line 200
       at Xiparo.Modules.Assessment.AssessmentDetailViewPresenter.View_OnCloseAssessment(Object sender, EventArgs`1 e) in C:\Projects\Xiparo.Assessment\Trunk\Sources\UI\Modules\Xiparo.Modules.Assessment\Views\AssessmentDetailView\AssessmentDetailViewPresenter.cs:line 89
       at Xiparo.Modules.Assessment.AssessmentDetailView.tabAssessmentDetail_OnClose(Object sender, EventArgs e) in C:\Projects\Xiparo.Assessment\Trunk\Sources\UI\Modules\Xiparo.Modules.Assessment\Views\AssessmentDetailView\AssessmentDetailView.xaml.cs:line 97
       at Xiparo.Infrastructure.Controls.TabControl.btnCloseAssessment_Click(Object sender, RoutedEventArgs e) in C:\Projects\Xiparo.Assessment\Trunk\Sources\UI\Infrastructure\Xiparo.Infrastructure\Controls\TabControl.xaml.cs:line 101
       at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
       at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
       at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
       at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
       at System.Windows.Controls.Primitives.ButtonBase.OnClick()
       at System.Windows.Controls.Button.OnClick()
       at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
       at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
       at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
       at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
       at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
       at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
       at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
       at System.Windows.UIElement.CrackMouseButtonEventAndReRaiseEvent(DependencyObject sender, MouseButtonEventArgs e)
       at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
       at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
       at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
       at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
       at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
       at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
       at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
       at System.Windows.Input.InputManager.ProcessStagingArea()
       at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
       at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
       at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
       at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
       at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
       at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
       at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
       at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Boolean isSingleParameter)
       at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
       at System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
       at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Boolean isSingleParameter)
       at System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority priority, Delegate method, Object arg)
       at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
       at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
       at System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
       at System.Windows.Forms.NativeWindow.WndProc(Message& m)
       at System.Windows.Forms.Integration.WindowsFormsHost.ActivateWindowListener.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
  InnerException: System.Data.SqlClient.SqlException
       Message="Cannot insert the value NULL into column 'AssessmentDamageId', table 'MDMMaster.Assessment.AssessmentDamageChecks'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated."
       Source=".Net SqlClient Data Provider"
       ErrorCode=-2146232060
       Class=16
       LineNumber=1
       Number=515
       Procedure=""
       Server="10.0.1.157\\SQLEXPRESS"
       State=2
       StackTrace:
            at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
            at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
            at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
            at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
            at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
            at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
            at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
            at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
            at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
            at SD.LLBLGen.Pro.ORMSupportClasses.ActionQuery.Execute()
       InnerException: 

So what is happening: During an INSERT of an AssessmentDamageCheck the FK of its AssessmentDamage isn't set.

Here is the code where I use a UnitOfWork to save the changes to the DB:


var vUnitOfWork = new UnitOfWork2();

//SAVE
// Add the AssessmentDamages for a controlled save action
vUnitOfWork.AddCollectionForSave(aAssessmentFilesEntity.AssessmentDamages);

// Add all the AssessmentDamageChecks of the AssessmentDamages for a controlled save action
foreach (AssessmentDamagesEntity vAssessmentDamage in aAssessmentFilesEntity.AssessmentDamages)
{
    vUnitOfWork.AddCollectionForSave(vAssessmentDamage.AssessmentDamageChecks);
}

//DELETE
// Add all the AssessmentDamageChecks of the AssessmentDamages for a controlled delete action
foreach (AssessmentDamagesEntity vAssessmentDamage in aAssessmentFilesEntity.AssessmentDamages.RemovedEntitiesTracker)
{
    if (vAssessmentDamage.AssessmentDamageChecks.RemovedEntitiesTracker != null)
        vUnitOfWork.AddCollectionForDelete(vAssessmentDamage.AssessmentDamageChecks.RemovedEntitiesTracker);
}
// Add the AssessmentDamages for a controlled delete action
if (aAssessmentFilesEntity.AssessmentDamages.RemovedEntitiesTracker != null)
    vUnitOfWork.AddCollectionForDelete(aAssessmentFilesEntity.AssessmentDamages.RemovedEntitiesTracker);

In the //SAVE part, this is what I notice about the entities: aAssessmentFilesEntity.AssessmentDamages[0].GetHashCode() is equal to vAssessmentDamage.AssessmentDamageChecks[0].AssessmentDamages.GetHashCode() _is equal to _vAssessmentDamage.AssessmentDamageChecks[1].AssessmentDamages.GetHashCode()

The following snippet is output from the debug at runtime when I run through the code above. **Note that the second insert of the AssessmentDamageCheck doesn't set the AssessmentDamageId. **How is this happening???


Method Enter: CreateInsertDQ
Method Enter: CreateSingleTargetInsertDQ
Generated Sql query: 
    Query: INSERT INTO [MDMMaster].[Assessment].[AssessmentDamages] ([AssessmentFileId], [LocationGenericId], [LocationId], [DamageTypeId], [RepairTypeId], [Comment], [ExtraTime], [Quantity])  VALUES (@AssessmentFileId, @LocationGenericId, @LocationId, @DamageTypeId, @RepairTypeId, @Comment, @ExtraTime, @Quantity);SELECT @Id=SCOPE_IDENTITY()
    Parameter: @Id : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Output. Value: <undefined value>.
    Parameter: @AssessmentFileId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1.
    Parameter: @LocationGenericId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 4.
    Parameter: @LocationId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 42.
    Parameter: @DamageTypeId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 4.
    Parameter: @RepairTypeId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 6.
    Parameter: @Comment : String. Length: 255. Precision: 0. Scale: 0. Direction: Input. Value: "".
    Parameter: @ExtraTime : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1.
    Parameter: @Quantity : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1.


Method Exit: CreateSingleTargetInsertDQ
Method Exit: CreateInsertDQ
Method Enter: CreateInsertDQ
Method Enter: CreateSingleTargetInsertDQ
Generated Sql query: 
    Query: INSERT INTO [MDMMaster].[Assessment].[AssessmentDamageChecks] ([AssessmentDamageId], [Data], [MeasurePointId])  VALUES (@AssessmentDamageId, @Data, @MeasurePointId);SELECT @Id=SCOPE_IDENTITY()
    Parameter: @Id : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Output. Value: <undefined value>.
    Parameter: @AssessmentDamageId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 90.
    Parameter: @Data : Xml. Length: 2147483647. Precision: 0. Scale: 0. Direction: Input. Value: .
    Parameter: @MeasurePointId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1.


Method Exit: CreateSingleTargetInsertDQ
Method Exit: CreateInsertDQ
Method Enter: CreateInsertDQ
Method Enter: CreateSingleTargetInsertDQ
Generated Sql query: 
    Query: INSERT INTO [MDMMaster].[Assessment].[AssessmentDamageChecks] ([Data], [MeasurePointId])  VALUES (@Data, @MeasurePointId);SELECT @Id=SCOPE_IDENTITY()
    Parameter: @Id : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Output. Value: <undefined value>.
    Parameter: @Data : Xml. Length: 2147483647. Precision: 0. Scale: 0. Direction: Input. Value: .
    Parameter: @MeasurePointId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1.


Method Exit: CreateSingleTargetInsertDQ
Method Exit: CreateInsertDQ
A first chance exception of type 'SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException' occurred in SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll

I use llblgen v2.6

If I need to provide more info, please just ask. If something about my issue is not clear (the setup / the entities / ....) please let me know. Any info on what's going on would be great! I would expect that everything gets saved fine but this isn't the case.

Kind regards, Wim

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 20-Nov-2008 04:53:27   

Hi Wim,

  • About the second AssessmentDamages Insert that fails: Does it belong to the same collections of the first AssessmentDamages Insert? Could you give us more info about the second entity that fails?

  • Does the deletes have something to do here? You don't paste any delete statement at SQL generated? (didn't paste it or there's nothing to delete?)

  • LLBLGen Runtime Library version?

David Elizondo | LLBLGen Support Team
hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 20-Nov-2008 09:55:27   

Hi,

  • The Runtime Library version = 2.6.8.911

  • The delete statements aren't there because everything happened in memory until the save. So I add a new AssessmentDamage in memory (who also holds 2 new AssessmentDamageChecks). Then I add a 2nd AssessmentDamage in memory (again with 2 new sub-entities). Then I remove the last AssessmentDamage added to memory, including its sub-entities. After all this, I try to save to the DataBase. Now an exception is thrown on the following line: vUnitOfWork.Commit(adapter, true); (the exception is visible in my first post)

I did another simple test, and this all works: Add 2 AssessmentDamages (including sub-entities) in memory. Do a save to the DataBase. (This works!!). Then delete the last AssessmentDamage added. (This also works !!). So it seems like the in memory deletion has something to do with it.

  • Some more info about the 2nd AssessmentDamageCheck: I'm quite sure this assessmentDamageCheck gets added to the same collection. Look at my first post where I checked the HashCodes of these entities. The HashCode of the AssessmentDamage is the same as the 1st AssessmentDamageCheck's AssessmentDamage, and also the same as the 2nd AssessmentDamageCheck's AssessmentDamage. (Note: AssessmentDamageCheck.AssessmentDamages is an AssessmentDamage entity. It is in plural because my table name is plural (this could be confusing))

Also some code: Here I create my AssessmentDamageChecks. I just added a small part of the code to make it not too confusing. I iterate of this piece of code 2 times. During the iteration the entity View.Model.AssessmentDamage doesn't change so both AssessmentChecks that get created reference the same AssessmentDamage.


AssessmentDamageChecksEntity vAssessmentDamageChecksEntity = new AssessmentDamageChecksEntity()
{
    AssessmentDamages = View.Model.AssessmentDamage,
    MeasurePoints = vMeasurePointEntity,
    Data = string.Empty
};

It seems like something weird is going on simple_smile At your service to help or supply extra info ....

Kind regards, Wim

hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 20-Nov-2008 10:58:02   

I just did some tests, and then I looked at the generated linq queries .... and I see some very strange things ......

I'll put them all in a document to view. The tests I did: (When I talk about an assessmentDamage that gets added, it also includes 2 AssessmentDamageCheck entities. All is added and removed in memory until the save to DB)

  • Add 5 AssessmentDamages, remove the last one, save.
  • Add 4 AssessmentDamages, remove the last one, save.
  • Add 3 AssessmentDamages, remove the last one, save.
  • Add 2 AssessmentDamages, remove the last one, save.

You need to look at which AssessmentDamage gets INSERTed and which AssessmentDamageChecks get INSERTed. It is so weird ....

(files will be attached asap!!) (I will also post the in memory delete code asap)

Kind regards, Wim

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 20-Nov-2008 11:43:17   
  • The Runtime Library version = 2.6.8.911

Just to rule out this possibility, would you please try the latest available version.

Thanks.

hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 20-Nov-2008 11:47:14   

The tests I did: (When I talk about an assessmentDamage that gets added, it also includes 2 AssessmentDamageCheck entities. All is added and removed in memory until the save to DB)

  • Add 5 AssessmentDamages, remove the last one, save.
  • Add 4 AssessmentDamages, remove the last one, save.
  • Add 3 AssessmentDamages, remove the last one, save.
  • Add 2 AssessmentDamages, remove the last one, save. (files attached to this thread. 4 pdf files in 1 zipFile called DebugOutput.zip) (Note the INSERTs that I marked red, because they shouldn't occur. )

  • the code of an in memory delete (don't mind the Model.IsNew. It has nothing to do with the IsNew of entities. It is just used for UI-interaction):


        private void btnDeleteAssessmentDamage_Click(object sender, RoutedEventArgs e)
        {
            EntityCollection<AssessmentDamagesEntity> vAssessmentDamages = GetSelectedAssessmentDamages();
            for (int i = vAssessmentDamages.Count - 1; i >= 0; i--)
            {
                AssessmentDamagesEntity vAssessmentDamage = vAssessmentDamages[i];

                for (int a = vAssessmentDamage.AssessmentDamageChecks.Count -1; a >= 0; a--)
                {
                    //remove the assessmentDamageChecks
                    vAssessmentDamage.AssessmentDamageChecks.RemoveTracked(vAssessmentDamage.AssessmentDamageChecks[a]);
                }

                //remove the assessmentDamage
                Model.AssessmentFile.AssessmentDamages.RemoveTracked(vAssessmentDamage);
            }

            Model.IsNew = true;
        }

Maybe I should replicate this behavior in a demo project? I could do this in the weekend .....

Kind regards, Wim

hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 20-Nov-2008 11:51:59   

Walaa wrote:

  • The Runtime Library version = 2.6.8.911

Just to rule out this possibility, would you please try the latest available version.

Thanks.

Ok I will do this first .... Please wait on my observations before you start to examine the files ....

Wim

hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 20-Nov-2008 12:17:59   

hypo wrote:

Walaa wrote:

  • The Runtime Library version = 2.6.8.911

Just to rule out this possibility, would you please try the latest available version.

Thanks.

Ok I will do this first .... Please wait on my observations before you start to examine the files ....

Wim

Hi Walaa, Sorry to say this, but I still have exactly the same behavior while using Runtime Version: 2.6.8.1114 Please have a look at the DebugOutput of my tests. The files are attached to this thread http://www.llblgen.com/tinyforum/GotoMessage.aspx?MessageID=82314&ThreadID=14771

Kind regards, Wim

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 21-Nov-2008 04:13:15   

Hi Wim,

I think the problem is about deleting a parent while the children remains inside in the queue to save of the UnitOfWork. All is in the way you add the entities to the bucket. See this example of "how not proceed":

// Add stuff to UnitOfWork
UnitOfWork2 uow = new UnitOfWork2();

// Category 1 and children
uow.AddForSave(newCategory1);
uow.AddCollectionForSave(newCategory1.Products);

// Category 2 and children
uow.AddForSave(newCategory2);
uow.AddCollectionForSave(newCategory2.Products);

// Add for delte the Category 2
uow.AddForDelete(newCategory2);

// save entities
DataAccessAdapter adapter = new DataAccessAdapter();
uow.Commit(adapter);

Note that the _newCategory2 _is added (only the entity), then the newCategory2.Products are added. Then the _newCategory2 _is removed, that doesn't imply that the products collection are removed form the save queue. That will cause that the some products (children of newCategory2 will be persisted without the information (FK) of their category).

So, this is the same code modified a little bit no avoid the error:

// Add stuff to UnitOfWork
UnitOfWork2 uow = new UnitOfWork2();

// Category 1 (recursively)
uow.AddForSave(newCategory1, null, false, true);

// Category 2 (recursively)
uow.AddForSave(newCategory2, null, false, true);

// Add for delete the Category 2
uow.AddForDelete(newCategory2);

// save entities
DataAccessAdapter adapter = new DataAccessAdapter();
uow.Commit(adapter);

Above code will add newCategory2 recursively. So when you add it for delete, somehow the newCategory2 and it's products are removed from the save queue. No errors expected simple_smile

I hope you can apply this concept to your project, otherwise please attach some repro (demo) zip we can build and recommend a solution.

David Elizondo | LLBLGen Support Team
hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 21-Nov-2008 10:15:58   

Hi Daelmo,

thanks for your reply.

The solution you proposed doesn't work either, in fact our problem comes down to this: We have a entitycollection of parent object where each parent object has a entitycollection of child objects.

Think of this as a entitycollection of orders where each order has an entitycollection of orderdetail lines.

What we have in memory is correct, there are some orders deleted and there are some orders created, there are some orderdetails deleted and there are some orderdetails created.

When we want to save this to the database we don't care what is already in the database, what we have in memory is correct.

In case of new entities they are added to their collection (IsNew = True), when they are deleted they are added to the removedentitiestracker of their collection.

It is of course also possible that their are entities in the removedentitiestracker collection which have never been saved to the database but I don't think this is a problem?

How can we save this entitycollection?

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 21-Nov-2008 10:57:35   

I think you should do the following:

1- Define a Cascade delete in the database on the AssessmentDamage table to delete the related AssessmentDamageChecks when an AssessmentDamage is deleted.

2- Loop to add FOR DELETE each AssessmentDamageChecks.RemovedEntitiesTracker collection of each AssessmentDamage entity. (Because you might need to remove a ADCheck while its parent AD entity is still there).

3- Add FOR DELETE AssessmentDamages.RemovedEntitiesTracker

If you don't want to define the cascade delete part, then your code for should look like the following:

var vUnitOfWork = new UnitOfWork2();

// RECURSIVELY add the AssessmentDamages for a controlled save action
vUnitOfWork.AddCollectionForSave(aAssessmentFilesEntity.AssessmentDamages, true);

//DELETE
foreach (AssessmentDamagesEntity vAssessmentDamage in aAssessmentFilesEntity.AssessmentDamages.RemovedEntitiesTracker)
{
        vUnitOfWork.AddCollectionForDelete(vAssessmentDamage.AssessmentDamageChecks.RemovedEntitiesTracker);

        vUnitOfWork.AddCollectionForDelete(vAssessmentDamage.AssessmentDamageChecks);  //// --->>>>  ADDED CODE
}

// Add the AssessmentDamages for a controlled delete action
if (aAssessmentFilesEntity.AssessmentDamages.RemovedEntitiesTracker != null)
    vUnitOfWork.AddCollectionForDelete(aAssessmentFilesEntity.AssessmentDamages.RemovedEntitiesTracker);

Which should make sure ALL AssessmentDamageChecks are deleted for each deleted AssessmentDamage entity.

hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 21-Nov-2008 15:41:34   

Hi Walaa, Thanks for the reply;

I already tried to delete the collection of the AssessmentDamageChecks (ADC) that is not in the removedEntitiesTracker. This makes no difference.

I also tried to delete each entity seperately (AddForDelete instead of the collection itself) by drilling down to the lowest level with FOR EACH loops. This also makes no difference.

I tried playing around with the recursive and the prefetch. No difference.

Adding a cascade delete to the database is not possible. It seems like a lot of work for something very straightforward.

After each change I did those tests again: Add 5 AD, remove1 / Add 4 AD, remove 1 ...... The results stay the same ... an unlogical order of generated queries.

This is what I want to accomplish: Add and remove entities (and their subEntities) in memory. After a lot of adding and removing, I want to Save (/persist) the resulting entityGraph in the dataBase. The functionality I'm trying to achieve is not complex in my opinion.

Kind regards, Wim

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 22-Nov-2008 04:35:30   

hypo wrote:

Hi Daelmo,

thanks for your reply.

The solution you proposed doesn't work either, in fact our problem comes down to this: We have a entitycollection of parent object where each parent object has a entitycollection of child objects.

Wim, my point was that the error is due to the save of children whose parent have been removed from the save queue.

So, I'm pretty sure the problem is how you are removing entities from collections. If I'm not mistaken, this is your DELETE in-memory code:

private void btnDeleteAssessmentDamage_Click(object sender, RoutedEventArgs e)
        {
            EntityCollection<AssessmentDamagesEntity> vAssessmentDamages = GetSelectedAssessmentDamages();
            for (int i = vAssessmentDamages.Count - 1; i >= 0; i--)
            {
                AssessmentDamagesEntity vAssessmentDamage = vAssessmentDamages[i];

                for (int a = vAssessmentDamage.AssessmentDamageChecks.Count -1; a >= 0; a--)
                {
                    //remove the assessmentDamageChecks
                    vAssessmentDamage.AssessmentDamageChecks.RemoveTracked(vAssessmentDamage.AssessmentDamageChecks[a]);
                }

                //remove the assessmentDamage
                Model.AssessmentFile.AssessmentDamages.RemoveTracked(vAssessmentDamage);
            }

            Model.IsNew = true;
        }

In that code, I see two weird things:

  1. You are removing (I think) entities from the collection you are iterating into. That is a little risky.
  2. What is the _RemoveTracked _method? Is that some custom code in the collection?

A repro solution would accelerate the resolution of this simple_smile

David Elizondo | LLBLGen Support Team
hypo
User
Posts: 34
Joined: 14-Oct-2008
# Posted on: 24-Nov-2008 15:46:39   

daelmo wrote:

  1. You are removing (I think) entities from the collection you are iterating into. That is a little risky.
  2. What is the _RemoveTracked _method? Is that some custom code in the collection?

A repro solution would accelerate the resolution of this simple_smile

Hi daelmo,

I know that I'm removing from the collection that I'm iterating through, that's why I use a for loop and iterate from the last to the first element.

This is our removeTracked (which is an extention method):


public static void RemoveTracked<TEntity>(this EntityCollection<TEntity> aEntityCollection, TEntity aObjectToRemove)
            where TEntity : EntityBase2, IEntity2
        {
            if (aEntityCollection.RemovedEntitiesTracker == null)
            {
                aEntityCollection.RemovedEntitiesTracker = new EntityCollection<TEntity>();
            }

            aEntityCollection.Remove(aObjectToRemove);
        }

The code might seem weird, but it all works perfectly when I'm adding and deleting entities normally. Only when I delete an entity which was added in memory (and not yet persited to the database) I get save errors.

When I look at the CollectionsToSave and CollectionsToDelete of the UnitOfWork, I see that they are all correct.

As soon as I have some time left I will try to replicate this. Kind regards, Wim

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 24-Nov-2008 15:59:52   

A Northwind repro would be great.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 25-Nov-2008 11:04:10   

Also, make sure the problem is in our code.

Frans Bouma | Lead developer LLBLGen Pro