8

我想创建一个显示 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 的问题?

4

3 回答 3

2

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

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

我认为问题在于,在一个完全非规范化的数据库中,试图正确地规范化一切,你已经设法给自己带来了很多麻烦。我的解决方案是非规范化你的 table MetaDataValues。您没有提及您使用的是什么 RDBMS,但 MySQL 的硬限制为4096 列或 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 个列选择语句的表。所有信息都在那里,您可以轻松访问。

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

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

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

于 2012-07-14T23:09:46.413 回答
1

我肯定看到您的伪代码方法存在问题,其中数据的每一行代表一个单独的查询。当您有 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);
于 2012-07-14T16:08:01.047 回答
1

你的意思是

<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 }
}
于 2012-07-13T07:49:32.643 回答