Record Already Exists

Record Already Exists

Old forum URL: forums.lhotka.net/forums/t/3736.aspx


Inquistive_Mind posted on Wednesday, October 17, 2007

Hello,

         I have a Business base BO which I am using for normal DB operations.For the record already exists condition, based on the exception raised (on UI) I am displaying a message that duplicate records cannot be added.This was a short term solution inorder to get things working.

         Right now I would like to implement this checking in the BO itself by using a Command Object in my Business Base BO.Is my approach right?Are there other approaches?Any sample code?

Thanks In Advance

KKoteles replied on Wednesday, October 17, 2007

I have made use of the Exists command Rocky demonstrated quite a bit.  Additionally, I have created my own custom rule to make use of it.  One change I made to the Exists method was to try to use my cached list object first (to try to avoid another trip to the database).  I had to make sure the user was allowed to get the list though before I tried to use it.  I didn’t add authorizations to the Exists command because in my case it was always allowed; however, it is easy enough to add in if you need it.  Here are some snippets of code:

 

protected void AddCustomRules()

{

            //

            // Name

            //

            ValidationRules.AddRule<StatusType>(UniqueName<StatusType>, "Name");

}

 

private static bool UniqueName<T>(T target, Csla.Validation.RuleArgs e) where T: StatusType

{

            if (StatusTypeList.CanGetObject())

            {

                        StatusTypeInfo test = StatusTypeList.Get()[target._name];

 

                        if (test != null && test.ID != target._id)

                        {

                                    e.Description = "Name must be unique";

                                    return false;

                        }

                        else

                        {

                                    return true;

                        }

            }

            else if (StatusType.Exists(target._name))

            {

                        StatusType test = StatusType.Get(target._name);

 

                        if (test.ID != target._id)

                        {

                                    e.Description = "Name must be unique";

                                    return false;

                        }

                        else

                        {

                                    return true;

                        }

            }

            else

            {

                        return true;

            }

}

 

#region Data Access - Exists

 

public static bool Exists(int id)

{

            if (StatusTypeList.CanGetObject())

            {

                        return Example.Business.StatusTypeList.Get().Contains(id);

            }

            else

            {

                        ExistsCommand result;

                        result = DataPortal.Execute<ExistsCommand>(new ExistsCommand(id));

                        return result.Exists;

            }

}

 

public static bool Exists(string name)

{

            if (StatusTypeList.CanGetObject())

            {

                        return Example.Business.StatusTypeList.Get().Contains(name);

            }

            else

            {

                        ExistsCommand result;

                        result = DataPortal.Execute<ExistsCommand>(new ExistsCommand(name));

                        return result.Exists;

            }

}

 

[Serializable()]

private class ExistsCommand : CommandBase

{

            private bool _exists = false;

            private int _id = 0;

            private string _name = string.Empty;

 

            public bool Exists

            {

                        get

                        {

                                    return _exists;

                        }

            }

 

            public ExistsCommand(int id)

            {

                        _id = id;

            }

 

            public ExistsCommand(string name)

            {

                        _name = name;

            }

 

            protected override void DataPortal_Execute()

            {

                        using (SqlConnection cn = new SqlConnection(KJJK.RBAC.Database.DatabaseConnection("Example")))

                        {

                                    cn.Open();

 

                                    using (SqlCommand cm = cn.CreateCommand())

                                    {

                                                cm.CommandType = CommandType.StoredProcedure;

 

                                                if (_id != 0)

                                                {

                                                            cm.CommandText = "ExistsStatusTypeByID_sp";

                                                            cm.Parameters.AddWithValue("@ID", _id);

                                                }

 

                                                if (_name.Equals(string.Empty) == false)

                                                {

                                                            cm.CommandText = "ExistsStatusTypeByName_sp";

                                                            cm.Parameters.AddWithValue("@Name", _name);

                                                }

 

                                                int count = (int)cm.ExecuteScalar();

                                                _exists = (count > 0);

                                    } //using SqlCommand

                        } //using SqlConnection

            }

 

}

 

#endregion //Data Access - Exists

 

Enjoy,

Ken

 

Inquistive_Mind replied on Wednesday, October 17, 2007

Thanks for the information Ken,

Yes, I was able to implement the ExistsCommand object and check for duplicates.But what are you thoughts on checking for an exception type and displaying the message accordingly?Right now I have 2 Catch blocks one specfic for duplicate records and the other a generalized one.This saves me one trip to the database in case of an Insert and Update.Using command based logic takes 2 trips for a successful insert,update where as exception based check just takes one both for a successful,failed insert and update.

Thanks,

KKoteles replied on Thursday, October 18, 2007

Inquistive_Mind,

I'm not sure why you are trying to catch exceptions.  All you really need to do is make the object Invalid (using a BusinessRule) so it cannot be saved.  Then since you know the object is Invalid, you just need to check the BrokenRules collection within the object itself.  If you use something like the ErrorProvider object, then this is handled for you pretty easily - it displays the message as a tooltip over the icon it displays.

When your business rules are checked, you might have to make one trip to the database for the Exists command (maybe not if the List was cached); however, nothing else is done because your object is Invalid.  I'm not sure I follow your concern...

Ken 

tetranz replied on Thursday, October 18, 2007

Don't forget that the only real test is whether or not you succeed in saving where there is a unique index on your database. Checking before you save and making the BO invalid with a "That name already exists" broken rule is all good stuff and makes the UI friendly but there may be the possibility that someone else saves between your check (or when your cached list was loaded) and the actual save. That's when you need to catch the exception and maybe display something like "oops ...  someone else just created that name moments ago."

Ross

Inquistive_Mind replied on Thursday, October 18, 2007

Hello Ken,

               I'm pasting the code snippnet for both ways here,maybe that would make it clearer to comment about the approaches.

try

{

assetTransmission = AssetTransmission.NewAssetTransmission();

assetTransmission.Transmission = TLTransmissionListUserControl.SelectedValue.ToString();

assetTransmission.AssetID =  Convert.ToInt32(Session["AssetID"]);

assetTransmission.FluidCapacity = Convert.ToDecimal(AssetTransmissionFluidCapacityRadNumericTextBox.Text);

assetTransmission.FluidID = Convert.ToInt32(TLFluidListUserControl.SelectedValue);

assetTransmission.TransSerialNumber = AssetTransmissionSerialNumberTextBox.Text;

assetTransmission.Save();

}

catch (Exception ex)

{

result = false;

if (((System.Data.SqlClient.SqlException)ex.GetBaseException()).Number == 2627)

{

MessageLabel.Text = "Value(s) already exist and cannot be added";

MessageLabel.Visible = true;

               }

}

Now using the Exists Command Class I have it as below

if (!AssetFuel.IsExists(Convert.ToInt32(Session["AssetID"]), TLFuelRadComboBox.SelectedValue))

{

assetFuel = AssetFuel.NewAssetFuel();

try

{

assetFuel.AssetID =  Convert.ToInt32(Request.Params["AssetID"]);

assetFuel.Fuel = TLFuelRadComboBox.SelectedValue;

assetFuel.FuelCapacity = Convert.ToDecimal(AssetFuelCapacityRadNumericTextBox.Text);

assetFuel.FuelUnitID = Convert.ToInt32(TLUnitRadComboBox.SelectedValue);

assetFuel.Save();

}

catch (Exception ex)

{

result = false;

Logger.Error(ex.Message );

}

return result;

}

else

{

result = false;

MessageLabel.Visible = true;

MessageLabel.Text = "Fuel already exists.";

return result;

}

In the first code snippnet the BO does not have a static method IsExists() that can call a Command Object to check for existence of the record for whihc i am making use of the exception number to determine if there exisits a duplicate.

However in the second code snippet I do have a static method whihc can invoke the Command Object to check for duplicate record.

Both works but not sure whihc one to pick because I would like to follow one standard and more ever in the first case it only works if the DB is SQL and in the second case the cost is the extra trip to the DB for every insert,update.

Thanks in Advance for all your responses.

P.S: NEWBIE HERE

 

KKoteles replied on Thursday, October 18, 2007

OK,  I kind of see what you are trying to do.  It looks as if you are trying to do this with no direct feedback to the user at the time they are filling out the screen.  One way is to disable the 'Save' button if your object is Invalid.  That way they can't even try to save the object unless it is valid.  You have a slight feedback problem to your users though if they don't know WHY the button is disabled (this is where I use the ErrorProvider).  OK, even if you leave the 'Save' enabled - I think I would lean closer to what you are doing in your first try .. catch; however, I would check to see if the object IsValid before I try to save it.  If it is not valid, then you can display the BrokenRules collection.  tetranz also is correct in that you will still need to guard against a possible caching issue so I wouldn't get rid of the exception checking.  Normally I have several catches - one for Csla.DataPortalException, one for System.Security.SecurityException, and finally Exception.  Here is how I'd quickly change your sample code.  This assumes you do have an Exists command though in order to be used within the validation rule; otherwise, the object will never mark itself as being InValid.  It is at the point at which you set your field as to when the validation will occur - and as a result the call to Exists.  You should not need to call it directly from your UI.  You can if you want, but it might end up in another call to the database.

assetTransmission = AssetTransmission.NewAssetTransmission();

assetTransmission.Transmission = TLTransmissionListUserControl.SelectedValue.ToString();

assetTransmission.AssetID =  Convert.ToInt32(Session["AssetID"]);

assetTransmission.FluidCapacity = Convert.ToDecimal(AssetTransmissionFluidCapacityRadNumericTextBox.Text);

assetTransmission.FluidID = Convert.ToInt32(TLFluidListUserControl.SelectedValue);

assetTransmission.TransSerialNumber = AssetTransmissionSerialNumberTextBox.Text;

if (assetTransmission.IsValid == false)

{

foreach (BrokenRule rule in assetTransmission.BrokenRulesCollection)

{

               MessageLabel.Text += rule.Description + " \r\n";

}

               MessageLabel.Visible = true;

}

else

{

            try

               {

               assetTransmission.Save();

               }

               catch (Exception ex)

               {

               result = false;

               if (((System.Data.SqlClient.SqlException)ex.GetBaseException()).Number == 2627)

               {

               MessageLabel.Text = "Value(s) already exist and cannot be added";

               MessageLabel.Visible = true;

                              }

               }

}

Ken

(and we are all NEWBIES - I learn something new everyday!)

Adalton4 replied on Thursday, October 18, 2007

Ken,

 

I would like to do this.

 

Use the Command object to give visual clues in the UI that something is wrong. This can indeed be done with the method you are proposing.

 

But when the object gets saved, I want to re-check again inside the saving transaction because in the mean time, some other transaction could have inserted (or some other transactional business rule) the same record. I don't want to rely on an exception during insert.

 

My real question is, is there a way to re-use the command object for this?

 

Rgds

Adalton4

KKoteles replied on Thursday, October 18, 2007

Adalton4,

With the UniqueName validation rule and with what you proposed before by calling the Exists command right before the save can both be done - where the exact same Exists method is called.  So yes, you are reusing the command object.  If the code is as you outlined, the time difference between when you set the property (and the Exists method is checked as part if its validation rule) and the time where you would call AssestFuel.IsExists("") is minimal so I don't think I would do them both.  The problem is that each of them is done before the actual save itself - so in theory no matter when you checked using the Exists command there is still a possibility that by the time the save hits the database that you end up with a duplicate.  You will always have to be able to handle the error from a duplicate entry (coming back from the database); however, doing one check (validation rule check before the save), the other check (Exists command right before the save), or both - can reduce the likely hood of it occuring.  If there is a greater time period between when you set the property value (and the property validation rules are checked) and when you call the save method of the object, then I would be more inclined to do as you proposed (and that is to call the Exists command before calling the save because in the time that elapsed there is a possibility that what was unique at the time the property value was set is no longer unique at the time the object is saved).

I'm not sure if I'm helping anymore at this point or not...

Ken

Adalton4 replied on Thursday, October 18, 2007

Inquistive_Mind:
implement this checking in the BO itself by using a Command Object in my Business Base BO

KKToles,

Shouldn't you pass the connection to the Command Object? In this case the exists command runs outside the transaction in the BO... How would you do this?

KKoteles replied on Thursday, October 18, 2007

Adalton4,

The Exists command / method is a static call.  It can be called without even having an instance of the object; therefore, it doesn't make sense to try to pass it a connection.  The method simply returns true or false - independant of anythign else going on within the object.  I'm not sure why you think you would want to do so?

Ken

Copyright (c) Marimer LLC