LINQ-to-SQL + One-to-Many + DataBinding deleting

2019-04-22 18:38发布

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..

7条回答
霸刀☆藐视天下
2楼-- · 2019-04-22 19:09

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.

查看更多
forever°为你锁心
3楼-- · 2019-04-22 19:13

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).

查看更多
走好不送
4楼-- · 2019-04-22 19:24
// 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

查看更多
forever°为你锁心
5楼-- · 2019-04-22 19:25

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. :)

查看更多
男人必须洒脱
6楼-- · 2019-04-22 19:27

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?

查看更多
我命由我不由天
7楼-- · 2019-04-22 19:28
        try
        {
            // Needed for existing records, but will fail for new records
            yourLINQDataContext.Ingredients.DeleteOnSubmit(ingredient);
        }
        catch (Exception)
        {
            // Swallow
        }

        yourRecipeObject.Ingredients.Remove(ingredient);
查看更多
登录 后发表回答