【问题标题】:Dynamically Creating GridView动态创建 GridView
【发布时间】:2012-07-10 16:17:49
【问题描述】:

我想创建一个显示 PDF 文件记录的 GridView。这些记录可以附加用户可自定义的元数据,因此他们可以创建自己的列并在其中输入自己的信息。然后我希望它显示在 GridView 中,这样他们就可以对每一列进行排序,如果列顺序为 -1,它将不会显示在 GridView 中。

比如有一个静态表

DocumentsTable:
ID int
PDF_Folder varchar
UserID int

然后还有另一个表,用户可以为其创建自己的列

MetaDataColumns:
ID int
userid int foreign key
KeyName varchar
Order int

以及保存值的表格

MetaDataValues:
ID int
UserID int foreign key
DocumentID int foreign key
MetaDataID int foreign key
value varchar(100)

现在的问题是我需要从 MetaDataColumn 中获取列来创建 GridView,然后用 MetaDataValue 表中的值填充它。我最初的计划是拥有一个动态创建 GridView 并向其添加列的函数,但是我一直坚持如何将 MetaDataValue 中的值用作列。或者,我可以只使用 GridView AutoGenerate 列,但我需要自定义 SQL 以显示自定义数据。我对如何处理这个问题有点困惑。

我想出的一种方法是这个伪代码:

private DataTable CreateColumns()
{
   var columns =  select * from MetaDataColumns 
                  where userid = UserId;

   DataTable dt = new DataTable();

   foreach (column in columns)
   {
       dt.Columns.Add(new DataColumn(column[keyName], typeof(string));  //assumes all string
   }

 return dt
}

private void PopulateDG(DataGrid dg)
{
    var documents = select * from DocumentsTable
                     where userid=UserId;

    foreach (document in documents)
    {            
        var columnValues = select * from MetaDatavalues 
                           documentID == document.id;

        DataRow dr = dg.NewRow();
        dr[columnValues.KeyName] = columnValues.value;

    }

 }

 private void LoadGV()
 {  
   DataGrid dg = CreateColumns();
   PopulateDG(dg);
   GridView.datasource = dg;
   GridView.DataBind();
  }

我不喜欢这种设计的一件事是它为文档表中的每一行创建另一个查询。我不确定这是否是 SQL 的问题?

【问题讨论】:

    标签: c# asp.net linq database-design


    【解决方案1】:

    您的问题主要是由于数据库的设计。您必须动态添加列,因为您已将列 (in 3NF) 转换为表中的一行。显然,这是因为您允许用户添加他们自己的列 - 我的心不寒而栗,但这就是应用程序的工作方式:-)。

    由于MetaDataColumns 的结构,我将假设用户能够定义一组列名,然后他们可以根据需要选择将其应用于单个文档。

    我认为问题在于,在一个完全去规范化的数据库中,试图正确地规范化一切,你已经设法给自己带来了很多麻烦。我的解决方案是非规范化您的表MetaDataValues。您没有提及您使用的是什么 RDBMS,但 MySQL 的硬限制为 4096 columns 或 65k 字节。 Oracle 中的限制是 1000 和 SQL Server 中的 1024

    如果您将MetaDataValues 的结构更改为以下结构,您应该能够在其中容纳至少 332 组信息。这将在 UserIDDocumentID 上分别唯一,因此理论上您可以删除代理键 ID

    MetaDataValues:
    ID int
    UserID int foreign key
    DocumentID int foreign key
    KeyName1 varchar
    Order1 int
    Value1 varchar(100) 
    ...
    KeyNameN varchar
    OrderN int
    ValueN varchar(100)
    

    当然,这确实将允许单个用户创建的列数上限设置为 332;但是,限制用户发疯的能力是正常的,任何能够想到将 332 位元数据单独存储在单个 PDF 中的人都应该受到某种限制。

    如果您确实有特别痴迷信息的用户,您可以随时声明具有相同结构的第二个表并继续填写。

    这样做意味着MetaDataColumns 除了向用户显示选项外,不会用于其他任何事情。您将必须在每次进行更改时更新MetaDataValues,并确保您没有覆盖已经存在的信息可能有点麻烦。我怀疑你必须做一些事情,比如在更新之前选择记录,遍历KeyName1 .. KeyNameN 并填写第一个没有任何数据的记录。或者,您可以只编写一个绝对可怕的 SQL 查询。无论哪种方式,这都会成为“瓶颈”。

    另一种选择是向MetaDataColumns 添加一个附加列,这表明该列与哪个 N 相关,但这将用户绝对限制为 332 列,而不是每个文档 332 列。

    但是,您现在从数据库中进行选择非常简单:

    select d.*, m.*
      from DocumentsTable d
      join MetaDataValues m
        on d.ID = m.DocumentID
       and d.UserID = m.UserID
     where d.UserId = ?
    

    无需尝试遍历动态生成 1,000 个列选择语句的表。所有信息都在那里,您可以轻松访问。

    归根结底,您问题的“正确”答案取决于您想把时间花在哪里。您是否希望多花半秒来创建或更新文档,或者多花半秒(可能更多)来选择该文档上的信息。

    就我个人而言,我认为用户理解创建某些东西需要时间,但没有什么比等待很长时间才能看到某些东西更烦人的了。

    还有另一种社交解决方案,而不是数据库解决方案。不要让您的用户创建自己的列。选择您的用户想要的最常见的元数据片段,并在数据库中以规范化的形式正确创建它们。您将能够创建具有正确数据类型的列(从长远来看,这为您省去很多麻烦)并且更轻松。我怀疑你会幸运地发生这种情况;但值得牢记。

    【讨论】:

    • 我最初的设计只是有 10 列用户可以重命名。显然我可以根据需要将其扩展到尽可能多的列,并且只有一个最大限制...
    • 在我走这条路之前.. 有没有办法我可以使用 PIVOT 只使用 1(或 2 来获取列名)查询。
    • 也有这个想法,元数据表的意义何在。我不会把 keynameN、orderN、valueN 放在文档表中吗?其他 2 个表的唯一原因是我可以动态添加列..
    • 实际上是 2 个表。文档表中的值以及键名和顺序。
    • 在枢轴上,我知道没有可以动态命名列的数据库。是的,您可以将其减少到两个。当时我一直分开,因为我不知道这会有多大的事业。更新关键字会很痛苦,但如果你只有 10 列,那应该会更容易。
    【解决方案2】:

    你是说

    <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="false">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Path=Id}" Header="ID"/>
            <DataGridTextColumn Binding="{Binding Path=Name}" Header="Name"/>
        </DataGrid.Columns>
    </DataGrid>
    

    并在后面的代码中创建一个类

    public class MetadataSource
    {
        public MetadataSource()
        { // use reflection to create properties/values }
    }
    

    【讨论】:

      【解决方案3】:

      我肯定看到您的伪代码方法存在问题,其中每行数据代表一个单独的查询。当您有 1,000 行数据时,您最终将有 1,000 多个查询访问数据库,您的页面会非常慢。

      您至少可以将两个 SQL 查询组合起来作为立即改善这种情况的步骤,例如:

      var values = SELECT * FROM MetaDataValues
      WHERE documentid IN (SELECT id FROM DocumentsTable WHERE userid = UserId)
      
      foreach (val in values)
      {            
          DataRow dr = dg.NewRow();
          ...
      }
      

      我一般不喜欢“SELECT *”方法,它会强制数据库进行额外的查询以填充所有列。在您的情况下,鉴于用户可能被限制在他们可以看到的列中,SQL 在许多方面会带来比必要更多的数据。因此,您的代码可以进一步整合和优化,例如:

      private void PopulateDG(DataGrid dg)
      {
          var columns = SELECT columnKey FROM MetaDataColumns
                  WHERE userid = UserId;
      
          // pseudo code, join column keys into a comma delimited string
          string columnFields = string.Join(",", columns);
      
          string getValueSql = string.Format("SELECT {0} FROM MetaDataValues
                  WHERE documentid IN (SELECT id FROM DocumentsTable WHERE userid = UserId)", columnFields);
      
          var values = ExecuteSql(getValueSql);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-14
        • 1970-01-01
        相关资源
        最近更新 更多