检查SqlDataReader对象中的列名称
如何检查SqlDataReader
对象中是否存在列? 在我的数据访问层中,我创build了一个为多个存储过程调用构build相同对象的方法。 其中一个存储过程具有其他存储过程不使用的附加列。 我想修改方法来适应每种情况。
我的应用程序是用C#编写的。
在接受的答案中,对控制逻辑使用exception被认为是不好的做法,并且具有性能成本。
如果你使用它很多,你可能想要考虑caching结果
更合适的方法是:
public static class DataRecordExtensions { public static bool HasColumn(this IDataRecord dr, string columnName) { for (int i=0; i < dr.FieldCount; i++) { if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase)) return true; } return false; } }
使用这个布尔函数要好得多:
r.GetSchemaTable().Columns.Contains(field)
一个电话 – 没有例外。 它可能会在内部抛出exception,但我不这么认为。
注意:在下面的评论中,我们知道了这个…正确的代码实际上是这样的:
public static bool HasColumn(DbDataReader Reader, string ColumnName) { foreach (DataRow row in Reader.GetSchemaTable().Rows) { if (row["ColumnName"].ToString() == ColumnName) return true; } //Still here? Column not found. return false; }
我认为你最好的select是在你的DataReader前面调用GetOrdinal(“columnName”) ,并在列不存在的情况下捕获IndexOutOfRangeException。
实际上,我们来做一个扩展方法:
public static bool HasColumn(this IDataRecord r, string columnName) { try { return r.GetOrdinal(columnName) >= 0; } catch (IndexOutOfRangeException) { return false; } }
编辑
好的,这篇文章最近开始得到一些反对意见,我不能删除它,因为这是被接受的答案,所以我要更新它,并且(我希望)试图certificate使用exception处理是正确的控制stream程。
实现这一目标的其他方式(如Chad Grant发布的)是循环访问DataReader中的每个字段,并对要查找的字段名称进行不区分大小写的比较。 这将工作得很好,真实地可能会比我上面的方法performance更好。 当然,我永远不会在循环中使用上面的方法。
我可以想到try / GetOrdinal / catch方法在循环不工作的情况。 然而,这是一个完全假设的情况,所以这是一个非常脆弱的理由。 不pipe怎么样,忍耐着我,看看你的想法。
设想一个数据库,允许你在表中“别名”列。 想象一下,我可以定义一个名为“EmployeeName”的列的表,但也给它一个“EmpName”的别名,做一个任何名称的select将返回该列中的数据。 跟我到目前为止?
现在想象一下,该数据库有一个ADO.NET提供程序,并且已经为它编写了一个IDataReader实现,它将列别名考虑在内。
现在, dr.GetName(i)
(在Chad的答案中使用)只能返回一个string,所以它只能返回列上的一个“别名”。 但是, GetOrdinal("EmpName")
可以使用此提供程序的字段的内部实现来检查每个列的别名,以查找您要查找的名称。
在这个假设的“别名列”的情况下,try / GetOrdinal / catch方法将是确保您正在检查结果集中列名的每个变体的唯一方法。
劣质的? 当然。 但值得一想。 老实说,我宁愿在IDataRecord上的“官方”HasColumn方法。
这里是一个茉莉的想法的工作示例:
var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select (row => row["ColumnName"] as string).ToList(); if (cols.Contains("the column name")) { }
在一行中,在你的DataReader检索之后使用它:
var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray();
然后,
if (fieldNames.Contains("myField")) { var myFieldValue = dr["myField"]; ...
这对我有用:
bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
如果你读到这个问题,Michael问DataReader,而不是DataRecord人。 让你的对象正确。
使用r.GetSchemaTable().Columns.Contains(field)
DataRecord上的r.GetSchemaTable().Columns.Contains(field)
可以工作,但是它会返回BS列(见下面的截图)。
要查看数据列是否存在AND包含DataReader中的数据,请使用以下扩展名:
public static class DataReaderExtensions { /// <summary> /// Checks if a column's value is DBNull /// </summary> /// <param name="dataReader">The data reader</param> /// <param name="columnName">The column name</param> /// <returns>A bool indicating if the column's value is DBNull</returns> public static bool IsDBNull(this IDataReader dataReader, string columnName) { return dataReader[columnName] == DBNull.Value; } /// <summary> /// Checks if a column exists in a data reader /// </summary> /// <param name="dataReader">The data reader</param> /// <param name="columnName">The column name</param> /// <returns>A bool indicating the column exists</returns> public static bool ContainsColumn(this IDataReader dataReader, string columnName) { /// See: http://stackoverflow.com/questions/373230/check-for-column-name-in-a-sqldatareader-object/7248381#7248381 try { return dataReader.GetOrdinal(columnName) >= 0; } catch (IndexOutOfRangeException) { return false; } } }
用法:
public static bool CanCreate(SqlDataReader dataReader) { return dataReader.ContainsColumn("RoleTemplateId") && !dataReader.IsDBNull("RoleTemplateId"); }
调用r.GetSchemaTable().Columns
DataReader上的列返回BS列:
以下是简单的,为我工作:
bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);
我为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
这里从茉莉花的解决scheme在一行…(又一个,简单!):
reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
这里是一个被接受的答案的单行linq版本:
Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")
Hashtable ht = new Hashtable(); Hashtable CreateColumnHash(SqlDataReader dr) { ht = new Hashtable(); for (int i = 0; i < dr.FieldCount; i++) { ht.Add(dr.GetName(i), dr.GetName(i)); } return ht; } bool ValidateColumn(string ColumnName) { return ht.Contains(ColumnName); }
这段代码纠正了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; }
获取所有这些无用的列名的原因,而不是你的表中的列的名称…是因为你正在获取架构列的名称(即架构表的列名称)
注意:这似乎只返回第一列的名称…
编辑:更正的代码,返回所有列的名称,但不能使用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 }
我也没有得到GetSchemaTable
的工作,直到我find这种方式 。
基本上我是这样做的:
Dim myView As DataView = dr.GetSchemaTable().DefaultView myView.RowFilter = "ColumnName = 'ColumnToBeChecked'" If myView.Count > 0 AndAlso dr.GetOrdinal("ColumnToBeChecked") <> -1 Then obj.ColumnToBeChecked = ColumnFromDb(dr, "ColumnToBeChecked") End If
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); } }
Columns.Contains
是不区分大小写的btw。
为了保持代码健壮和干净,请使用一个扩展函数,如下所示:
Public Module Extensions <Extension()> Public Function HasColumn(r As SqlDataReader, columnName As String) As Boolean Return If(String.IsNullOrEmpty(columnName) OrElse r.FieldCount = 0, False, Enumerable.Range(0, r.FieldCount).Select(Function(i) r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase)) End Function End Module
你也可以调用你的DataReader的GetSchemaTable()如果你想列的列表,你不想得到一个例外…
这些答案已经发布在这里。 只需要一点点:
bool b = reader.GetSchemaTable().Rows .Cast<DataRow>() .Select(x => (string)x["ColumnName"]) .Contains(colName, StringComparer.OrdinalIgnoreCase); //or bool b = Enumerable.Range(0, reader.FieldCount) .Select(reader.GetName) .Contains(colName, StringComparer.OrdinalIgnoreCase);
第二个是更干净,更快。 即使你在第一种方法中每次都不运行GetSchemaTable
,查找速度也会很慢。
在你的特定情况下(所有的程序都有相同的列,除了1有额外的1列),检查阅读器会更好更快。 FieldCount属性来区分它们。
const int NormalColCount=..... if(reader.FieldCount > NormalColCount) { // Do something special }
我知道这是一个旧的职位,但我决定在相同的情况下帮助其他人。 您也可以(出于性能原因)将此解决scheme与解决scheme迭代解决scheme混合使用。
我的数据访问类需要向后兼容,所以我可能会尝试访问数据库中不存在的版本中的列。 我们有一些相当大的数据集被返回,所以我不是一个扩展方法的粉丝,它必须迭代每个属性的DataReader列集合。
我有一个实用程序类创build列的私人列表,然后有一个通用的方法,试图根据列名称和输出参数types来parsing一个值。
private List<string> _lstString; public void GetValueByParameter<T>(IDataReader dr, string parameterName, out T returnValue) { returnValue = default(T); if (!_lstString.Contains(parameterName)) { Logger.Instance.LogVerbose(this, "missing parameter: " + parameterName); return; } try { if (dr[parameterName] != null && [parameterName] != DBNull.Value) returnValue = (T)dr[parameterName]; } catch (Exception ex) { Logger.Instance.LogException(this, ex); } } /// <summary> /// Reset the global list of columns to reflect the fields in the IDataReader /// </summary> /// <param name="dr">The IDataReader being acted upon</param> /// <param name="NextResult">Advances IDataReader to next result</param> public void ResetSchemaTable(IDataReader dr, bool nextResult) { if (nextResult) dr.NextResult(); _lstString = new List<string>(); using (DataTable dataTableSchema = dr.GetSchemaTable()) { if (dataTableSchema != null) { foreach (DataRow row in dataTableSchema.Rows) { _lstString.Add(row[dataTableSchema.Columns["ColumnName"]].ToString()); } } } }
然后我可以像这样调用我的代码
using (var dr = ExecuteReader(databaseCommand)) { int? outInt; string outString; Utility.ResetSchemaTable(dr, false); while (dr.Read()) { Utility.GetValueByParameter(dr, "SomeColumn", out outInt); if (outInt.HasValue) myIntField = outInt.Value; } Utility.ResetSchemaTable(dr, true); while (dr.Read()) { Utility.GetValueByParameter(dr, "AnotherColumn", out outString); if (!string.IsNullOrEmpty(outString)) myIntField = outString; } }
怎么样
if (dr.GetSchemaTable().Columns.Contains("accounttype")) do something else do something
这可能不会像循环一样高效