Search code examples
c#sql-serverforeign-keys

How to treat deletion triggered exceptions, based on a ForeignKeyConstraint?


Yesterday I asked about how to create a one-liner for ForeignKeyConstraint objects, and the proposed solution is working fine.

Now I have following line of source code for creating the ForeinKeyConstraint (splitted over multiple lines for readability purposes):

dt_Locations.Constraints.Add(
  new ForeignKeyConstraint(dt_Areas.Columns["Id"], 
                           dt_Locations.Columns["AreaId"]) 
  { 
     DeleteRule = Rule.None, 
     UpdateRule = Rule.None
  }
);

When I try to delete a row from the DataTable dt_Areas, while it is being referred to by a DataTable dt_Locations entry, I get an exception:

try
{ 
  dt_Areas.Rows[0].Delete();
}
catch (Exception ex)
{
  MessageBox.Show($"You can't do that: [{dt_Locations.Constraints[0].ToString()}] and [{ex.Message}]");
}

Now, next part in that story: I would like to change the messagebox into something like:

"You want to remove 'Area' tupples which are being referred to by 'Locations' tupples. Do you want to remove those 'Locations' tupples and remove the 'Area' tupples afterwards?"

This message being shown with a MessageBoxButton.YesNo in the MessageBox constructor.

In order to perform do this removal, I need:

dt_Areas.TableName;
dt_Locations.TableName;
dt_Areas.Columns["AreasId"].Ordinal;     // in order to know which column I need to take the value from
                                         // that I need for searching in the Locations table.
dt_Locations.Constraints[0].<properties> // properties: "RelatedColumns", "RelatedTable", "Table", ...

I am capable of doing that, but it looks like such an enormous burden, that I almost cannot believe such a feature not being provided by the .Net platform.

Am I correct? Is there a simple way to remove the "dependencies" of a constraint?

Thanks in advance

Edit after first comment from Caius

As far as I have understood, there are basically two types of DeleteRules, None and Cascade, with their own meaning:

  • None : don't let the tupples be deleted if they are referenced by tupples in other tables.
  • Cascade : not only delete the tables that are referenced, but the referencing ones too.

What I would like to do, is to show a "warning" message, something like this here:

Screenshot

Answering "Yes" would cause all the tupples to be deleted.
Answering "No" would cause none of the tupples to be deleted.

This comes down to this (erroneous) code:

try
{ 
  dt_Areas.Rows[0].Delete();
}
catch (Exception ex)
{
  if (MessageBox.Show("In order to .... Is that what you want?",
                      "Warning",
                      MessageBoxButton.YesNo) == MessageBoxResult.Yes)
  {
    (ForeignKeyConstraint)(dt_Locations.Constraints[0]).DeleteRule = Rule.Cascade;
    dt_Areas.Rows[0].Delete();
    (ForeignKeyConstraint)(dt_Locations.Constraints[0]).DeleteRule = Rule.None;
  }
}

However, as mentioned, this code is erroneous, because it seems not to be possible to alter the DeleteRule at runtime, hereby the compilation result:

error CS1061: 'Constraint' does not contain a definition for 'DeleteRule' 
and no accessible extension method 'DeleteRule' accepting
a first argument of type 'Constraint' could be found
(are you missing a using directive or an assembly reference?)

How can I solve this?


Solution

  • It should be possible to toggle the behavior of the constraint at runtime by e.g.

        ((ForeignKeyConstraint)dt_Locations.Constraints[0]).DeleteRule = Rule.Cascade
    

    If your FK will not always be the first constraint, you might have to look for it with eg

    foreach(Constraint c in dt_Locations.Constraints)
      if(c is ForeignKeyConstraint fk ...)
        fk.DeleteRule = ....
    

    You can replace the three dots with additional logic such as inspecting the name, if you need..