我想创建一个显示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的问题?
答案 0 :(得分:2)
您的问题主要是由于数据库的设计。您必须动态添加列,因为您已将列(in 3NF)转换为表中的行。显然,这是因为你允许用户添加他们自己的列 - 我的头脑不寒而栗,但这就是应用的工作方式: - )。
由于MetaDataColumns
的结构,我将假设用户能够定义一组列名,然后他们可以根据需要选择应用于单个文档。
我认为问题在于,在一个完全非规范化的数据库中尝试正常化所有内容时,你已经设法给自己带来了很多麻烦。我的解决方案是对你的表MetaDataValues
进行非规范化。您没有提到您正在使用的RDBMS,但MySQL的硬限制为4096 columns或65k字节。 Oracle中的限制是1000和SQL Server中的1024。
如果您将MetaDataValues
的结构更改为以下内容,则应该能够在其中容纳至少 332组信息。这在UserID
,DocumentID
上是唯一唯一的,因此理论上您可以删除代理键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列选择语句。所有信息都在那里,方便您访问。
在一天结束时,您问题的“正确”答案取决于您想要花时间的地方。您是否希望它花费半秒钟来创建或更新文档,或者半秒钟(可能更多)来选择该文档的信息。
就个人而言,我认为用户明白创造某些东西需要时间,但没有什么比等待多年才能看到出现的东西更令人烦恼。
还有另一个社交而非数据库解决方案。不允许用户创建自己的列。选择用户想要的最常见元数据,并在数据库中以标准化形式正确创建。您可以使用正确的数据类型创建列(从长远来看,将为您节省大量麻烦),并且可以更轻松地完成任务。我怀疑你会幸运地发生这种情况;但值得记住。
答案 1 :(得分: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 }
}
答案 2 :(得分: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);