• 要掌握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



    除了要求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的索引。根据在异常中能否找到该索引字来判断提示信息。

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

  • 相关阅读:
    TMainMenu 类[三] 手动建立菜单(5) : 给菜单项添加事件
    TMainMenu 类[二] 成员列表
    TMainMenu 类[三] 手动建立菜单(4) : 添加分割线与隐藏多余的分割线
    初学 Delphi 嵌入汇编[30] 寄存器表
    TMainMenu 类[三] 手动建立菜单(6) : 更换菜单
    TMainMenu 类[三] 手动建立菜单(7) : 指定快捷键
    关于网络编程(服务端)的一些笔记 roen的专栏 博客频道 CSDN.NET
    关于 多进程epoll 与 “惊群”问题
    乱谈服务器编程 MrDB 博客园
    再谈select, iocp, epoll,kqueue及各种I/O复用机制 Shallway 博客频道 CSDN.NET
  • 原文地址:https://www.cnblogs.com/wayfarer/p/5860.html
Copyright © 2020-2023  润新知