检查 SqlDataReader 对象中的列名称
如何检查 SqlDataReader 对象中是否存在列? 在我的数据访问层中,我创建了一个为多个存储过程调用构建相同对象的方法。 其中一个存储过程有一个附加列,其他存储过程未使用该列。 我想修改该方法以适应每种情况。
我的应用程序是用 C# 编写的。
How do I check to see if a column exists in a SqlDataReader
object? In my data access layer, I have create a method that builds the same object for multiple stored procedures calls. One of the stored procedures has an additional column that is not used by the other stored procedures. I want to modified the method to accommodate for every scenario.
My application is written in C#.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(27)
像其他一些答案一样,使用 Exception 来控制逻辑是 被认为是不好的做法并且具有性能成本。 它还向分析器发送抛出的异常的误报,上帝帮助任何人将调试器设置为在抛出的异常时中断。
GetSchemaTable() 也是许多答案中的另一个建议。 这不是检查字段是否存在的首选方法,因为它并未在所有版本中实现(它是抽象的,并且在某些版本的 dotnetcore 中会抛出 NotSupportedException)。 GetSchemaTable 在性能方面也有些过分,因为如果您查看来源。
如果您经常使用循环字段,并且您可能需要考虑缓存结果,那么它可能会对性能产生较小的影响。
Using
Exception
s for control logic like in some other answers is considered bad practice and has performance costs. It also sends false positives to the profiler of # exceptions thrown and god help anyone setting their debugger to break on exceptions thrown.GetSchemaTable() is also another suggestion in many answers. This would not be a preffered way of checking for a field's existance as it is not implemented in all versions (it's abstract and throws NotSupportedException in some versions of dotnetcore). GetSchemaTable is also overkill performance wise as it's a pretty heavy duty function if you check out the source.
Looping through the fields can have a small performance hit if you use it a lot and you may want to consider caching the results.
正确的代码是:
The correct code is:
在一行中,在 DataReader 检索后使用它:
然后,
编辑
更高效的一行,不需要加载架构:
In one line, use this after your DataReader retrieval:
Then,
Edit
Much more efficient one-liner that does not requires to load the schema:
我认为你最好的选择是致电 GetOrdinal("columnName" ) 放在前面的 DataReader 上,并在该列不存在的情况下捕获 IndexOutOfRangeException。
事实上,让我们做一个扩展方法:
编辑
好吧,这篇文章最近开始获得一些反对票,我无法删除它,因为它是已接受的答案,所以我要去更新它并(我希望)尝试证明使用异常处理作为控制流的合理性。
实现此目的的另一种方法,如 Chad Grant 发布的 ,就是循环遍历DataReader中的每个字段,并对要查找的字段名进行不区分大小写的比较。 这会非常有效,并且说实话可能会比我上面的方法表现得更好。 当然,我永远不会在性能成为问题的循环中使用上述方法。
我可以想到一种情况,其中 try/GetOrdinal/catch 方法可以在循环不起作用的情况下起作用。 然而,目前这完全是假设的情况,因此这是一个非常站不住脚的理由。 无论如何,请耐心听我说,看看你的想法。
想象一个允许您对表中的列进行“别名”的数据库。 想象一下,我可以定义一个表,其中包含名为“EmployeeName”的列,但也为其指定一个别名“EmpName”,并且对任一名称进行选择都会返回该列中的数据。 到目前为止和我在一起吗?
现在想象一下该数据库有一个 ADO.NET 提供程序,并且他们已经为其编写了一个 IDataReader 实现,该实现考虑了列别名。
现在,
dr.GetName(i)
(如 Chad 的答案中所用)只能返回单个字符串,因此它必须仅返回一个 上的“别名”柱子。 但是,GetOrdinal("EmpName")
可以使用此提供程序字段的内部实现来检查您要查找的名称的每列别名。在这种假设的“别名列”情况下,try/GetOrdinal/catch 方法将是确保您检查结果集中列名的每个变体的唯一方法。
脆弱吗? 当然。 但值得思考。 老实说,我更喜欢 IDataRecord 上的“官方”HasColumn 方法。
I think your best bet is to call GetOrdinal("columnName") on your DataReader up front, and catch an IndexOutOfRangeException in case the column isn't present.
In fact, let's make an extension method:
Edit
Ok, this post is starting to garner a few down-votes lately, and I can't delete it because it's the accepted answer, so I'm going to update it and (I hope) try to justify the use of exception handling as control flow.
The other way of achieving this, as posted by Chad Grant, is to loop through each field in the DataReader and do a case-insensitive comparison for the field name you're looking for. This will work really well, and truthfully will probably perform better than my method above. Certainly I would never use the method above inside a loop where performace was an issue.
I can think of one situation in which the try/GetOrdinal/catch method will work where the loop doesn't. It is, however, a completely hypothetical situation right now so it's a very flimsy justification. Regardless, bear with me and see what you think.
Imagine a database that allowed you to "alias" columns within a table. Imagine that I could define a table with a column called "EmployeeName" but also give it an alias of "EmpName", and doing a select for either name would return the data in that column. With me so far?
Now imagine that there's an ADO.NET provider for that database, and they've coded up an IDataReader implementation for it which takes column aliases into account.
Now,
dr.GetName(i)
(as used in Chad's answer) can only return a single string, so it has to return only one of the "aliases" on a column. However,GetOrdinal("EmpName")
could use the internal implementation of this provider's fields to check each column's alias for the name you're looking for.In this hypothetical "aliased columns" situation, the try/GetOrdinal/catch method would be the only way to be sure that you're checking for every variation of a column's name in the resultset.
Flimsy? Sure. But worth a thought. Honestly I'd much rather an "official" HasColumn method on IDataRecord.
这是 Jasmin 想法的一个工作示例:
Here is a working sample for Jasmin's idea:
这对我有用:
This works for me:
以下很简单并且对我有用:
The following is simple and worked for me:
我为 Visual Basic 用户写了这个:
我认为这个更强大,用法是:
I wrote this for Visual Basic users:
I think this is more powerful and the usage is:
如果您阅读了这个问题,Michael 会询问 DataReader,而不是 DataRecord 人员。 让你的物体正确。
在 DataRecord 上使用
r.GetSchemaTable().Columns.Contains(field)
确实有效,但它返回 BS 列(请参见下面的屏幕截图。)要查看数据列是否存在并且包含DataReader,使用以下扩展:
用法:
在 DataReader 上调用
r.GetSchemaTable().Columns
返回 BS 列:If you read the question, Michael asked about DataReader, not DataRecord folks. Get your objects right.
Using a
r.GetSchemaTable().Columns.Contains(field)
on a DataRecord does work, but it returns BS columns (see screenshot below.)To see if a data column exists AND contains data in a DataReader, use the following extensions:
Usage:
Calling
r.GetSchemaTable().Columns
on a DataReader returns BS columns:TLDR:
有很多关于性能和不良实践的答案,所以我在这里澄清一下。
返回的列数越多,异常路由的速度就越快,返回的列数越少,循环路由的速度就越快,交叉点在 11 列左右。 滚动到底部查看图表和测试代码。
完整答案:
一些最佳答案的代码有效,但这里存在一个潜在的争论,即基于逻辑中异常处理及其相关性能的接受的“更好”答案。
为了澄清这一点,我不认为有太多关于捕获异常的指导。 Microsoft 确实有关于一些指南 >抛出异常。 他们在那里确实声明:
第一个注释是“如果可能”的宽大处理。 更重要的是,描述给出了这样的背景:
这意味着,如果您正在编写可能被其他人使用的 API,请让他们能够在不使用 try/catch 的情况下导航异常。 例如,为您的抛出异常的 Parse 方法提供 TryParse。 这并没有说明你不应该捕获异常。
此外,正如另一位用户指出的那样,捕获始终允许按类型进行过滤,并且最近允许通过 when 子句。 如果我们不应该使用语言功能,这似乎是对它们的浪费。
可以说,抛出异常会产生一些成本,并且该成本可能会影响重循环中的性能。 然而,也可以说,在“连接的应用程序”中,异常的成本可以忽略不计。 十多年前就对实际成本进行了调查:异常的成本有多高在 C# 中?
换句话说,连接和查询数据库的成本可能比抛出异常的成本相形见绌。
除此之外,我想确定哪种方法真正更快。 正如预期的那样,没有具体的答案。
随着列数的增加,任何在列上循环的代码都会变得更慢。 也可以说,任何依赖异常的代码都会变慢,具体取决于查询失败的速度。
获取 Chad Grant 和Matt Hamilton,我运行了这两种方法最多 20 列,错误率高达 50%(OP 表明他在不同的存储过程之间使用这两个测试,所以我假设只有两个)。
结果:
以下是使用 LINQPad 绘制的 sstatic.net/H0gPW.png" rel="nofollow noreferrer">< /a>
这里的锯齿形是每个列计数内的故障率(未找到列)。
对于较窄的结果集,循环是一个不错的选择。 然而,GetOrdinal/Exception 方法对列数不太敏感,并且在 11 列左右开始优于循环方法。
也就是说,我并没有真正偏好性能,因为作为整个应用程序返回的平均列数,11 列听起来很合理。 无论哪种情况,我们在这里讨论的是毫秒的几分之一。
但是,从代码简单性和别名支持方面来看,我可能会选择 GetOrdinal 路线。
这是 LINQPad 形式的测试。 随意用你自己的方法重新发布:
TLDR:
There are lots of answers with claims about performance and bad practice, so I clarify that here.
The exception route is faster for higher numbers of returned columns, the loop route is faster for lower number of columns, and the crossover point is around 11 columns. Scroll to the bottom to see a graph and test code.
Full answer:
The code for some of the top answers work, but there is an underlying debate here for the "better" answer based on the acceptance of exception handling in logic and its related performance.
To clear that away, I do not believe there is much guidance regarding catching exceptions. Microsoft does have some guidance regarding throwing exceptions. There they do state:
The first note is the leniency of "if possible". More importantly, the description gives this context:
That means, if you are writing an API, that might be consumed by somebody else, give them the ability to navigate an exception without a try/catch. For example, provide a TryParse with your exception-throwing Parse method. Nowhere does this say though that you shouldn't catch an exception.
Further, as another user points out, catches have always allowed filtering by type and somewhat recently allow further filtering via the when clause. This seems like a waste of language features if we're not supposed to be using them.
It can be said that there is some cost for a thrown exception, and that cost may impact performance in a heavy loop. However, it can also be said that the cost of an exception is going to be negligible in a "connected application". Actual cost was investigated over a decade ago: How expensive are exceptions in C#?
In other words, the cost of a connection and query of a database is likely to dwarf that of a thrown exception.
All that aside, I wanted to determine which method truly is faster. As expected there is no concrete answer.
Any code that loops over the columns becomes slower as the number of columns increase. It can also be said that any code that relies on exceptions will slow depending on the rate in which the query fails to be found.
Taking the answers of both Chad Grant and Matt Hamilton, I ran both methods with up to 20 columns and up to a 50% error rate (the OP indicated he was using this two test between different stored procedures, so I assumed as few as two).
Here are the results, plotted with LINQPad:
The zigzags here are fault rates (column not found) within each column count.
Over narrower result sets, looping is a good choice. However, the GetOrdinal/Exception method is not nearly as sensitive to number of columns and begins to outperform the looping method right around 11 columns.
That said, I don't really have a preference performance wise as 11 columns sounds reasonable as an average number of columns returned over an entire application. In either case we're talking about fractions of a millisecond here.
However, from a code simplicity aspect, and alias support, I'd probably go with the GetOrdinal route.
Here is the test in LINQPad form. Feel free to repost with your own method:
这是已接受答案的单行 LINQ 版本:
Here is a one-liner LINQ version of the accepted answer:
这是 Jasmine 的一行解决方案...(还有一个,虽然简单!):
Here is the solution from Jasmine in one line... (one more, though simple!):
为了保持代码的健壮性和简洁性,请使用单个扩展函数,如下所示:
C#
VB:
To keep your code robust and clean, use a single extension function, like this:
C#
VB:
此代码纠正了 Levitikon 的代码问题:
(改编自:[1]:http://msdn .microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx)
从表中获取所有这些无用的列名而不是列名的原因...
是因为您正在获取架构列的名称(即架构表的列名称)
注意:这似乎只返回第一列的名称...
编辑:更正的代码返回所有列的名称,但是 < strong>你不能使用 SqlDataReader 来做到这一点
This code corrects the issues that Levitikon had with their code:
(adapted from: [1]: http://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx)
The reason for getting all of those useless column names and not the name of the column from your table...
Is because your are getting the name of schema column (i.e. the column names for the Schema table)
NOTE: this seems to only return the name of the first column...
EDIT: corrected code that returns the name of all columns, but you cannot use a SqlDataReader to do it
这是一个相当古老的线程,但我想提供我的两分钱。
大多数建议的解决方案面临的挑战是,它要求您每次都枚举您要检查的每一列的每一行的所有字段。
其他人正在使用不受全局支持的
GetSchemaTable
方法。就我个人而言,我对抛出和捕获异常来检查字段是否存在没有任何问题。 事实上,我认为从编程的角度来看,这可能是最简单的解决方案,也是最容易调试和创建扩展的解决方案。 我注意到吞咽异常不会对性能产生负面影响,除非涉及其他事务或奇怪的回滚逻辑。
使用
try-catch
块实现如果您想避免异常处理,我建议您在初始化读取器时将结果保存到
HashSet
中,然后检查返回到您想要的列。 或者,对于微优化,您可以将列实现为Dictionary
,以防止从Name
到ordinal
的重复解析通过SqlDataReader
对象。使用
HashSet
实现 使用
Dictionary
实现This is a pretty old thread, but I wanted to provide my two cents.
The challenge with most of the proposed solutions is that it requires you to enumerate over all fields every time for every row for every column you're checking.
Others are using the
GetSchemaTable
method which is not globally supported.Personally, I have no issue with throwing and catching exceptions to check if a field exists. In fact, I think it's probably the most straightforward solution from a programming perspective and the easiest to debug and create an extension for. I've noticed no negative performance hits on swallowing exceptions except where there is some other transaction involved or weird rollback logic.
Implementation using a
try-catch
blockIf you want to avoid exception handling, I'd recommend saving your results to a
HashSet<string>
when you initialize your reader, then checking back to it for the columns you want. Alternatively for a micro-optimization, you can implement your columns as aDictionary<string, int>
to prevent a duplicate resolution fromName
toordinal
by theSqlDataReader
object.Implementation using
HashSet<string>
Implementation using
Dictionary<string, int>
我也没有让
GetSchemaTable
工作,直到我发现 这样。基本上我这样做:
Neither did I get
GetSchemaTable
to work, until I found this way.Basically I do this:
顺便说一句,
Columns.Contains
不区分大小写。Columns.Contains
is case-insensitive btw.我的数据访问类需要向后兼容,因此我可能会尝试访问数据库中尚不存在的版本中的列。 我们返回了一些相当大的数据集,因此我不太喜欢必须为每个属性迭代 DataReader 列集合的扩展方法。
我有一个实用程序类,它创建一个私有列列表,然后有一个通用方法,尝试根据列名称和输出参数类型解析值。
然后我可以像这样调用我的代码
My data access class needs to be backward compatible, so I might be trying to access a column in a release where it doesn't exist in the database yet. We have some rather large data sets being returned so I'm not a big fan of an extension method that has to iterate the DataReader column collection for each property.
I have a utility class that creates a private list of columns and then has a generic method that attempts to resolve a value based on a column name and output parameter type.
Then I can just call my code like so
整个问题的关键是 此处:
如果引用的三行(当前为第 72、73 和 74 行)被取出,那么您可以轻松检查
-1
以确定该列是否不存在不存在。在确保本机性能的同时解决此问题的唯一方法是使用基于
Reflection
的实现,如下所示:Usings:
基于 Reflection 的扩展方法:
The key to the whole problem is here:
If the referenced three lines (currently lines 72, 73, and 74) are taken out, then you can easily check for
-1
in order to determine if the column doesn't exist.The only way around this while ensuring native performance is to use a
Reflection
based implementation, like the following:Usings:
The Reflection based extension method:
在您的特定情况下(所有过程都具有相同的列,除了一个具有附加一列的过程),检查阅读器的 FieldCount 属性以区分它们会更好更快。
您还可以(出于性能原因)将此解决方案与解决方案迭代解决方案混合使用。
In your particular situation (all procedures has the same columns except one which has an additional one column), it will be better and faster to check the reader's FieldCount property to distinguish between them.
You can also (for performance reasons) mix this solution with the solution iterating solution.
我建议使用
try{} catch{}
来解决这个简单的问题。 但是,我不建议在 catch 中处理异常。I would recommend using
try{} catch{}
for this simple issue. However, I would not recommend handling exception in catch.如果您想要列列表但又不想要,您还可以在 DataReader 上调用 GetSchemaTable()不想有例外......
You can also call GetSchemaTable() on your DataReader if you want the list of columns and you don't want to have to get an exception...
尽管没有公开公开的方法,但
SqlDataReader
所依赖的内部类System.Data.ProviderBase.FieldNameLookup
中确实存在一个方法。为了访问它并获得本机性能,您必须使用 ILGenerator 在运行时创建一个方法。 以下代码将让您直接访问 System.Data.ProviderBase.FieldNameLookup 类中的 int IndexOf(string fieldName) 并执行该记录SqlDataReader.GetOrdinal() 这样做不会产生副作用。 生成的代码镜像现有的
SqlDataReader.GetOrdinal()
,只不过它调用FieldNameLookup.IndexOf()
而不是FieldNameLookup.GetOrdinal()
。GetOrdinal()
方法调用IndexOf()
函数,并在返回-1
时引发异常,因此我们绕过该行为。Although there is no publicly exposed method, a method does exist in the internal class
System.Data.ProviderBase.FieldNameLookup
whichSqlDataReader
relies on.In order to access it and get native performance, you must use the ILGenerator to create a method at runtime. The following code will give you direct access to
int IndexOf(string fieldName)
in theSystem.Data.ProviderBase.FieldNameLookup
class as well as perform the book keeping thatSqlDataReader.GetOrdinal()
does so that there is no side effect. The generated code mirrors the existingSqlDataReader.GetOrdinal()
except that it callsFieldNameLookup.IndexOf()
instead ofFieldNameLookup.GetOrdinal()
. TheGetOrdinal()
method calls to theIndexOf()
function and throws an exception if-1
is returned, so we bypass that behavior.使用:
在循环中它可能不会那么有效。
Use:
It probably would not be as efficient in a loop.
这对我有用:
This works to me:
使用:
您可以从 获取更多详细信息来自 SqlDataReader 的列名?。
Use:
You can get more details from Can you get the column names from a SqlDataReader?.