Problem with double delete

Nov 12, 2009 at 5:26 PM

We create an entity, then we delete it, and then we redelete it (ok, bad programming, but nobody is perfect).

When we save

Conflictexception Row not found or changed

I think  the problem is in SetAsDeleteOnSubmit, State = New, then State = CancelNew, then State = Deleted.

if (this.LINQEntityState == EntityState.New)
                this.LINQEntityState = EntityState.CancelNew;

else
                this.LINQEntityState = EntityState.Deleted;

Nov 13, 2009 at 12:07 PM
Try having a delete method where there is a using block around the
Context.

Delete(long IdToDelete)
{
//using block here
}

...then delete by Id and etc.

That works for me and deletes are idempotent, as they should be.

HTH.

-- Mark Kamoski


>
Nov 13, 2009 at 1:10 PM

All --

This is a follow-up on my previous post.

I forgot to mention at least one thing, as follows.

In the solution that I described above, one must also make sure that any Context used for a data-fetch containing that entity would need to be refreshed.

HTH.

Thank you.

-- Mark Kamoski

 

 

Nov 13, 2009 at 1:18 PM

All --

BTW, here is the code sample that I am referencing above.

 

/// <summary>
/// This will delete the entity with the given ID if the ID does exist.
///</summary>
/// <param name="targetPkId">This is the ID to use.</param>
/// <remarks>
/// If the ID does not exist, then this will NOT throw an exception and will NOT make any changes.
///</remarks>
public void DeleteByPkId(object targetPkId)
{
	if (targetPkId == null)
	{
		throw new System.ApplicationException("The given object, targetPkId, is null.");
	}

	//Create a helper.
	ArrayList myPkIdList = new ArrayList();

	//Add the object without casting because the overload will cast if necessary.
	myPkIdList.Add(targetPkId);

	//Call an overload.
	this.DeleteByPkId(myPkIdList);
}

/// <summary>
/// This will interate the given list and will delete the entity with the given ID if the ID exists.
///</summary>
/// <param name="targetPkIdList">This is the list of IDs to use.</param>
/// <remarks>
/// If the ID does not exist, then this will NOT throw an exception and will NOT make any changes.
///</remarks>
public void DeleteByPkId(ArrayList targetPkIdList)
{
	if (targetPkIdList == null)
	{
		throw new System.ApplicationException("The given object, targetPkIdList, is null.");
	}

	using (Team.Abcd.BusinessLayer.BusinessEntities.DataClassesContext myContext =
		new Team.Abcd.BusinessLayer.BusinessEntities.DataClassesContext(
			Team.Abcd.BusinessLayer.BusinessComponents.EntityHelper.GetConnectionString()))
	{
		myContext.ObjectTrackingEnabled = true;
		myContext.DeferredLoadingEnabled = myContext.ObjectTrackingEnabled;
		System.Guid myPkIdTemp = Guid.Empty;
		IEnumerator myEnumerator = targetPkIdList.GetEnumerator();
		myEnumerator.Reset();

		while (myEnumerator.MoveNext())
		{
			myPkIdTemp = new Guid(myEnumerator.Current.ToString());

			var q = from n in myContext.CitizenshipCodes
					where (n.PkId == myPkIdTemp)
					select n;

			if ((q == null) || (q.LongCount() <= 0L) || (q.First() == null))
			{
				//Continue. There is nothing to delete.
			}
			else if (q.LongCount() == 1)
			{
				myContext.CitizenshipCodes.DeleteOnSubmit(q.First());
				myContext.SubmitChanges(System.Data.Linq.ConflictMode.FailOnFirstConflict);
			}
			else
			{
				throw new System.ApplicationException(
					"A retrieve for myPkIdTemp='" + myPkIdTemp.ToString() + 
					"' returned LongCount='" + q.LongCount().ToString() + "'.");
			}
		}
	}
}



HTH.

Thank you.

-- Mark Kamoski

Nov 13, 2009 at 1:23 PM

All --

BTW, I have this sample code working in the end-to-end sample that I have uploaded at the following link...

http://linq2sqleb.codeplex.com/SourceControl/PatchList.aspx

...where, if you are interested in it, you need to find my latest-and-greatest upload of...

Northwind01_T4Sample_YYYYMMDDHHMM.zip

...and that might help.

Thank you.

-- Mark Kamoski

Nov 13, 2009 at 2:16 PM

Hi everybody,

Bonjour cestbienmoi, (Parles-tu français ? j'espère que tu n'es pas belge ... :-) )

The problem occurs when you delete an entity first, then its parent because cascade delete make the SetAsDeleteOnSubmit function below to be executed twice (see the SetAsDeleteOnSubmit(bool ApplyToChildEntities) code, the ToEntityTree() function will return the 'CancelNew' child ...).

I think a solution could be to replace this code

public void SetAsDeleteOnSubmit()

{

if ( ... detached ..) throw ...; //no change

if (..nottracked .. throw ... : //no change

if ( ...New ..)

  this.LinqEntityState = CancelNew ; // to be changed

else

this.LinqEntityState = Deleted ;

}

by this one

public void SetAsDeleteOnSubmit()

{

 

if ( ... detached ..) throw ...; //no change

if (..nottracked .. throw ... : //no change

if ( ...New ..)

 foreach (PropertyInfo propInfo in _cacheAssociationFKProperties[this.GetType()].Values)

{

   propInfo.SetValue(this, null, null);

}

else

 

this.LinqEntityState = Deleted ;

 

<font size="2">

}

</font>

 

This way, this entity will not appear when its parent call the ToEntityTree() function.  It will be in the garbage collector, as it should be.

The entitystate 'CancelNew' will be unused. This state should disapear from the LinqEntityBase.cs code. 

 

PS1 :

We could do the same when a entity is deleted, and 'detache' them from the root entity

public void SetAsDeleteOnSubmit() {

 

if ( ... detached ..) throw ...; //no change

if (..nottracked .. throw ... : //no change

//if ( ...New ..) this line disapear

 foreach (PropertyInfo propInfo in _cacheAssociationFKProperties[this.GetType()].Values)

{

   propInfo.SetValue(this, null, null);

}

 

 

if (this.LINQEntityState != EntitySate.New)

{

 //We must keep this entity in an array in order to be able to give it when calling the SynchroniseWithDataContext function.

}

 

<font size="2">

}

</font>

 

 

This way, the entitystate 'Deleted' will be unused too. This state should disapear from the LinqEntityBase.cs code which will be simplified. The SynchroniseWithDataContext function does not need to process the cascadeDelete option...this is redundant with the SetAsDeleteOnSubmit(bool ApplyToChildEntities) function).

 

Patrick

Coordinator
Nov 16, 2009 at 8:45 PM

Hi Patrick,

The first idea is good, I think it may be possible to remove CancelNew and for everything still to work - though I will need to think about this some more.

The second idea is not so good, we can't clear all the FK's as they may be part of the key information for removing the record when it goes to the database or we might remove (i.e. the FK might be part of the key).  So not quite sure about this one..

Back to cestbienmoi's original issue, on first thought I thought the easiest thing to do would be to add a line in (highlighted in blue):

        public void SetAsDeleteOnSubmit()
        {
            if (this.LINQEntityState == EntityState.Detached)
                throw new ApplicationException("You cannot modify the Entity State from 'Detached' to 'Delete' ");

            if (this.LINQEntityState == EntityState.NotTracked)
                throw new ApplicationException("You cannot change the Entity State when the Entity is not change tracked");

            if (this.LINQEntityState == EntityState.New)

                this.LINQEntityState = EntityState.CancelNew;

            else if(this.LINQEntityState != EntityState.CancelNew)


                this.LINQEntityState = EntityState.Deleted;

        }

Would this not stop the issue?

Cheers

Matt

Nov 17, 2009 at 3:03 AM
Please do not break anything.

I do not see the need for any change, but that is probably because my
architecture is quite disconnected, etc.

Just asking for care and gentleness with the now stable code base.

:-)

>
Nov 17, 2009 at 8:37 AM

Thanks,

The 2 solutions are good, the second is simpler and easier to test (it is a non breaking change, only a bug correction), the first can be usefull to have a more clean tree.

Coordinator
Nov 17, 2009 at 9:37 AM
mkamoski wrote:
Please do not break anything.

I do not see the need for any change, but that is probably because my
architecture is quite disconnected, etc.

Just asking for care and gentleness with the now stable code base.

:-)

>

Lol!

I'll implement the simple change soon, where it doesn't set a CancelNew to a delete (cause it doesn't make sense) and it won't break anything.

:)

Cheers

Matt

 

Coordinator
Nov 20, 2009 at 10:36 PM

Done.

Please see the latest source code.

Bug Fix: When EntityState == CanceNew, don't change to EntityState == Deleted when SetAsDeleteOnSubmit is called.

Cheers

Matt.

Dec 1, 2009 at 8:37 PM
mhunter wrote:

...Please see the latest source code....

 

Matt --

On this page...

http://linq2sqleb.codeplex.com/

...it currently says the following... 

"

Downloads

Recommended release:

Version 1.1 Final

Mon Jul 13 2009 at 3:00 AM

"

...so where exactly is the new version that is "done" with this change?

Do I go to...

http://linq2sqleb.codeplex.com/SourceControl/ListDownloadableCommits.aspx

...can click the latest "Change Set" and then "Download"?

Or is there another way to do it?

Please advise.

Thank you.

-- Mark Kamoski

Coordinator
Dec 2, 2009 at 10:19 AM

Hi Mark,

Sorry, it's just on the downloadable commits page.

I think I'll do another release anyways :)

What was the last copy you downloaded? 1.1?

BTW, just reading your blog post - sounds good.  Just about to download your code and have a peek.

I've been getting into T4 templates of late, something I've always wanted to do... they rock!  Been generating entities left right and center!

Cheers

Matt.

Coordinator
Dec 2, 2009 at 10:55 AM

New release :)

Change spoken of above are included.

http://linq2sqleb.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=36734