要掌握Sql Server,我还差得远啊!

一个小问题,把我难了半小时,发现答案后,把我差点没气晕!

昨天下午看www.asp.net提供的IBuySpy示例程序(相信很多人都知道IBuySpy和Stater Kits吧)。Register.aspx提供用户注册。我记得以前开发e-commerce网站时,对于用户注册要加很多审核条件的。想看看它是怎么完成的。

代码很简单:该方法响应用户单击“注册Register”按钮。

private void RegisterBtn_Click(object sender, System.Web.UI.ImageClickEventArgs e) {
            // Only attempt a login if all form fields on the page are valid
            if (Page.IsValid == true) {

                // Store off old temporary shopping cart ID
                IBuySpy.ShoppingCartDB shoppingCart = new IBuySpy.ShoppingCartDB();
                String tempCartId = shoppingCart.GetShoppingCartId();

                // Add New Customer to CustomerDB database
                IBuySpy.CustomersDB accountSystem = new IBuySpy.CustomersDB();
                String customerId = accountSystem.AddCustomer(Name.Text, Email.Text, Password.Text);

            if (customerId != "") {

                // Set the user's authentication name to the customerId
                FormsAuthentication.SetAuthCookie(customerId, false);

                // Migrate any existing shopping cart items into the permanent shopping cart
                shoppingCart.MigrateCart(tempCartId, customerId);

                // Store the user's fullname in a cookie for personalization purposes
                Response.Cookies["IBuySpy_FullName"].Value = Server.HtmlEncode(Name.Text);

                // Redirect browser back to shopping cart page
                Response.Redirect("ShoppingCart.aspx");
                }
                else {
                    MyError.Text = "Registration failed:  That email address is already registered.
";
                }
            }
        }

AddCustomer()方法就是完成添加用户功能的。如果返回ID为空,则添加失败,否则添加Cookie,并将临时用户的购物车内容迁移到已注册用户的购物车中。最后转向购物车页面。

注意到:添加失败显示的信息是:电子邮件已经注册。

试验了一下,Register对同名的用户名没有限制,但限制了同名的e-mail。因为登陆是通过E-mail来完成的。

因为AddCustomer()方法是在CustomerDB类中,而CustomerDB类无非是调用一个存储过程而已,添加新用户的存储过程如下:

CREATE Procedure CustomerAdd
    (
        @FullName   nvarchar(50),
        @Email      nvarchar(50),
        @Password   nvarchar(50),
        @CustomerID int OUTPUT
    )
    AS

    INSERT INTO Customers
    (
        FullName,
        EMailAddress,
        Password
    )

    VALUES
    (
        @FullName,
        @Email,
        @Password
    )

    SELECT
        @CustomerID = @@Identity

要掌握Sql Server,我还差得远啊!

除了要求CustomerID需要唯一性外(数据库是将CustomerID设为自增的),对E-mail并没有特别的要求啊。

在回头看CustomerDB类,也确实是老老实实地用SqlCommand调用该存储过程。传递参数的时候也没有限制。然而当我试图用相同的E-mail注册,就是通不过。显然程序是受到条件限制的。我反复看他自身的帮助文档,又以为是Validator控件来限制。不幸的是,我看清楚了,对于接收输入的电子邮件文本框,Validator控件只是用RequiredField控件要求此项不能为空,用RegularExpressionValidator,加入正则表达式要求输入的e-mail地址合法。并没有搜索数据表,判断e-mail是否重复的功能啊!我真是很困惑啊!

最后我接连注册了几个用户,再打开Sql Server的企业管理器,发现Customer数据表的CustomerID的增长有问题。按道理,随着用户的增加,只要中间没删除,每条记录的ID应是依此递增的。但我却发现我新增加的ID号中间有“断层”。后来分析规律发现,当我新增加一个用户时,例如ID为20;如果此时我连续增加两条e-mail为重复的用户,结果当然是没有添加成功。但当我再添加一个合法用户时,此时ID变为了23。中间恰好空出了2个ID。又试了一次,仍然如此,绝对不是巧合。

恍然大悟,明白了这其实是数据库本身的限制。不过看了Customer表,除了ID加了标识种子,e-mail和name相同没有什么异样。那么难道是trigger在搞鬼?打开触发器一看,根本就没有。

那么为什么呢?思索良久,看到菜单中选项“管理索引”。忽然想到索引就是要求列的值是唯一的。打开来看,果然,表对E-mail添加了索引,选项中有“唯一性”。于是我为用户名也添加了索引,结果自然是也不能添加同名用户了。不过提示信息还是邮件地址错误。为什么呢,代码是就是这样写的嘛。

就是这样一个小问题,折磨了我这么久。归根结底,还是在于自己对数据库太无知了。用这种方法避免重复,自然会很好,不用花时间在存储过程中判断(我以前就是这样做的)。当然也有缺点,就是当要限制多个字段时,例如name,email甚至IDCard,那么给用户的提示警告信息就很麻烦了。因为他们得到的结果都是customerID为空。也许可以用Catch捕获异常来判断,也许索引值不一,抛出的异常不一样?

一会儿试试!

试了一下,原来的CustomerDB类中添加新用户的方法是:

public String AddCustomer(string fullName, string email, string password)
        {

            // Create Instance of Connection and Command Object
            SqlConnection myConnection = new SqlConnection(ConfigurationSettings.AppSettings["ConnectionString"]);
            SqlCommand myCommand = new SqlCommand("CustomerAdd", myConnection);

            // Mark the Command as a SPROC
            myCommand.CommandType = CommandType.StoredProcedure;

            // Add Parameters to SPROC
            SqlParameter parameterFullName = new SqlParameter("@FullName", SqlDbType.NVarChar, 50);
            parameterFullName.Value = fullName;
            myCommand.Parameters.Add(parameterFullName);

            SqlParameter parameterEmail = new SqlParameter("@Email", SqlDbType.NVarChar, 50);
            parameterEmail.Value = email;
            myCommand.Parameters.Add(parameterEmail);

            SqlParameter parameterPassword = new SqlParameter("@Password", SqlDbType.NVarChar, 50);
            parameterPassword.Value = password;
            myCommand.Parameters.Add(parameterPassword);

            SqlParameter parameterCustomerID = new SqlParameter("@CustomerID", SqlDbType.Int, 4);
            parameterCustomerID.Direction = ParameterDirection.Output;
            myCommand.Parameters.Add(parameterCustomerID);

            try {
                myConnection.Open();
                myCommand.ExecuteNonQuery();
                myConnection.Close();

                // Calculate the CustomerID using Output Param from SPROC
                int customerId = (int)parameterCustomerID.Value;

                return customerId.ToString();
            }
            catch {
                return String.Empty;
            }
为了捕获异常,我修改为:

catch (System.Exception e)
{

 return e.Message.ToString()

}

然后将Register.aspx后面的代码改为:

 if (customerId != "") {

    MyError.Text = customerId;

       // Set the user's authentication name to the customerId
       FormsAuthentication.SetAuthCookie(customerId, false);

       // Migrate any existing shopping cart items into the permanent shopping cart
       shoppingCart.MigrateCart(tempCartId, customerId);

       // Store the user's fullname in a cookie for personalization purposes
       Response.Cookies["IBuySpy_FullName"].Value = Server.HtmlEncode(Name.Text);

       // Redirect browser back to shopping cart page

       //注释掉转移页面功能,使我能看到错误的提示信息!
       //Response.Redirect("ShoppingCart.aspx");
          }

我是将CustomerId的值赋给MyError的Text属性中。这里CustomerId是存储过程返回的Id值,为string类型。如果出现异常,其内容应为异常的描述。MyError是提示错误信息的Label控件。这样一改后,当然不好了,因为会将错误信息显示在页面上,不过我现在的目的只是要得到异常的内容。然后我在Customer数据表中添加了对FullName的索引,设置为唯一值。

运行后,我试图添加一同名非法用户,显示异常为:
不能在具有唯一索引 'ix_Name' 的对象 'Customers' 中插入重复键的行。语句已终止。

如果我添加一同e-Mail的非法用户,则显示为:

约束 'IX_Customers'。不能在对象 'Customers' 中插入重复键。语句已终止。

可惜没有所谓异常号,否则可以直接根据异常号,判断是何错误,然后在MyError中显示出来。当然通过字符串也可以办到。就是通过string.IndexOf()去判断是否有该索引就可以了。因为对于每一个索引,其名字是不同的。例如ix_Name代表用户名FullName的索引,IX_Customers代表EmailAddress的索引。根据在异常中能否找到该索引字来判断提示信息。

虽然可以办到,不过比较麻烦,不知道有没有更好的办法呢?我的意思是说不要用存储过程去判断是否有重复列。

 

你可能感兴趣的:(SQL Server)