Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Restrict delete solution?
Message
General information
Forum:
ASP.NET
Category:
The Mere Mortals .NET Framework
Miscellaneous
Thread ID:
01141331
Message ID:
01156750
Views:
13
This message has been marked as the solution to the initial question of the thread.
Here's a partial generalized "restrict delete" solution for MM2.2 business object framework. Please take a look at it, and anyone improves it, please let me know.

It (for now,):
1) Works in MSSQL 2005. Not sure about 2000.
2) Uses MSSQL db public stored procedure "sp_fkeys" to create a DataSet of foreign tables/keys for a passed table. The query could be optimized, and is apparently "SQLForeignKeys" in ODBC.
3) Checks all foreign tables and returns a count of tables with delete restricted records for the record to be deleted. This could be optimized to exit on the first restricted result. I'd prefer to give the user message (a string array) of which tables have records which would need to be deleted first, but a) don't want to give "internal" names of tables, and b) haven't implemented metadata solution for English names for tables.
4) Haven't checked this with solutions using mmBusinessEntity yet, but I think it should work.
5) Assumes the primary key of the current business object is the foreign key value. This works for me, but others may need to change. Handling compound primary/foreign keys is an exercise left to others.
6) I've reformatted the code here to fit the posting window, if there are errors I've created in doing so, they should be easy to fix.

Changes to ABusinessObject:
 /// <summary>
 /// Application-level Business Object class
 /// </summary>
 public class ABusinessObject : mmBusinessObject
 {
    ...
   /// <summary>
   /// English singular name of the Business Object/Table record
   /// </summary>
     public string Name
     {
       get { return _name; }
       set { _name = value; }
     }
     private string _name = "record";

   /// <summary>
   /// Check if record can be deleted, set rules
   /// </summary>
   /// <param name="dr"></param>
   /// <returns></returns>
   protected override bool HookPreDelete(DataRow dr)
   {
      bool result = true;

      // get number of restricted tables
      // +++ get English names of tables for messages
      int restrictedTableCount = this.CheckForeignTableDelete(dr);

      // Can't delete
      if (restrictedTableCount > 0)
      {
         // Add a broken rule
         mmBusinessRule Rules = this.GetBusinessRuleObject();                
         Rules.ClearAll();

         // format message
         string errMsg = "Cannot delete this " + this.Name + ".  " + 
            restrictedTablesCount.ToString();
         errMsg += restrictedTableCount == 1 ? " table has" : " tables have";
         errMsg += " records dependent on it.";

         Rules.AddErrorProviderBrokenRule(null, errMsg);

         // return false so delete does not occur                        
         result = false;
      }
      return result;            
   }

   /// <summary>
   /// check if foreign key relations prevent deletion of passed data row
   /// </summary>
   /// <param name="dr">DataRow containing foreign key value(s)</param>
   /// <returns>number of tables with restricted records</returns>
   /// <remarks>
   /// +++ this should be altered to handle compound keys
   /// +++ change to return string of English table names, 
   ///    perhaps from extended properties, or metadata table
   /// Only tested for MSSQL2005, 
   ///    The sp_fkeys stored procedure is equivalent to SQLForeignKeys in ODBC.
   /// </remarks>
   private int CheckForeignTableDelete(DataRow dr)
   {
      int restrictedTableCount = 0;

      // get foreign keys from catalog
      mmDataAccessBase DAO = this.GetDataAccessObject();

      // see SQL 2005 docs for sp_fkeys
      // FKTABLE_NAME
      // FKCOLUMN_NAME
      // DELETE_RULE smallint 0 = cascade, 1 = restrict

      DataSet dsForeignKeys = new DataSet();

      DAO.FillDataSet(
         dsForeignKeys, 
         "sp_fkeys", 
         "fk_table", 
         CommandType.StoredProcedure, new IDbDataParameter[] { 
             this.CreateParameter("@pktable_name", this.TableName) }, 
         true);

      // for each table in dsForeignKeys, check if foreign key value exists
      if (dsForeignKeys != null && dsForeignKeys.Tables[0].Rows.Count > 0)
      {
         foreach (DataRow drForeignKeys in dsForeignKeys.Tables[0].Rows)
         {
            // if deletes are restricted, 1 = restrict
            if ( Convert.ToInt32( drForeignKeys["DELETE_RULE"] ) == 1)  
            {
               // check if restricted records exist
               int exists = (int)DAO.ExecScalar(
                  "IF EXISTS " +
                  "(SELECT * FROM " + 
                  "[" + drForeignKeys["FKTABLE_NAME"].ToString() + "] WHERE [" + 
                  drForeignKeys["FKCOLUMN_NAME"].ToString() + "] = @fkvalue) " +
                  "SELECT 1 ELSE SELECT 0",
                  new IDbDataParameter[] 
                  { this.CreateParameter("@fkvalue", dr[this.PrimaryKey]) }
                  );

               if (exists == 1)
               {
                  restrictedTableCount++;
               }
            }
         }
      }
      return restrictedTableCount;
   }
}
To show the error message in your form, you need code (elsewhere in this thread) like (in the delete button):
   this.Result = mmButtonHelper.DeleteHandler(this, this.BindingSource,
   this.BindingSourceMember, ref this.BizObj);

   // if can't delete, show message
   if (this.Result == false)
   {
      mmBusinessObject bizObj = (mmBusinessObject)this.BizObj;

      mmBusinessRule Rules = bizObj.GetBusinessRuleObject();
                    
      if (Rules != null)
      {
         string ErrorMsg = Rules.GetAllBrokenRules();
         if (!mmString.Empty(ErrorMsg))
         {
            MessageBox.Show(ErrorMsg, "Can't Delete");
         }
      }                    
   }
Somewhat advanced:

I implement default views in order to have the mmDataGridView default sorted. There's a bug in mmToolStripButton where you can't set the BindingSourceMember, so I've had to subclass mmToolStripButtonDelete. PLUS figure a way to get the BindingSourceMember view. I get this from the mmDataGridView. This view has to be set in mmButtonHelper.DeleteHandler(), or else the delete-click will always delete the FIRST record of the DataSet, NOT the current record. Later, when implementing Save/Cancel/New/Delete menu tabs to allow hotkeys for these behaviors, I decided to override the form delete and just call that for all deletes. I hope this saves people a bunch of time figuring, and/or influences changes in the foundation.

These changes assume that these deletes will be on the form's PRIMARY Business Object.

Changes to subclass of mmMaintenanceForm:
/// <summary>
/// extended MM business form
/// </summary>
public class myMaintenanceForm : mmMaintenanceForm
{
   // ...
   public mmDataGridView NavDataGrid
   {
      get { return _navDataGrid; }
      set { _navDataGrid = value; }
   }
   private mmDataGridView _navDataGrid = null;

   // Called by delete menu tab delete tool strip button 
   public override bool Delete()
   {
      return base.Delete((mmBusinessObject)this.PrimaryBizObj,
        (DataView)this.NavDataGrid.DataSource);  // <-- NOTE passing DataView
   }
}
Changes to EACH maintenance form:
/// <summary>
/// Summary description for GradeForm
/// </summary>
public partial class MyForm : myMaintenanceForm
{
   // ...
   
   /// <summary>
   /// Constructor
   /// </summary>
   public MyForm()
   {
      // Instantiate and register business objects
      // ...

      InitializeComponent();

      // ... 

      // Register the DataGrid as the Navigation control
      this.NavControl = this.grdMyGrid;

      // NEW - Required for menutabs and ToolStripButtons when using DataViews!!!
      // Register the DataGrid as a reference for the form's Navigation DataSource
      this.NavDataGrid = this.grdMyGrid;

      // ...
   }
   // ...
}
Changes to subclass of mmToolStripButtonDelete:
Add a new component. Then change the .cs to something like:
/// <summary>
/// ToolStrip Delete for myMaintenanceForm
/// </summary>
public partial class myToolStripButtonDelete : mmToolStripButtonDelete
{
   public myToolStripButtonDelete ()
   {
      InitializeComponent();
   }

   public myToolStripButtonDelete (IContainer container)
   {
      container.Add(this);

      InitializeComponent();
   }

   /// <summary>
   /// Delete click event 
   /// </summary>
   /// <param name="sender"></param>
   /// <param name="e"></param>
   protected override void mmToolStripButtonDelete_Click(object sender, EventArgs e)
   {
      if (this.BindingFlag)
      {                
         // Get a reference to the parent business form
         mmBusinessBaseForm ParentForm = mAppDesktop.FormMgr.GetParentBusinessForm(this);

         myMaintenanceForm Maintform = ParentForm as myMaintenanceForm;
                
         // set the binding to DefaultView of the NavDataGrid
         // code was:
         // this.Result = mmButtonHelper.DeleteHandler(this, this.BindingSource,
         //    this.BindingSourceMember, ref this.BizObj);
         // BUT these bindings never got set
         //    this.BindingSourceMember = "someDataView" will cause error
         //           
         // old way, deprecated, still works
         //this.Result = mmButtonHelper.DeleteHandler(this,              
         //   Maintform.NavDataGrid.BindingSource,
         //   Maintform.NavDataGrid.BindingSourceMember, ref this.BizObj);

         this.Result = Maintform.Delete();

         // if can't delete, show message
         if (this.Result == false)
         {
            mmBusinessObject bizObj = (mmBusinessObject)Maintform.PrimaryBizObj;
                                         
            mmBusinessRule Rules = bizObj.GetBusinessRuleObject();
                    
            if (Rules != null)
            {
               string ErrorMsg = Rules.GetAllBrokenRules();
               if (!mmString.Empty(ErrorMsg))
               {
                  MessageBox.Show(ErrorMsg, "Can't Delete");
               }
            }                    
         }
      }
   }
}
Thanks!
Previous
Reply
Map
View

Click here to load this message in the networking platform