在csdn我看到的一个很普遍的编程错误,就是在使用GridView时在page_load中从后台(数据库)绑定数据。实际上,那种写法是asp.net1.1时代我们不太懂asp.net,并且使用DataGrid等比较简单的控件时遗留的一种非常不成熟的写法,没想到现在在很多使用GridView编程的“范例”代码中还不断地影响初学者。这个写法错误地在每一次回发时在page_load中绑定数据,而这个方法中实际上应该是创建上一个页面输出html时的GridView控件树结构,而并不是应该去根据当前后台数据库的最新数据去创建GridView的控件树结构。因此,在page_load中绑定数据不但是大大降低程序运行效率的(因为重复读取后台数据),而且还有逻辑错误。如果要手写绑定代码,那么所有在回发后的绑定功能也应该是在page_load结束之后的某个事件中,而不应该在page_load中。虽然我使用数据源控件,无需手写相关代码,因此可以确保流程正确。但是,为了给不使用数据源控件绑定数据而是手写代码像GridView.DataSource属性传递数据的做法写一个Demo,我写了一个简化了的程序。(这里使用了.net2008的功能)
首先,在你的网站下创建一个cs文件来提供测试数据:using System.Collections.Generic;
using System.Linq;static public class BLL
{
    private static List<TestType> ret;    static public IEnumerable<TestType> GetDatas(string cond1, string cond2)
    {
        if (ret == null)
        {
            ret = new List<TestType>();
            for (int i = 0; i < 10; i++)
            {
                TestType x = new TestType
                {
                    申请人名 = "王海波" + i.ToString(),
                    审核人名 = "",
                    审核意见 = ""
                };
                ret.Add(x);
            }
        }
        var result = ret.Where(c => true);
        if (cond1 != string.Empty)
            result = result.Where(c => c.审核意见.Contains(cond1));
        if (cond2 != string.Empty)
            result = result.Where(c => c.审核人名 == cond2);
        return result;
    }    static public void UpdateRet(string name, string a, string b)
    {
        TestType line = ret.Where(x => x.申请人名 == name).FirstOrDefault();
        if (line != null)
        {
            line.审核意见 = a;
            line.审核人名 = b;
        }
    }}public class TestType
{
    public string 申请人名 { get; set; }
    public string 审核意见 { get; set; }
    public string 审核人名 { get; set; }
}这里,业务对象是由TestType来简单定义的。BLL类有两个方法,用来查询和更新数据。我使用一个 List<TestType> 类型的静态数据集合来保存演示测试数据。实际使用时则往往只要将方法改为对后台数据库操作即可,接口时一样的。
而一个处理界面的aspx内容如下:<%@ Page Language="C#" %><script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            this.GridView1.DataBind();
    }    protected void GridView1_DataBinding(object sender, EventArgs e)
    {
        this.GridView1.DataSource = BLL.GetDatas(this.TextBox1.Text.Trim(), this.TextBox2.Text.Trim());
    }    protected void GridView1_RowEditing(object sender, GridViewEditEventArgs e)
    {
        this.GridView1.EditIndex = e.NewEditIndex;
        this.GridView1.DataBind();
    }    protected void GridView1_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
    {
        this.GridView1.EditIndex = -1;
        this.GridView1.DataBind();
    }    protected void Button1_Click(object sender, EventArgs e)
    {
        this.GridView1.DataBind();
    }    protected void 更新这一行(object sender, EventArgs e)
    {
        var bt = sender as LinkButton;
        var a = (bt.FindControl("Label1") as Label).Text;
        var b = (bt.FindControl("TextBox1") as TextBox).Text;
        var c = (bt.FindControl("TextBox2") as TextBox).Text;
        BLL.UpdateRet(a, b, c);
        this.GridView1.EditIndex = -1;
        this.GridView1.DataBind();
    }    private string[] _审核意见 = null;    public string[] 审核意见
    {
        get
        {
            if (_审核意见 == null)
                _审核意见 = new string[] { "", "同意", "不同意", "发回重审" };
            return _审核意见;
        }
    }    protected void 选择审核意见(object sender, EventArgs e)
    {
        var list = sender as DropDownList;
        var tb = list.FindControl("TextBox1") as TextBox;
        tb.Text = list.SelectedValue;
    }
</script><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    审核意见:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
    <br />
    审核人:<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
    <br />
    <asp:Button ID="Button1" runat="server" Text="重新查询" OnClick="Button1_Click" />
    <br />
    <br />
    <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" OnDataBinding="GridView1_DataBinding"
        OnRowCancelingEdit="GridView1_RowCancelingEdit" OnRowEditing="GridView1_RowEditing">
        <Columns>
            <asp:TemplateField HeaderText="申请人姓名" SortExpression="申请人名">
                <EditItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text='<%# Eval("申请人名") %>'></asp:Label>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text='<%# Bind("申请人名") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="审核意见" SortExpression="审核意见">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("审核意见") %>'></asp:TextBox>
                    <asp:DropDownList ID="DropDownList1" runat="server" DataSource="<%# this.审核意见 %>"
                        AutoPostBack="true" OnSelectedIndexChanged="选择审核意见">
                    </asp:DropDownList>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label2" runat="server" Text='<%# Bind("审核意见") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="审核人名" SortExpression="审核人名">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("审核人名") %>'></asp:TextBox>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label3" runat="server" Text='<%# Bind("审核人名") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField ShowHeader="False">
                <EditItemTemplate>
                    <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="True" Text="更新"
                        OnClick="更新这一行"></asp:LinkButton>
                    &nbsp;<asp:LinkButton ID="LinkButton2" runat="server" CausesValidation="False" CommandName="Cancel"
                        Text="取消"></asp:LinkButton>
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False" CommandName="Edit"
                        Text="编辑"></asp:LinkButton>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>
    <br />
    <asp:Button ID="Button2" runat="server" Text="什么也不做,专用来测试随机回发时页面状态是否正常" />
    </form>
</body>
</html>
在界面中,有两个查询条件,第一个用于输入过滤审核意见(包含某些文字),第二个用来过滤审核人姓名。请注意,数据时如何、合适绑定的。如果你在 BLL.GetDatas 方法设置一个断点,观察一下,何时才会读取后台数据库值。例如,你可以对某一行,点击“编辑”,然后再DropdownList上选择某个一个审批意见,然后再点击页面最底部的“什么也不做,专用来测试随机回发”按钮,你会看到DroopdownList中的值的状态并不会丢失,而且此时 BLL.GetDatas 中的断点也不会执行(说明并不读取后台数据库值),而且你在“审核意见”的get方法的第一行设置断点调试可以看到此时并不会重新读取DropdownList的后台数据(这说明每一行中某列嵌入复杂控件此时也不会重新绑定)。而开头说的那种错误写法,则是大量地重复读取后台数据用于重新绑定,不但速度及其慢,而且逻辑上也是有bug的(只是你在开发用的机器上没有什么并发操作,很难发现bug)。实际上,我看到很多在page_load中有为GridView的DataSource设置从后台查询的数据集来绑定控件做法,不但初学者会如此,写“范例”代码的blog中也会有很多。这是一个相当普遍的编程错误。

解决方案 »

  1.   

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                this.GridView1.DataBind();
        }
    这个写法其实是我们的一个懒习惯。按照逻辑,此时放在PreRender中其实是更加合适的,既:    protected void Page_PreRender(object sender, EventArgs e)
        {
            if (!IsPostBack)
                this.GridView1.DataBind();
        }
    只是因为页面一创建,就有这个方法写好了,所以我们习惯上随便把代码塞在这个方法里。
      

  2.   

    呵呵!我承认我是在Page_Load中给GridView绑定数据源的。领教了。
      

  3.   

    明确地举个有问题的例子:如果你打开一个GridView的“范例程序”,如果你在page_load一眼就看到这样的代码:protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                bind();
            }
        }
    那么,几乎就可以肯定它就是我这里说的“很普遍的编程错误写法”了。
      

  4.   


    sorry!例子中多谢了一个判断,我要说的是,如果你看到的代码是这样:    protected void Page_Load(object sender, EventArgs e)
        {
            bind();
        }这几乎就可以肯定(凭bind这个名字)是我说的普遍的编程错误写法了。因为,这个程序中只要页面回发就重新绑定数据,所以是错误的。
      

  5.   

    我们经常看到有人说“把bind()放到 if(!IsPostback) 判断的外边”。其实如果在page_load执行bind(),就应该有这个判断,而不要每一次回发时都bind()。举这个例子,同时也说明了另外几个小“技巧”,包括:
    1. 使用一个BLL层来提供测试数据。实际上,对于复杂的程序,花点时间写“假的”测试数据时非常值得的,而真实的BLL代码可以并行、滞后开发。
    2. 在“更新”按钮中,直接查找同一行中的其它控件就可以了,这是稳定可靠的写法。
    3. 许多时候,看似操作正常的界面实际上数据处理有错误。因此可以放置一个专门用来引起回发的测试按钮,像这个demo中所说的操作一样专门在每一个重要的点上随机点击这个按钮。
      

  6.   

    楼主说的极是!!
    用ObjectDataSource就没这个问题!!
    不知理解是否对!!
    望楼主赐教!!谢谢!!
      

  7.   

    是啊,我们现在写的程序都是在page_load里的写的,
    有待改进!
      

  8.   

    感谢楼主
    之前一直写在Page_Load中
    先把代码执行看看效果谢谢了
      

  9.   

    顶呀..学习一下.不过我习惯用于If( !IsPostBack)
    {
     Binder();
    }
      

  10.   

     学习了 ....  但是我之前遇到的问题是动态创建控件 如果放在 If( !IsPostBack) {} 里面的话 就会丢失数据 那个问题困扰我好久,后来不得不用hidden来存起来 
      

  11.   

    LZ,一般情况下都会把绑定的代码if (!IsPostBack)下吧,这样如果没有数据更新时,即点击其他按钮时,这样也不会重复执行绑定代码的吧.
      

  12.   

    写在 Page_PreRender里?效率高多少?没试过
      

  13.   


    楼主比较的是不是这两方法?不过偶有几个疑惑啊,望楼主解答,放在 Page_PreRender的好处在哪?在page_load中绑定数据不但是大大降低程序运行效率的(因为重复读取后台数据),而且还有逻辑错误。
    page_load和Page_PreRender都会执行,如果不判断IsPostBack应该都是一样重复绑定数据的吧?
    而开头说的那种错误写法,则是大量地重复读取后台数据用于重新绑定,不但速度及其慢,而且逻辑上也是有bug的(只是你在开发用的机器上没有什么并发操作,很难发现bug)。 并发操作的发就页面的CS文件是不可能发生的吧?每个请求都new一个新类,除非你的BLL层有可能造成并发错误,但是如果你的BLL层会有这种可能应该你的数据绑定在哪个事件中都会是一样造成并发错误的吧?Page_PreRender是在控件事件后发生,page_load在控件事件前发生,试想一种可能啊,如果写this.bind()中,两种事件都没有加IsPostBack,而你在编辑GridView的时候是否出不了编辑效果?而在page_load事件却可以出效果,虽然这种效果很容易造你上述所说的错误。在page_load还是在Page_PreRender事件中绑定数据哪个更好  能详细文字描述下?
      

  14.   

    看过LZ的一些帖子,感觉很有技术含量,人也很有才华.不过,这篇帖子怎么就这么垃圾呢?
    如果是动态生成的控件,控件上动态绑定事件,你能搞个不在ISPOSTBACK判断外面绑定而成功的例子吗?
      

  15.   

    一直怀疑SP1234大侠的表达能力
    今天看来确实不咋地
    楼主是不是想要说
    “不应该把绑定GridView的代码放在Page_Load里的同时又不使用IsPostBack来限定”?
    也就是说,楼主其实提倡下面的代码:protected void Page_Load(object sender, EventArgs e) {
      if(!IsPostBack) {
        BindGrid();
      }
    }private void BindGrid() {
      gridview1.DataSource = GetData();
      gridview1.DataBind();
    }
    而不是:protected void Page_Load(object sender, EventArgs e) {
      //if(!IsPostBack) {
        BindGrid();
      //}
    }private void BindGrid() {
      gridview1.DataSource = GetData();
      gridview1.DataBind();
    }
    至于楼主推荐的放到 Page_PreRender(object, EventArgs) 中,
    我觉得更加让人迷惑。 
      

  16.   

    厉害啊但我通常使用GridView的时候,是用objectdatasource来提供数据的,我根本就不知道GridView.bind的时候是在Page_Load中执行还是在Page_PreRender中执行