表单和列表,或者它的前身“用户定义表”模块,是为创建和维护列表而设计的。由于输出可以用模板控制,该模块可以用于许多不同的场景:公告、联系人列表、食谱、词汇表、常见问题解答、留言簿、评论、地址列表、地图等。简而言之,表单和列表是一个适用于许多不同用例的通用工具箱。
设计灵活性也有缺点;它没有针对性能进行优化。数据是动态的;列表不直接映射到SQL表。相反,数据被序列化成字段值对流,这使得很难直接用SQL处理。您不能使用简单的SQL语句进行查询,而且索引对搜索也没有好处。
对于很多用例,这种方法已经足够好了,通常列表很小,只包含几十到几百个条目。但我听说过更大的安排。他们通常开始小,设置是快速和容易。一开始一切都很顺利。然后,经过一段时间的生产后,数据增长到数千条记录,将体验变成一场噩梦。在论坛上被问到时,我们只能承认这是设计出来的。
我们过去使用较大的记录集进行了一些测试,并且能够解决最坏的减速影响。最后几天,我花了一些时间进行更严肃的测量。为了更好地理解,我将勾勒出表单和列表的渲染管道。
渲染过程

SQL模式
- Form和List中的每个单元格都存储在表“UserDefinedData”中。表中的每条记录都包含价值作为字符串并引用场和行。将值保存到类型的列中ntext (*)。
- 表“UserDefinedFields”保存着列表的模式。除了名称和字段类型之外,它还包含最常见的属性。此外,表“UserDefinedFieldSettings”根据当前字段类型保存了额外的专门化信息。
- 所有模块实例的数据通过调用存储过程并将内容加载到具有三个datatable的数据集中来传输:字段,FieldSettings,数据。
- 在加载时,字段值对流被传输并旋转到DataTable中数据。它的列映射到字段,并且值已经转换为匹配的系统数据类型,如String或Int。
-
一些表单和列表类型需要特殊处理:
- 例如,datetime值存储在UTC中,尽管它们应该在门户或用户时间中显示。我们也不想为布尔值显示一个简单的0或1,而是显示一个复选框的图像,是否选中取决于该值。
- 某些类型返回对页面/制表符或文件的引用,例如:文件标识= 1234。诸如名称、路径或下载URL之类的信息需要通过额外的API调用来检索。
- 其他类型(如计算列)则动态执行,查询其他列
- 表单和列表添加隐藏列以获取收集到的任何其他信息。
- 创建完所有列后,就可以创建数据表了排序或过滤后的对着任何柱子。这是旧时尚数据集的一个好处;早在Linq (to object)之前,它就支持脱机过滤语句。
- 作为最后一步,将数据集(最好是表数据)绑定到网格上,或者将数据集转换为XML和xsl——转换为Html。
简而言之:
GetData模块→创建数据集→丰富数据集→过滤数据集->绑定到输出
可能有多少记录?
它是如何表现的呢?首先,让我们从一个数据类型为text的自定义字段开始,用字符串“text”填充。由/at创建/更改的强制审计字段也是该模块的一部分。我只是将每次访问的记录数量增加了10倍。结果如下:

x轴表示记录的数量,y轴表示所需的时间。
呈现时间尺度与记录的数量成线性关系。在我的机器上渲染10,000条记录需要大约两秒钟的时间。这对于只有视图访问者的小型网站来说可能是可以接受的,但在繁忙的环境中肯定会造成麻烦。
电场类型的影响
接下来,我创建了一个列表,其中包含每种数据类型的一个字段。我添加了几行代码,导出了一个模块,编辑了多次复制数据部分的XML,然后再次导入。这产生了一个大约有18000条记录的列表。从页面加载到输出需要超过35秒的时间。现在我对渲染管道的不同部分的影响很感兴趣:

正如您所看到的,将这个集合从数据库加载到数据表已经花费了将近5秒的时间,但是大部分时间都花在了字段的处理上(5)。让我们详细分析一下:

(请注意,y轴是exp10)
- 时间简单类型(字符串和数字)可以被安全地忽略。我们说的是纳秒。
- 值类型添加隐藏列(5)耗时约10µs。如果编写时没有考虑到性能,即使重复调用本地化也会导致延迟。
- 引用类型(5b)正在调用API来检索更多信息,在最坏的情况下导致额外的缓慢的数据库调用。下载已经花费了0.1ms。Image类型甚至更慢,但已经优化(**)这设置。
- 计算列而查找也不是那么慢,至少对于非常简单的表达式来说是这样。
- 加载数据需要时间,并且与第一次记录的尺寸相匹配。
- 绑定到数据网格或XSL转换(不在本记录中)可以忽略,至少如果它们限制了输出的数量,例如使用分页。
结果
- 表单和列表在每个模块的记录数量有限的情况下工作得非常好,支持并解决了许多用例。它从来就不是为更大的数据集设计的。
- 关于什么是合理的大小,没有一个通用的答案——它在很大程度上取决于不同数据类型的使用情况。
- 如果您将超过1,000条记录存储到单个Form和List中,并且该列表仍在增长,则可能需要重新考虑您的设计。
结论
我希望这个考试能帮助你优化你的表单和列表处理。问题是,这是否也有助于优化模块本身。我过去有过很多关于绩效的建议,这里有一些想法和我的观点:
- 虽然System.Data.DataSet速度较慢,但它仍然是模块的基础。如果它被任何illist替换
和Linq,它肯定会提高速度,但任何过滤器或计算列肯定会被打破。它将不再是表单和列表。
- 数字或bool字段的类型感知列将没有可测量的效果,因为这些改进将发生在ns的维度上,而不是µs。
- 缓存很困难,因为数据集可能对当前上下文有很多依赖,例如用户时间、本地化或令牌
- 优化某些类型是可能的,因为API调用的结果可以缓存,至少每个请求(**)。
- 在数据库内部连接以填充关于选项卡和文件的信息对于性能来说是有意义的,但是它会绕过公共DotNetNuke API。DotNetNuke架构不是API的一部分,可以在每个新版本中进行更改——这甚至不是一个bug,只是糟糕的编程。此外,Form和List数据类型独立于模块。可以删除、替换或添加新的数据类型到Form和List,而无需触及模块的源代码。
当前方法的缺点是,过滤发生在渲染管道的非常后期的状态。因此,可以针对任何列和列类型进行筛选和排序。
解决方案可能很简单,通过在SQL服务器端添加过滤:
在SQL Server上过滤数据→为模块GetData→创建数据集→丰富数据集→过滤数据集→绑定输出
这里我不喜欢的是它不能对每种数据类型都起作用,过滤在管道中会发生两次,两种类型的过滤器肯定会有不同的语法。没有任何背景知识的用户会感到困惑。
我想知道你在想什么。表单和列表是否提供任何额外的过滤器支持?还有其他想法吗?
*将字段值的类型从ntext更改为nvarchar(max)不会对性能产生任何影响,尽管它在其他原因上是有意义的。
**在这里的特殊情况下,只有大约20个不同的图像一次又一次地重复,缓存将图像的时间从大约12秒减少到不到一秒(18k记录)。