C# 如何在DataView.ToTable()中避免KeyNotFoundException?

C# 如何在DataView.ToTable()中避免KeyNotFoundException?,c#,asp.net,datatable,dataview,C#,Asp.net,Datatable,Dataview,我正在开发一个旧式ASP.NET代码库,它将一些信息存储在DataTable对象内的缓存中(通过企业库)。它是一个运行在.NET 4.0中的多用户intranet环境。生产中存在一个问题,该问题可以追溯到以前的“已修复”问题,作为其可能的根本原因:调用DataView.ToTable()时发生的KeyNotFoundException。此特定代码是验证的一部分,在从应用程序中的大多数页面加载页面期间可能会发生验证 var table = GetSomeDataTableFromCache();

我正在开发一个旧式ASP.NET代码库,它将一些信息存储在
DataTable
对象内的缓存中(通过企业库)。它是一个运行在.NET 4.0中的多用户intranet环境。生产中存在一个问题,该问题可以追溯到以前的“已修复”问题,作为其可能的根本原因:调用
DataView.ToTable()
时发生的
KeyNotFoundException
。此特定代码是验证的一部分,在从应用程序中的大多数页面加载页面期间可能会发生验证

var table = GetSomeDataTableFromCache();
var view = table.DefaultView;
view.RowFilter = "Foo = 'Bar'";
var filteredTable = view.ToTable();
这是代码的简化。发生的事情是,在过去的几天里,这段代码显然抛出了前面提到的异常。先前的开发人员通过捕获并接受异常并返回null来“修复”它。这种行为被认为是其他问题的罪魁祸首

我试图复制原始异常,但没有取得多大成功。我觉得,如果我能重现它,我就能理解它,并试图形成一个比忽视它更好的解决原始问题的方案,从而避免忽视它所造成的混乱

我注意到Google在
DataView.ToTable()+KeyNotFoundException
上的搜索结果表明验证列名是否存在。此处有一个示例:

但是,我注意到,当将
RowFilter
设置为无效列名时,该行上会出现
EvaluateException
。我还尝试通过使用空表以及使用产生空结果的过滤器来排除错误。事实证明,每种情况都不例外


那么,如果不是建议的无效列名,
KeyNotFoundException
会在哪里发生呢?一旦我们知道它是如何发生的,我们如何避免它呢?

请注意,ASP.NET本质上就是一个多线程系统。被证明对读取是线程安全的,但对写入不是。这是很重要的考虑。

在本文介绍的代码段中,从缓存中提取
数据表
的实例并访问其
默认视图
以进行筛选并写入新的
数据表
可能会出现不安全的情况,而这并不是因为筛选器中的列,而是可以同时执行相同代码的多个线程

作为一个实现细节,
DataView
利用了几个内部状态位。在执行
ToTable()
期间,涉及多个非本地状态。特别是,有一个字典字段在DataRow上设置了键,还有一个DataRow字段用作键!此字典被清除、添加到,行被引用行的值覆盖,然后设置为null,这是整个过程的一部分。当多个线程同时执行此操作时,一个线程正在覆盖另一个线程所依赖的状态并使其无效并非不可想象。这可能导致问题中提到的例外情况,以及其他潜在的有害后果

无论如何,让我们试着以代码片段为起点,在一个可以利用多次执行的环境中重现这个问题

static void Main()
{
    var table = new DataTable();
    table.Columns.Add("Foo");
    table.Columns.Add("ID", typeof(int));

    for (int i = 0; i < 100; i++)
    {
        table.Rows.Add(i.ToString(), i);
    }

    for (int j = 0; j < 100; j++) 
    {
        Enumerable
           .Range(0,100)
           .AsParallel()
           .ForAll(item => ExecuteToTable(table, item));
    }
}

static void ExecuteToTable(DataTable table, int item)
{
    var view = table.DefaultView;
    view.RowFilter = string.Format("Foo = '{0}'", item);
    var filteredTable = view.ToTable();
}
所以我们复制了它,希望能理解它。避免它怎么样?有几种可能的解决方案,根据您的用例,有些方案比其他方案更容易接受,可以是大规模重写,也可以是细微的更改

你可以

使用访问DefaultView之前的
table.Copy()
复制数据表。这将为每个请求提供自己的表(在上面的代码段中)。但是,如果表很大,复制可能会很昂贵。尝试使用
Copy()
运行上述复制代码,并查看是否避免了异常

完全避免使用数据视图。Linq对数据表也很有用。下面的代码段可用于生成过滤后的DataTable输出。但是,还要注意,如果没有行通过过滤器,
CopyToDataTable()
可能会引发自己的异常。如果有可能发生这种情况,请在调用最后一部分之前拆分代码并检查结果(使用.Any())。与
DataView
相比的另一个缺点是,如果使用可用的
ToTable
重载,则使用
DataView
可以指定希望包含在输出表中的列

var filteredTable
        = table.AsEnumerable()
               .Where(row => string.Equals(row.Field<string>("Foo"), item.ToString(), StringComparison.InvariantCultureIgnoreCase))
               .CopyToDataTable();
var filteredTable
=表.可计算()
.Where(row=>string.Equals(row.Field(“Foo”)、item.ToString()、StringComparison.InvariantCultureIgnoreCase))
.CopyToDataTable();

当然,您可以进一步探索重新设计代码,在缓存策略中添加您自己的线程安全措施,等等,尽管这些措施需要更多的时间来实现更改实际表的过滤方式。

请注意,ASP.NET本质上是一个多线程系统。被证明对读取是线程安全的,但对写入不是。这是很重要的考虑。

在本文介绍的代码段中,从缓存中提取
数据表
的实例并访问其
默认视图
以进行筛选并写入新的
数据表
可能会出现不安全的情况,而这并不是因为筛选器中的列,而是可以同时执行相同代码的多个线程

作为一个实现细节,
DataView
利用了几个内部状态位。在执行
ToTable()
期间,涉及多个非本地状态。特别是,有一个在DataRow上键入的字典字段,还有一个DataRow
var filteredTable
        = table.AsEnumerable()
               .Where(row => string.Equals(row.Field<string>("Foo"), item.ToString(), StringComparison.InvariantCultureIgnoreCase))
               .CopyToDataTable();