动态创建GridView
我想创建一个显示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的问题?
您的问题主要是由于数据库的设计 。 您必须动态添加列,因为您已将列( 在3NF中 )转换为表中的行。 显然,这是因为你允许用户添加他们自己的列 – 我的头脑不寒而栗,但这就是应用程序的工作方式:-)。
由于MetaDataColumns
的结构,我将假设用户能够定义一组列名,然后他们可以根据需要选择应用于单个文档。
我认为问题在于,在一个完全去规范化的数据库中,为了正确地规范化一切,你已经设法给自己带来了很多麻烦。 我的解决方案是对你的表MetaDataValues
进行非规范化。 您没有提到您正在使用的RDBMS,但MySQL的硬限制为4096列或65k字节。 在SQL Server中,Oracle的限制是1000和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
添加一个添加列,它指示列与哪个列相关,但这会将用户绝对限制为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列选择语句。 所有信息都在那里,方便您访问。
在一天结束时,您问题的“正确”答案取决于您想要花时间的地方。 您是否希望它花费半秒钟来创建或更新文档,或者半秒钟(可能更多)来选择该文档的信息。
就个人而言,我认为用户明白创造一些东西需要时间,但没有什么比等待多年才能看到出现的东西更令人烦恼的了。
还有另一种社交而非数据库解决方案。 不允许用户创建自己的列。 选择用户想要的最常见的元数据,并在数据库中以规范化的forms正确创建它们。 您将能够使用正确的数据类型创建列(从长远来看,这将为您节省大量麻烦),并且可以更轻松地完成它。 我怀疑你会幸运地发生这种情况; 但值得记住。
你的意思是
并在代码后面创建一个类
public class MetadataSource { public MetadataSource() { // use reflection to create properties/values } }
我肯定会看到您的伪代码方法存在问题,其中数据的每一行代表一个单独的查询。 当您有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);