如何检查一个SqlDataReader
对象中是否存在一列?在我的数据访问层中,我创建了一个方法,为多个存储过程调用构建同一个对象。其中一个存储过程有一个其他存储过程没有使用的额外列。我想修改该方法以适应每种情况。
我的应用程序是用C#编写的。
如何检查一个SqlDataReader
对象中是否存在一列?在我的数据访问层中,我创建了一个方法,为多个存储过程调用构建同一个对象。其中一个存储过程有一个其他存储过程没有使用的额外列。我想修改该方法以适应每种情况。
我的应用程序是用C#编写的。
reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
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);
}
这是已接受答案的一个一行代码的LINQ版本:
Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")
public static class Extensions
{
public static bool HasColumn(this SqlDataReader r, string columnName)
{
return !string.IsNullOrEmpty(columnName) && r.FieldCount > 0 && Enumerable.Range(0, r.FieldCount).Select((i) => r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase);
}
}
Public Module Extensions
<Extension()>
Public Function HasColumn(r As SqlDataReader, columnName As String) As Boolean
Return Not String.IsNullOrEmpty(columnName) AndAlso r.FieldCount > 0 AndAlso Enumerable.Range(0, r.FieldCount).Select(Function(i) r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase)
End Function
End Module
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;
}
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
}
return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();
:) - nawfal这是一个相当古老的帖子,但我想提供我的意见。
大多数提出的解决方案的挑战在于,每次检查每个列时,需要枚举所有字段的每一行。
其他人使用的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();
}
}
}
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
不区分大小写。
我的数据访问类需要向后兼容,因此可能会尝试在数据库中还不存在该列的版本中访问列。我们返回一些相当大的数据集,因此我不是很喜欢对于每个属性都必须迭代 DataReader 列集合的扩展方法。
我有一个实用程序类,它创建了一个私有列列表,然后具有一个通用方法,该方法尝试根据列名和输出参数类型解析值。
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;
}
}
try{} catch{}
。然而,我不建议在 catch 中处理异常。try
{
if (string.IsNullOrEmpty(reader["Name"].ToString()))
{
name = reader["Name"].ToString();
}
}
catch
{
//Do nothing
}
我也没能让 GetSchemaTable
正常工作,直到我找到了这种方法。
基本上我是这样做的:
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