Cascade delete in Entity Framework
I had a really hard time getting a simple cascade delete to work with the Entity Framework and I thought I had to write it down here so that I can remember it in the future.
I was trying to delete an item from my database with some children. But I got the error
A relationship is being added or deleted from an AssociationSet ‘FK_ItemChildren_Item’. With cardinality constraints, a corresponding ‘ItemChildren’ must also be added or deleted.
I started to google on it and I found this thread in the MSDN forums. The first suggestion where to add cascade delete to the database and then update the model to get the changes. I did that but I still got the same error. The problem where this.
The SSDL part of the edmx file were updated correctly.
<Association Name="FK_ItemChildren_Item"> <End Role="Item" Type="Model.Store.Item" Multiplicity="1"> <OnDelete Action="Cascade" /> </End> <End Role="ItemChildren" Type="Model.Store.ItemChildren" Multiplicity="*" /> <ReferentialConstraint> .. </ReferentialConstraint> </Association>
But the CSDL part where not updated:
<Association Name="FK_ItemChildren_Item"> <End Type="Model.Item" Role="Item" Multiplicity="1"> <OnDelete Action="Cascade"></OnDelete> </End> <End Type="Model.ItemChildren" Role="ItemChildren" Multiplicity="*"> </End> </Association>
The line in “<OnDelete Action=”Cascade”></OnDelete>” was not updated for some reason. When I added that in the model it worked. I guess the designer is not 100% even though the released EF.
UPDATE!
Alexander Muylaert sent me this script via the comments.
Thanks for your blog. I had this issue on a huge model. Over 180 tables and hundreds of references. I didn’t feel like checking all this manually.
I open the edmx file as an xmldocument and executed the following code. It fixes this issue in batch… The code is not cleaned, but I have already lost a complete day and I can’t afford to waste more time. I would like to post this, so someone could use this code and post a cleaned version.
private static string MakeKey(string aAssociation, string aRole) {
return aAssociation + "_/_" + aRole;
}
public static void FixCascadeDeleteIssues(XmlDocument XDOC, XmlNode SSDLNode, XmlNode CSDLNode, ref bool aChanged) {
HashSet lst = new HashSet();
foreach (XmlNode SSDLChild in SSDLNode) {
if (SSDLChild.Name == "Association") {
foreach (XmlNode EndChild in SSDLChild) {
if (EndChild.Name == "End") {
foreach (XmlNode DeleteChild in EndChild) {
if (DeleteChild.Name == "OnDelete") {
var x = DeleteChild.Attributes.GetNamedItem("Action");
if (x.Value == "Cascade") {
lst.Add(MakeKey(SSDLChild.Attributes.GetNamedItem("Name").Value, EndChild.Attributes.GetNamedItem("Role").Value));
}
}
}
}
}
}
}
Dictionary FMaps = new Dictionary();
foreach (XmlNode CSDLChild in CSDLNode) {
if (CSDLChild.Name == "EntityContainer") {
foreach (XmlNode ContainerChild in CSDLChild) {
if (ContainerChild.Name == "AssociationSet") {
foreach (XmlNode EndChild in ContainerChild) {
if (EndChild.Name == "End") {
string Association = ContainerChild.Attributes.GetNamedItem("Name").Value;
string Role = EndChild.Attributes.GetNamedItem("Role").Value;
string EntitySet = EndChild.Attributes.GetNamedItem("EntitySet").Value;
FMaps.Add(MakeKey(Association, Role), EntitySet);
}
}
}
}
}
}
foreach (XmlNode CSDLChild in CSDLNode) {
if (CSDLChild.Name == "Association") {
string Association = CSDLChild.Attributes.GetNamedItem("Name").Value;
if (Association == "FK_DEV_PARENT_DEV_ID") {
Console.WriteLine("");
}
foreach (XmlNode EndChild in CSDLChild) {
if (EndChild.Name == "End") {
string Multiplicity = EndChild.Attributes.GetNamedItem("Multiplicity").Value;
if (Multiplicity == "*") {
continue;
}
string role = EndChild.Attributes.GetNamedItem("Role").Value;
string v;
if (FMaps.TryGetValue(MakeKey(Association, role), out v)) role = v;
string key = MakeKey(CSDLChild.Attributes.GetNamedItem("Name").Value, role);
if (lst.Contains(key)) {
bool hasdel = false;
foreach (XmlNode DeleteChild in EndChild) {
if (DeleteChild.Name == "OnDelete") {
var x = DeleteChild.Attributes.GetNamedItem("Action");
if (x.Value == "Cascade") {
hasdel = true;
}
}
}
if (!hasdel) {
XmlNode newSub = XDOC.CreateNode(XmlNodeType.Element, "OnDelete", NamespaceDelete);
XmlAttribute xa = XDOC.CreateAttribute("Action", null);
xa.Value = "Cascade";
newSub.Attributes.Append(xa);
EndChild.AppendChild(newSub);
aChanged = true;
}
}
}
}
}
}
}