views:

4441

answers:

7

I use LINQ-to-SQL to load data from a database that has two tables in a one-to-many relationship (one Recipe has many Ingredients).

I load a Recipe and LINQ retrieves Ingredient objects into an EntitySet that is binded into a ListBox.

If I want to delete some Ingredients off a Recipe, I get a "An attempt was made to remove a relationship between a Recipe and a Ingredient. However, one of the relationship's foreign keys (Ingredient.RecipeID) cannot be set to null.

I SOLVED this problem using the well known solution by adding 'DeleteOnNull="true"' to the DBML file. But adding this setting only removes the problem when we are deleting Ingredient objects that were retrieved from the DB.

The problem is with the Ingredient objects that were created in code (added to a Recipe) and added to the EntitySet collection of Ingredients and then deleted BEFORE SubmitUpdates is called. Then, the same exception happens again. This usually happens on a new, unsaved recipe when user is adding ingredients to it, makes a mistake and erases an ingredient off a recipe. I added the DeleteOnNull to both 'Association Name="Recipe_Ingredient"' lines in DBML.

How am I supposed to remove such objects? The only solution I see at the moment is that I would load the ingredients into a collection not under the DataContext and then when saving, delete all ingredients off a recipe and add then again from that cache..

A: 

you need to decouple the save code from the events in your GUI, it seems like you're a little to eager to save things to the db before the dust has settled and you're queuing and removing things from the db that never got there in the first place, it would be best if you could identify a point when the user will "commit" their changes, and at that moment, process the full condition of the GUI - this will save you a bunch of spaghetti code.

I would also be curious to know if your entities have autonumber IDs or if you're using some other ID mechanism. You're probably sending DELETEs to the database for the as-yet-uncommitted Ingredient records, if those include NULL IDs, I think the linq could get nasty.

Have you hooked up a textwriter to your DataContext.Log to see what sorts of SQL is generated just before you get your exeception?

Andrew Theken
to clarify a bit: I (1) pull data from DB to a memory collection. (2) bind that collection to GUI. (3) add, alter and delete data in memory collection. (4) commit via LINQ back to DB when user presses Save. The problem is with collection members that were created, added, deleted (and never comitted)
Jurij
+2  A: 
        try
        {
            // Needed for existing records, but will fail for new records
            yourLINQDataContext.Ingredients.DeleteOnSubmit(ingredient);
        }
        catch (Exception)
        {
            // Swallow
        }

        yourRecipeObject.Ingredients.Remove(ingredient);
Jurij
The above code handles new uncommitted ones that were just created and deleted in memory (it isn't sample code - I literally copied and pasted the code from a live production app and altered the names of types and variables).Did you call InsertOnSubmit when you originally created the ingredient?
You did it! I did add the new Ingredient object into the EntitySet collection of a Recipe object, but did not call InsertOnSubmit as it seemed to do nothing. But your proposal solves the problem. Thank you very much!
Jurij
+1  A: 

It seems that you're looking for something that I was looking for myself just a few days back when I asked "How do I design backing data types for a databound WPF dialog with Ok/Cancel buttons?".

The answer is an intriguing post from Paul Stovell describing a sample IEditable adapter for Linq to Sql. This will let you create your desired "Apply/Cancel" semantics in a generalized manner without completely dissociating yourself from the underlying ORm-generated classes through a full custom-written layer.

It's a pretty slick trick, overall, that will essentially let you sidestep the problems you're fighting right now. :)

On a different note, I'm curious as to why your recipe to ingredient relationship is 1:n instead of m:n. Is it for simplicity's sake? I use garlic in a lot of recipes. :)

Greg D
A: 

Thank you for your answer, I will examine the posts and see what I can do. I must say I'm surprised to even see this problem occuring, it seems quite natural to me that one could add records to the LINQ-provided "cache" of data, then decide to erase some of them and then commit. Change tracking should be able to handle that. I just starting with LINQ so I might be doing a stupid mistake somewhere in the code (wouldn't be the first).

On the other note: You are quite correct that garlic can belong to many recipes (not my coctail recipes thought!). I actually model that with an Article object/table. But for a recipe, you need quantities. So in my model, you have a Recipe that has 1:n Ingredients, each of them having a Quantity, a 1:1 link to an Article (which has a Name, an AlcoholContent and some data to establish an interchangeability hierarchy) and a 1:1 link to an Unit (for the quantity to make sense). So in a sense, Ingredient table makes a M:N relationship between Recipe and Article, and at the same time adding some additional information to each individual linked pair.

Jurij
A: 

I had exactly the same problem. I had a parent / child hierarchy, and when adding and removing the child entity without saving to the database I received the "An attempt was made to remove a relationship" exception.

I discovered that this problem only arose when I set an object style property of the child to another linq-sql entity before saving. eg

1. This creates the error

 RetailAccountCustomerCard racc = new RetailAccountCustomerCard();

 Card addedCard = _idc.Cards.Where(c => c.CardId == card.CardId).ToList().First();

 racc.Card = addedCard;

 this.CurrentCustomer.RetailAccountCardsBindingList.Add(racc); 

 // Some code triggered by the user before saving to the db

 CurrentCustomer.RetailAccountCardsBindingList.Remove(racc);

2. This doesn't create the error

 RetailAccountCustomerCard racc = new RetailAccountCustomerCard();

 racc.CardId = card.CardId;  // note that I have set the Id property not the object

 this.CurrentCustomer.RetailAccountCardsBindingList.Add(racc); 


 // Some code triggered by the user before saving to the db

 CurrentCustomer.RetailAccountCardsBindingList.Remove(racc);

Strangely enough, the error that arises in 1. specifies the problem is to do with the relationship is on the RetailAccountCustomerId property of RetailAccountCustomerCard. IT HAS NOTHING to do with the Card object I added. It seems that simply setting any object property of the new entity triggers the problem.

NB. Example 1 works fine in terms of saving, it only causes a problem if the the new entity is deleted before saving.

Calling DataContext.GetChanges() seems to fix the issues in case 1. You also need not setup relations using the first method.
leppie
A: 

I am running into a similar issue, as a workaround, I need to call DataContext.GetChanges(), then everything seems to have caught on again :)

Another problem you could have it that you are binding to columns and not entity properties, and hence the referential collections are not updated (already stated by someone else, but enforcing the fact).

leppie
+1  A: 
// Create new entities
Cart c = new Cart();
CartEntry ce = new CartEntry();
ce.Cart = c;

// Delete the entry
c.CartEntries.Remove(ce);
dc.Cartentries.Attach(ce);
dc.CartEntries.DeleteOnSubmit(ce);

// Insert the cart into database
dc.Carts.InsertOnSubmit(c);
dc.SubmitChanges();

Explaination of the issue: Both entities, c and ce, are not related to a data context - they are not being tracked. EntitySet.Remove() (first delete line) only removes the relation between c and ce. While c can exist without associated cart entries, ce can't exist without an assiciated cart because of a foreign key constraint. When submitting changes to the database, the disconnected ce is dealt with as well, causing a constraint violation and the exception.

In order to get rid of that untracked and disconnected cart entry you need to attach it to your data context (causing it to be tracked) and then mark it for delete on submit. The moment you submit your changes the cart entry will be deleted properly and not cause the exception.

For more details on that issue check this out: http://msdn.microsoft.com/en-us/library/bb546187%28v=VS.100%29.aspx

TexTheAfk