22 ответов:
используя
Exceptions для логики управления, как и в некоторых других ответах, считается плохой практикой и имеет затраты на производительность.цикл по полям может иметь небольшой хит производительности, если вы используете его много, и вы можете рассмотреть возможность кэширования результатов
более подходящий способ сделать это:
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)один вызов-без исключений. Это может вызвать исключения внутри, но я так не думаю.
примечание: в комментариях ниже, мы это поняли... правильный код на самом деле это:
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; }
Я думаю, что лучше всего позвонить GetOrdinal ("columnName") на вашем DataReader спереди, и поймать IndexOutOfRangeException в случае, если столбец не присутствует.
В самом деле, давайте сделаем метод расширения:
public static bool HasColumn(this IDataRecord r, string columnName) { try { return r.GetOrdinal(columnName) >= 0; } catch (IndexOutOfRangeException) { return false; } }Edit
хорошо, в последнее время этот пост начинает набирать несколько голосов вниз, и я не могу удалить его, потому что это принятый ответ, поэтому я собираюсь обновить его и (надеюсь) попытаться оправдать использование обработка исключений как поток управления.
другой способ достижения этого, как написал Чад Грант, в цикле через каждое поле в DataReader и сделать сравнение без учета регистра для имени поля, которое вы ищете. Это будет работать очень хорошо, и честно говоря, вероятно, будет работать лучше, чем мой метод выше. Конечно, я бы никогда не использовал метод выше внутри цикла, где performace была проблемой.
Я могу придумать ситуацию, в которой попробовать/метод GetOrdinal/catch будет работать там, где петли не. Это, однако, совершенно гипотетическую ситуацию сейчас, так что это очень неубедительное оправдание. Как бы то ни было, потерпите меня и посмотрите, что вы думаете.
представьте себе базу данных, которая позволила вам "псевдоним" столбцов в таблице. Представьте, что я могу определить таблицу со столбцом с именем "EmployeeName", но также дать ему псевдоним "EmpName", и выполнение выбора для любого имени вернет данные в этом столбце. Со мной так далеко?
Теперь представьте, что есть ADO.NET поставщик для этой базы данных, и они закодировали реализацию IDataReader для нее, которая учитывает псевдонимы столбцов.
Теперь
dr.GetName(i)(как используется в ответе чада) может возвращать только одну строку, поэтому она должна возвращать только один из "псевдонимов" на столбе. Однако,GetOrdinal("EmpName")можно использовать внутреннюю реализацию полей этого поставщика, чтобы проверить псевдоним каждого столбца для имени, которое вы ищете для.в этой гипотетической ситуации "aliased columns" метод try/GetOrdinal/catch будет единственным способом убедиться, что вы проверяете каждую вариацию имени столбца в наборе результатов.
хлипкие? Конечно. Но стоит подумать. Честно говоря, я бы предпочел "официальный" метод HasColumn на IDataRecord.
в одной строке, используйте это после извлечения DataReader:
var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray();затем,
if (fieldNames.Contains("myField")) { var myFieldValue = dr["myField"]; ...Edit
гораздо более эффективный однострочный, который не требует загрузки схемы:
var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));
вот рабочий образец для идеи Жасмин:
var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select (row => row["ColumnName"] as string).ToList(); if (cols.Contains("the column name")) { }
это работает для меня:
bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
следующее просто и работает для меня:
bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);
если вы читали вопрос, Майкл спросил о DataReader, а не о людях DataRecord. Получите ваши объекты правильно.
С помощью
r.GetSchemaTable().Columns.Contains(field)на DataRecord работает, но он возвращает столбцы BS (см. скриншот ниже.)чтобы узнать, существует ли столбец данных и содержит ли он данные в 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:
Я написал для пользователей 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
вот решение от жасмина в одну строку... (еще один очень простой!):
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); }
этот код исправляет проблемы, которые Левитикон имел с их кодом: (адаптировано из: [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работать, пока не найду таким образом.в основном я делаю это:
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без учета регистра кстати.
чтобы ваш код был надежным и чистым, используйте одну функцию расширения, например:
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
вы также можете позвонить GetSchemaTable () на вашем DataReader, если вы хотите список столбцов, и вы не хотите, чтобы получить исключение...
эти ответы уже опубликованы здесь. Просто Линк-Инг немного:
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), будет лучше и быстрее проверять reader. Свойство FieldCount позволяет различать их.
const int NormalColCount=..... if(reader.FieldCount > NormalColCount) { // Do something special }Я знаю, это старый пост, но я решил ответить, чтобы помочь другим в такой же ситуации. вы также можете (по соображениям производительности) смешать это решение с решением итерации решения.
мой класс доступа к данным должен быть обратно совместим, поэтому я могу пытаться получить доступ к столбцу в выпуске, где он еще не существует в базе данных. У нас есть несколько довольно больших наборов данных, которые возвращаются, поэтому я не большой поклонник метода расширения, который должен повторять коллекцию столбцов 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; } }
Хотя нет открытого метода, метод существует во внутреннем классе
System.Data.ProviderBase.FieldNameLookup, которыйSqlDataReaderполагается на.чтобы получить доступ к нему и получить собственную производительность, необходимо использовать ILGenerator для создания метода во время выполнения. Следующий код даст вам прямой доступ к
int IndexOf(string fieldName)наSystem.Data.ProviderBase.FieldNameLookupкласс, а также выполнять Бухгалтерский учет, чтоSqlDataReader.GetOrdinal()так что нет побочного эффекта. Сгенерированный код отражает существующийSqlDataReader.GetOrdinal()кроме того, что это звонитFieldNameLookup.IndexOf()вместоFieldNameLookup.GetOrdinal(). ЭлементGetOrdinal()вызов методаIndexOf()функция и выдает исключение, если-1возвращается, поэтому мы обходим это поведение.using System; using System.Data; using System.Data.SqlClient; using System.Reflection; using System.Reflection.Emit; public static class SqlDataReaderExtensions { private delegate int IndexOfDelegate(SqlDataReader reader, string name); private static IndexOfDelegate IndexOf; public static int GetColumnIndex(this SqlDataReader reader, string name) { return name == null ? -1 : IndexOf(reader, name); } public static bool ContainsColumn(this SqlDataReader reader, string name) { return name != null && IndexOf(reader, name) >= 0; } static SqlDataReaderExtensions() { Type typeSqlDataReader = typeof(SqlDataReader); Type typeSqlStatistics = typeSqlDataReader.Assembly.GetType("System.Data.SqlClient.SqlStatistics", true); Type typeFieldNameLookup = typeSqlDataReader.Assembly.GetType("System.Data.ProviderBase.FieldNameLookup", true); BindingFlags staticflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Static; BindingFlags instflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance; DynamicMethod dynmethod = new DynamicMethod("SqlDataReader_IndexOf", typeof(int), new Type[2]{ typeSqlDataReader, typeof(string) }, true); ILGenerator gen = dynmethod.GetILGenerator(); gen.DeclareLocal(typeSqlStatistics); gen.DeclareLocal(typeof(int)); // SqlStatistics statistics = (SqlStatistics) null; gen.Emit(OpCodes.Ldnull); gen.Emit(OpCodes.Stloc_0); // try { gen.BeginExceptionBlock(); // statistics = SqlStatistics.StartTimer(this.Statistics); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Call, typeSqlDataReader.GetProperty("Statistics", instflags | BindingFlags.GetProperty, null, typeSqlStatistics, Type.EmptyTypes, null).GetMethod); gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StartTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null)); gen.Emit(OpCodes.Stloc_0); //statistics // if(this._fieldNameLookup == null) { Label branchTarget = gen.DefineLabel(); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField)); gen.Emit(OpCodes.Brtrue_S, branchTarget); // this.CheckMetaDataIsReady(); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Call, typeSqlDataReader.GetMethod("CheckMetaDataIsReady", instflags | BindingFlags.InvokeMethod, null, Type.EmptyTypes, null)); // this._fieldNameLookup = new FieldNameLookup((IDataRecord)this, this._defaultLCID); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_defaultLCID", instflags | BindingFlags.GetField)); gen.Emit(OpCodes.Newobj, typeFieldNameLookup.GetConstructor(instflags, null, new Type[] { typeof(IDataReader), typeof(int) }, null)); gen.Emit(OpCodes.Stfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.SetField)); // } gen.MarkLabel(branchTarget); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField)); gen.Emit(OpCodes.Ldarg_1); //name gen.Emit(OpCodes.Call, typeFieldNameLookup.GetMethod("IndexOf", instflags | BindingFlags.InvokeMethod, null, new Type[] { typeof(string) }, null)); gen.Emit(OpCodes.Stloc_1); //int output Label leaveProtectedRegion = gen.DefineLabel(); gen.Emit(OpCodes.Leave_S, leaveProtectedRegion); // } finally { gen.BeginFaultBlock(); // SqlStatistics.StopTimer(statistics); gen.Emit(OpCodes.Ldloc_0); //statistics gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StopTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null)); // } gen.EndExceptionBlock(); gen.MarkLabel(leaveProtectedRegion); gen.Emit(OpCodes.Ldloc_1); gen.Emit(OpCodes.Ret); IndexOf = (IndexOfDelegate)dynmethod.CreateDelegate(typeof(IndexOfDelegate)); } }
как о
if (dr.GetSchemaTable().Columns.Contains("accounttype")) do something else do somethingвероятно, это было бы не так эффективно в цикле

Comments