我如何检查看看一个列是否存在于一个SqlDataReader对象?在我的数据访问层,我创建了一个为多个存储过程调用构建相同对象的方法。其中一个存储过程具有其他存储过程不使用的附加列。我想修改方法以适应各种情况。
我的应用程序是用c#编写的。
我如何检查看看一个列是否存在于一个SqlDataReader对象?在我的数据访问层,我创建了一个为多个存储过程调用构建相同对象的方法。其中一个存储过程具有其他存储过程不使用的附加列。我想修改方法以适应各种情况。
我的应用程序是用c#编写的。
当前回答
对于这个简单的问题,我建议使用try{} catch{}。但是,我不建议在catch中处理异常。
try
{
if (string.IsNullOrEmpty(reader["Name"].ToString()))
{
name = reader["Name"].ToString();
}
}
catch
{
//Do nothing
}
其他回答
这是一个相当老的帖子,但我想提供我的意见。
大多数提议的解决方案的挑战在于,它要求您每次都为检查的每一行和每一列枚举所有字段。
其他的则使用GetSchemaTable方法,该方法不受全局支持。
就我个人而言,我对抛出和捕获异常以检查字段是否存在没有问题。事实上,我认为从编程的角度来看,这可能是最直接的解决方案,也是最容易调试和创建扩展的解决方案。我注意到吞咽异常不会对性能造成负面影响,除非涉及到其他事务或奇怪的回滚逻辑。
使用try-catch块实现
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
public class MyModel {
public int ID { get; set; }
public int UnknownColumn { get; set; }
}
public IEnumerable<MyModel> ReadData(SqlCommand command) {
using (SqlDataReader reader = command.ExecuteReader()) {
try {
while (reader.Read()) {
// init the row
MyModel row = new MyModel();
// bind the fields
row.ID = reader.IfDBNull("ID", row.ID);
row.UnknownColumn = reader.IfDBNull("UnknownColumn", row.UnknownColumn);
// return the row and move forward
yield return row;
}
} finally {
// technically the disposer should handle this for you
if (!reader.IsClosed) reader.Close();
}
}
}
// I use a variant of this class everywhere I go to help simplify data binding
public static class IDataReaderExtensions {
// clearly separate name to ensure I don't accidentally use the wrong method
public static T IfDBNull<T>(this IDataReader reader, string name, T defaultValue) {
T value;
try {
// attempt to read the value
// will throw IndexOutOfRangeException if not available
object objValue = reader[name];
// the value returned from SQL is NULL
if (Convert.IsDBNull(objValue)) {
// use the default value
objValue = defaultValue;
}
else if (typeof(T) == typeof(char)) {
// chars are returned from SQL as strings
string strValue = Convert.ToString(objValue);
if (strValue.Length > 0) objValue = strValue[0];
else objValue = defaultValue;
}
value = (T)objValue;
} catch (IndexOutOfRangeException) {
// field does not exist
value = @defaultValue;
} catch (InvalidCastException, ex) {
// The type we are attempting to bind to is not the same as the type returned from the database
// Personally, I want to know the field name that has the problem
throw new InvalidCastException(name, ex);
}
return value;
}
// clearly separate name to ensure I don't accidentally use the wrong method
// just overloads the other method so I don't need to pass in a default
public static T IfDBNull<T>(this IDataReader reader, string name) {
return IfDBNull<T>(reader, name, default(T));
}
}
如果您想避免异常处理,我建议在初始化阅读器时将结果保存到HashSet<string>中,然后再检查它以查找所需的列。或者,为了进行微优化,您可以将列实现为Dictionary<string, int>,以防止SqlDataReader对象从Name到ordinal的重复解析。
使用HashSet<string>实现
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
public class MyModel {
public int ID { get; set; }
public int UnknownColumn { get; set; }
}
public IEnumerable<MyModel> ReadData(SqlCommand command) {
using (SqlDataReader reader = command.ExecuteReader()) {
try {
// first read
if (reader.Read()) {
// use whatever *IgnoreCase comparer that you're comfortable with
HashSet<string> columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// init the columns HashSet<string, int>
for (int i = 0; i < reader.FieldCount; i++) {
string fieldName = reader.GetName(i);
columns.Add(fieldName);
}
// implemented as a do/while since we already read the first row
do {
// init a new instance of your class
MyModel row = new MyModel();
// check if column exists
if (columns.Contains("ID") &&
// ensure the value is not DBNull
!Convert.IsDBNull(reader["ID"])) {
// bind value
row.ID = (int)reader["ID"];
}
// check if column exists
if (columns.Contains("UnknownColumn") &&
// ensure the value is not DBNull
!Convert.IsDBNull(reader["UnknownColumn"])) {
// bind value
row.UnknownColumn = (int)reader["UnknownColumn"];
}
// return the row and move forward
yield return row;
} while (reader.Read());
}
} finally {
// technically the disposer should handle this for you
if (!reader.IsClosed) reader.Close();
}
}
}
使用Dictionary<string, int>实现
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
public class MyModel {
public int ID { get; set; }
public int UnknownColumn { get; set; }
}
public IEnumerable<MyModel> ReadData(SqlCommand command) {
using (SqlDataReader reader = command.ExecuteReader()) {
try {
// first read
if (reader.Read()) {
// use whatever *IgnoreCase comparer that you're comfortable with
Dictionary<string, int> columns = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
// init the columns Dictionary<string, int>
for (int i = 0; i < reader.FieldCount; i++) {
string fieldName = reader.GetName(i);
columns[fieldName] = i;
}
// implemented as a do/while since we already read the first row
do {
// init a new instance of your class
MyModel row = new MyModel();
// stores the resolved ordinal from your dictionary
int ordinal;
// check if column exists
if (columns.TryGetValue("ID", out ordinal) &&
// ensure the value is not DBNull
!Convert.IsDBNull(reader[ordinal])) {
// bind value
row.ID = (int)reader[ordinal];
}
// check if column exists
if (columns.TryGetValue("UnknownColumn", out ordinal) &&
// ensure the value is not DBNull
!Convert.IsDBNull(reader[ordinal])) {
// bind value
row.UnknownColumn = (int)reader[ordinal];
}
// return the row and move forward
yield return row;
} while (reader.Read());
}
} finally {
// technically the disposer should handle this for you
if (!reader.IsClosed) reader.Close();
}
}
}
这段代码纠正了Levitikon在他们的代码中遇到的问题: (改编自:[1]:http://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx)
public List<string> GetColumnNames(SqlDataReader r)
{
List<string> ColumnNames = new List<string>();
DataTable schemaTable = r.GetSchemaTable();
DataRow row = schemaTable.Rows[0];
foreach (DataColumn col in schemaTable.Columns)
{
if (col.ColumnName == "ColumnName")
{
ColumnNames.Add(row[col.Ordinal].ToString());
break;
}
}
return ColumnNames;
}
获取所有那些无用的列名,而不是从表中获取列名的原因是…… 是因为您正在获取模式列的名称(即schema表的列名)
注意:这似乎只返回第一列的名称…
EDIT:返回所有列名称的修正代码,但不能使用SqlDataReader来完成
public List<string> ExecuteColumnNamesReader(string command, List<SqlParameter> Params)
{
List<string> ColumnNames = new List<string>();
SqlDataAdapter da = new SqlDataAdapter();
string connection = ""; // your sql connection string
SqlCommand sqlComm = new SqlCommand(command, connection);
foreach (SqlParameter p in Params) { sqlComm.Parameters.Add(p); }
da.SelectCommand = sqlComm;
DataTable dt = new DataTable();
da.Fill(dt);
DataRow row = dt.Rows[0];
for (int ordinal = 0; ordinal < dt.Columns.Count; ordinal++)
{
string column_name = dt.Columns[ordinal].ColumnName;
ColumnNames.Add(column_name);
}
return ColumnNames; // you can then call .Contains("name") on the returned collection
}
public static bool DataViewColumnExists(DataView dv, string columnName)
{
return DataTableColumnExists(dv.Table, columnName);
}
public static bool DataTableColumnExists(DataTable dt, string columnName)
{
string DebugTrace = "Utils::DataTableColumnExists(" + dt.ToString() + ")";
try
{
return dt.Columns.Contains(columnName);
}
catch (Exception ex)
{
throw new MyExceptionHandler(ex, DebugTrace);
}
}
列。Contains不区分大小写。
Use:
if (dr.GetSchemaTable().Columns.Contains("accounttype"))
do something
else
do something
在循环中它可能没有那么有效。
我为Visual Basic用户写了这个:
Protected Function HasColumnAndValue(ByRef reader As IDataReader, ByVal columnName As String) As Boolean
For i As Integer = 0 To reader.FieldCount - 1
If reader.GetName(i).Equals(columnName) Then
Return Not IsDBNull(reader(columnName))
End If
Next
Return False
End Function
我认为这个更强大,用法是:
If HasColumnAndValue(reader, "ID_USER") Then
Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If