I have a simple parent-child situation where the parent can have multiple children. The user can update the list of children at will by selecting or deselecting them from a listbox. I try updating the child list using code like below but I get a SqlException:
Violation of PRIMARY KEY constraint
'PK_Child_1'. Cannot insert duplicate
key in object 'dbo.Child'.
It appears that LINQ is inserting the new children before deleting the existing ones. I'm sure there is a simple pattern for handling this but I'm stumped.
context.DeleteAllOnSubmit(parent.Children);
foreach (string childname in listBox1.SelectedItems) {
parent.Children.Add(new Child(parentkey, childname));
}
context.SubmitChanges();
Each parent has a unique key (a GUID) and all parent and child columns are non-nullable. The child table is a simple two-column table with the parent key and a varchar value, with a compound primary key consisting of both columns.
Thanks
The problem is that you're adding the new items before the old ones have been deleted.
Linq-to-SQL has a clearly defined order of operations on a SubmitChanges() call (you should definitely also use SubmitChanges() - not Save() !):
* Inserts
* Updates
* Deletes
(for reference, see here and here)
The trouble here is - you seem to add back child nodes that have the same keys as the ones you delete away in the DeleteAllOnSubmit() call.
But because the INSERT of the new items comes before the deletion, you end up with a conflict.
What you need to do is one of two things:
either you delete the child nodes and then call SubmitChanges() before adding the new items again
OR:
you change your logic so that child nodes that still exist aren't first deleted and then re-added; change your logic to only delete those items that are really deleted (and not added back), and insert only those that are truly new
With either of these two steps, you should get your system working.
Marc
Related
I know this is an odd question because I've always been taught to use a foreign key constraint. However, I've come across a case where a foreign key reference value must be kept for historic purpose when the reference is deleted.
It is a task management system whereby a task occurrence references a parent task containing the recurrence rule. This parent task can be deleted, but the occurrence itself must remain in tact with the non-existing parent id. If the parent task cannot be found, the system simply returns an error - eg "parent task no longer exist." The reason why the parent id cannot be set to null on cascade is because it is being used elsewhere in the occurrence as an identifying key.
Another example: What about a YouTube video that was removed from a playlist. Similar situation right? It is being referenced in the playlist, but the video doesn't exist, so it returns an error in the playlist instead.
Do I simply not define a foreign key at all and just simply create the parent_id reference column as a normal column? I just want to be sure how this is normally handled when one encounters a case where one table references another, but the former is not constraint by the existence of the latter.
Having a constraint is just a technical helper to enforce the semantics defined for the database, i.e. "this column contains a number that is not only an INTEGER(32) but also an identifier for a record in some other table". As such they're not strictly necessary, but it:
makes the intention of the field clear (self documentation)
keeps your data "clean" by preventing incorrect data from being inserted
gives the database engine a hint concerning the content of the table which may allow the db to perform more efficiently.
That said, the "proper" way to accomplish what you've described would be not to physically delete the parent record in the first place. Instead, mark the parent as deleted. Since you're keeping the record for historical purposes, surely you'll want to be able to know what the parent used to be, even if it's no longer active or valid.
Second option would be to create a dummy "parent record deleted" reference. Whenever you delete a parent, you update remaining references to point to the dummy record instead. At least you wouldn't rely on errors to implement expected and valid behaviour.
Finally, I see no reason you shouldn't be able to set the foreign key to NULL. It sounds like you're using the foreign key as part of the primary key of the record in question ("is being used .. as an identifying key"). This you almost certainly should not be doing, if that's the root cause of the problem, start by changing that.
Do I simply not define a foreign key at all and just simply create the
parent_id reference column as a normal column?
Yes. At least this is the way I got to know and how we handle this stuff at work.
You might then want to set an index on the reference column.
Within my database there are two tables, let's say object and object_score. An object has an object_score_id and that column is foreign keyed to object_score.id. We have the relation set up this way because, intuitively, an object owns its score.
The problem we're running into is that we'd like to write a trigger to automatically create a corresponding object_score when an object is inserted. However, we don't have any identifying information about an object_score to uniquely identify it, so if we create the row in a BEFORE INSERT ON trigger we have no way to add its id to the foreign key column in object.
Is there any way to work around this other than to flip the relationship so an object_score owns an object?
Usually in a parent-child relationship, the foreign key would exist in the child table. In this case, object being the parent, and object_score being the child. So all object_scores are tagged (owned) with a object_id from object. Then, you can enforce that all object_scores have an object, and there are no orphans.
Having a foreign key in the parent table usually represents a look-up table, where you have an attribute that may be shared across rows. For example, object_color, and the object has a color_id.
I have 2 tables with a master-detail relationship. We use a DataGridView to handle the detail records. The detail records have fields with lookup values (foreign keys). Instead of setting the ID value of the lookup tables we set the object value. This all works well, instead of one situation. If we add a detail row and set at least one of the lookup fields and remove this row before saving (submitchanges), we receive this error:
An attempt was made to remove a relationship between a TableX and a TableY. However, one of the relationship's foreign keys (TableY.TableXID) cannot be set to null.
This problem was discussed in this question (scroll down to answer of Neil Barnwell), but it doesn't have an answer for this particular problem. The solution "Call Datacontext.GetChanges" doesn't help as Datacontext doesnt expose a method GetChanges and GetChangedset fails with same error.
I assume there is some kind of mistake when removing the row. When you add the detail row to the master row, it is automatically attached to the context. So if you add with:
myMasterRow.DetailsRows.Add(myDetails);
You have to use DeleteOnSubmit to reverse the adding:
context.DetailsRows.DeleteOnSubmit(myDetails);
The data context is smart enough to treat the deletion as "undo add", so the entry will never hit the database.
It seems that if you set an object of the entity and remove the new record before any submit Linq still thinks you want to add the new record. In this case you have to set all objects that have FK references to null.
See this post which describes the problem and solution in detail: http://social.msdn.microsoft.com/Forums/en/linqtosql/thread/4170c7bb-f727-4e6a-9190-fc268ae4ce4b
This is a tough question to word...so bear with me.
I have two tables in my database, [Item] and [AssignedProperty]. [AssignedProperty] is the child in a parent-child relationship with [Item]. So it basically just has an ItemID field and a foreign key relationship utilizing it. [AssignedProperty] also has an identical relationship with another table named [Property], and its own primary key, AssignedPropertyID. This creates a nice many-to-many relationship. I have a constraint however, that dictates that one [AssignedProperty] cannot have duplicate occurrences of ItemID and PropertyID in the same record. Therefore an [Item] can only have one of each [Property].
This works nicely, but with LINQ-To-SQL, theres an issue in what I think is the order of execution that conflicts with this constraint.
To produce this error, I load up an [Item] in my application using LINQ-To-SQL. I then select an [AssignedProperty] object, from the item's [AssignedProperties] collection and delete it. I then create a new [AssignedProperty] object with the exact same ItemID and PropertyID as the one I just deleted, and add it back to the collection. When I call [SubmitChanges] on the DataContext, it will flag me on the constraint. If the old [AssignedProperty] record was deleted first, then there is no reason why the new one should produce that error. It looks like it's attempting to insert the new [AssignedProperty] before deleting the old one.
How should I fix this, and in the database or my app? TIA
The 94% is bugging me, and the answer is actually in the comments of the original post.
So, this is just so I can actually select an answer.
I have a many-to-many relation in 3 tables: ProgramUserGroup and Feature are the two main tables, and the link between them is LinkFeatureWithProgramUserGroup, where I have Foreign key relations to the two parent tables.
I have a dataset with inserts: I want to add a new row to ProgramUserGroup, and a related (existing) Feature to the LinkFeatureWithProgramUserGroup table.
When Inserting new rows, I'm setting the default id to -1:
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> <DataSetUserGroup xmlns="http://tempuri.org/DataSetUserGroup.xsd">
<ProgramUserGroup diffgr:id="ProgramUserGroup1" msdata:rowOrder="0" diffgr:hasChanges="inserted">
<id>-1</id>
<Name>99999999999</Name>
<Active>false</Active>
</ProgramUserGroup>
<LinkFeatureWithProgramUserGroup diffgr:id="LinkFeatureWithProgramUserGroup1" msdata:rowOrder="0" diffgr:hasChanges="inserted">
<id>-1</id>
<Feature_id>7</Feature_id>
<ProgramUserGroup_id>-1</ProgramUserGroup_id>
</LinkFeatureWithProgramUserGroup> </DataSetUserGroup> </diffgr:diffgram>
while I'm updating the tables, I get an error:
"The INSERT statement conflicted with the FOREIGN KEY constraint "FK-LinkFeatu-Progr-7DCDAAA2". The conflict occurred in database "x", table "dbo.ProgramUserGroup", column 'id'."
The code for the update is the following:
DataSetUserGroupTableAdapters.LinkFeatureWithProgramUserGroupTableAdapter lfa = new LinkFeatureWithProgramUserGroupTableAdapter();
DataSetUserGroupTableAdapters.ProgramUserGroupTableAdapter pug = new ProgramUserGroupTableAdapter();
pug.Update(dsUserGroup.ProgramUserGroup);
lfa.Update(dsUserGroup.LinkFeatureWithProgramUserGroup);
if I check the ProgramUserGroup table's new row's ID, it has been updated from -1 to ##identity (like 1099), so it's okay - it inserts the new row.
But In the LinkFeatureWithProgramUserGroup table, the related ProgramUserGroup.ID value is still -1, it was not updated anyhow.
How could I force the update of the link table's keys as well?
I've tried
pug.Update(dsUserGroup.ProgramUserGroup);
dsUserGroup.Merge(dsUserGroup.ProgramUserGroup);
lfa.Update(dsUserGroup.LinkFeatureWithProgramUserGroup);
But didn't solve the problem :(
Thanks,
b.
Yes, there's a work-around for this.
You need to tell your parent table's table-adapter to refresh the
data-table after update operation.
This is how you can do that.
Open the properties of ProgramUserGroupTableAdapter -> Default Select Query -> Advnaced options. and Check the option of Refresh the data table. Save the adapter now. Now when you call update on table-adapter, the data-table will be updated [refreshed] after the update operation and will reflect the latest values from database table. if the primary-key or any coloumn is set to auto-increment, the data-table will have those latest value post recent update.
Now you can Call the update as pug.Update(dsUserGroup.ProgramUserGroup);
Read latest values from the ProgramUserGroup coloumns and assign respective values into the child table before update. This will work exactly the way you want.
alt text http://ruchitsurati.net/files/tds1.png