Mic*_*ern 204 .net c# sqldatareader
如何检查SqlDataReader对象中是否存在列?在我的数据访问层中,我创建了一个方法,为多个存储过程调用构建相同的对象.其中一个存储过程具有另一个列,其他存储过程不使用该列.我想修改方法以适应每个场景.
我的应用程序是用C#编写的.
Cha*_*ant 322
Exception在某些其他答案中使用s作为控制逻辑被认为是不好的做法并且具有性能成本.
如果你经常使用它,那么在字段中循环会有很小的性能损失,你可能想要考虑缓存结果
更合适的方法是:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
Jas*_*ine 64
使用这个布尔函数要好得多:
r.GetSchemaTable().Columns.Contains(field)
Run Code Online (Sandbox Code Playgroud)
一个电话 - 没有例外.它可能会在内部抛出异常,但我不这么认为.
注意:在下面的评论中,我们想出了......正确的代码实际上是这样的:
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;
}
Run Code Online (Sandbox Code Playgroud)
Mat*_*ton 32
我认为你最好的选择是在你的DataReader上预先调用GetOrdinal("columnName"),并在列不存在时捕获IndexOutOfRangeException.
实际上,让我们做一个扩展方法:
public static bool HasColumn(this IDataRecord r, string columnName)
{
try
{
return r.GetOrdinal(columnName) >= 0;
}
catch (IndexOutOfRangeException)
{
return false;
}
}
Run Code Online (Sandbox Code Playgroud)
编辑
好吧,这篇文章最近开始收集一些下注,我不能删除它,因为它是公认的答案,所以我要更新它,并且(我希望)试图证明使用异常处理是合理的.控制流.
Chad Grant发布的另一种实现方法是循环访问DataReader中的每个字段,并对您要查找的字段名称进行不区分大小写的比较.这将非常有效,并且真实地可能比我上面的方法表现得更好.当然,我绝不会在性能问题的循环中使用上面的方法.
我可以想到一种情况,其中try/GetOrdinal/catch方法将在循环不起作用的情况下工作.然而,这是一个完全假设的情况,所以这是一个非常脆弱的理由.无论如何,忍受我,看看你的想法.
想象一个允许您在表中"别名"列的数据库.想象一下,我可以使用名为"EmployeeName"的列定义一个表,但也给它一个别名"EmpName",并且对任一名称执行select会返回该列中的数据.和我一起到目前为止?
现在想象一下,该数据库有一个ADO.NET提供程序,并且它们为它编写了一个IDataReader实现,它将列别名考虑在内.
现在,dr.GetName(i)(在乍得的答案使用)只能返回一个字符串,所以它必须只返回一个列上的"别名"的.但是,GetOrdinal("EmpName")可以使用此提供程序字段的内部实现来检查每个列的别名,以查找您要查找的名称.
在这个假设的"别名列"情况下,try/GetOrdinal/catch方法将是确保您在结果集中检查列名称的每个变体的唯一方法.
劣质的?当然.但值得一想.老实说,我更倾向于IDataRecord上的"官方"HasColumn方法.
Lar*_*rry 27
在一行中,在DataReader检索后使用它:
var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray();
Run Code Online (Sandbox Code Playgroud)
然后,
if (fieldNames.Contains("myField"))
{
var myFieldValue = dr["myField"];
...
Run Code Online (Sandbox Code Playgroud)
编辑
更高效的单行程,不需要加载模式:
var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));
Run Code Online (Sandbox Code Playgroud)
小智 18
以下是Jasmin的想法的工作示例:
var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select
(row => row["ColumnName"] as string).ToList();
if (cols.Contains("the column name"))
{
}
Run Code Online (Sandbox Code Playgroud)
小智 12
这对我有用:
bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
Run Code Online (Sandbox Code Playgroud)
小智 10
以下内容很简单,对我有用:
bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);
Run Code Online (Sandbox Code Playgroud)
如果您阅读了这个问题,Michael会询问DataReader,而不是DataRecord的人.让你的对象正确.
使用r.GetSchemaTable().Columns.Contains(field)on 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
public static bool CanCreate(SqlDataReader dataReader)
{
return dataReader.ContainsColumn("RoleTemplateId")
&& !dataReader.IsDBNull("RoleTemplateId");
}
Run Code Online (Sandbox Code Playgroud)
调用r.GetSchemaTable().ColumnsDataReader返回BS列:

小智 7
我为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
Run Code Online (Sandbox Code Playgroud)
我认为这更强大,用法是:
If HasColumnAndValue(reader, "ID_USER") Then
Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If
Run Code Online (Sandbox Code Playgroud)
域名注册地址:
有很多关于性能和不良做法的说法的答案,所以我在这里澄清一下。
返回的列数越多,异常路由越快,列数越少,循环路由越快,交叉点大约为 11 列。滚动到底部以查看图表和测试代码。
完整答案:
一些顶级答案的代码有效,但基于对逻辑中异常处理及其相关性能的接受程度,此处存在关于“更好”答案的潜在争论。
为了清除这一点,我认为没有太多关于捕获异常的指导。Microsoft 确实有一些关于抛出异常的指导。他们确实在那里声明:
如果可能,不要在正常的控制流中使用异常。
第一个注释是“如果可能”的宽大处理。更重要的是,描述给出了这个上下文:
框架设计者应该设计 API 以便用户可以编写不会抛出异常的代码
这意味着,如果您正在编写可能被其他人使用的 API,请让他们能够在没有 try/catch 的情况下导航异常。例如,为抛出异常的 Parse 方法提供 TryParse。 尽管这并没有说明您不应该捕获异常。
此外,正如另一位用户指出的那样,捕获一直允许按类型过滤,并且最近允许通过when 子句进一步过滤。如果我们不应该使用它们,这似乎是对语言功能的浪费。
可以说抛出异常是有代价的,这个代价可能会影响重循环中的性能。但是,也可以说在“连接的应用程序”中,异常的成本可以忽略不计。十多年前调查了实际成本:C# 中的异常有多昂贵?
换句话说,连接和查询数据库的成本可能比抛出异常的成本相形见绌。
除此之外,我想确定哪种方法真正更快。正如预期的那样,没有具体的答案。
随着列数的增加,任何在列上循环的代码都会变慢。也可以说,任何依赖于异常的代码都会根据查询失败的速度变慢。
根据Chad Grant和Matt Hamilton的答案,我运行了两种方法,最多 20 列,错误率高达 50%(OP 表示他在不同的存储过程之间使用这两个测试,所以我假设只有两个)。
以下是使用LINQPad绘制的结果:
此处的锯齿形是每个列计数内的故障率(未找到列)。
在较窄的结果集上,循环是一个不错的选择。但是,GetOrdinal/Exception 方法对列数几乎没有那么敏感,并且在 11 列左右开始优于循环方法。
也就是说,我真的没有偏好性能明智,因为 11 列听起来合理,因为整个应用程序返回的平均列数。在任何一种情况下,我们都在这里讨论几分之一毫秒。
但是,从代码简单性和别名支持方面来看,我可能会选择 GetOrdinal 路线。
这是 LINQPad 形式的测试。随意使用您自己的方法重新发布:
void Main()
{
var loopResults = new List<Results>();
var exceptionResults = new List<Results>();
var totalRuns = 10000;
for (var colCount = 1; colCount < 20; colCount++)
{
using (var conn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDb;Initial Catalog=master;Integrated Security=True;"))
{
conn.Open();
//create a dummy table where we can control the total columns
var columns = String.Join(",",
(new int[colCount]).Select((item, i) => $"'{i}' as col{i}")
);
var sql = $"select {columns} into #dummyTable";
var cmd = new SqlCommand(sql,conn);
cmd.ExecuteNonQuery();
var cmd2 = new SqlCommand("select * from #dummyTable", conn);
var reader = cmd2.ExecuteReader();
reader.Read();
Func<Func<IDataRecord, String, Boolean>, List<Results>> test = funcToTest =>
{
var results = new List<Results>();
Random r = new Random();
for (var faultRate = 0.1; faultRate <= 0.5; faultRate += 0.1)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var faultCount=0;
for (var testRun = 0; testRun < totalRuns; testRun++)
{
if (r.NextDouble() <= faultRate)
{
faultCount++;
if(funcToTest(reader, "colDNE"))
throw new ApplicationException("Should have thrown false");
}
else
{
for (var col = 0; col < colCount; col++)
{
if(!funcToTest(reader, $"col{col}"))
throw new ApplicationException("Should have thrown true");
}
}
}
stopwatch.Stop();
results.Add(new UserQuery.Results{
ColumnCount = colCount,
TargetNotFoundRate = faultRate,
NotFoundRate = faultCount * 1.0f / totalRuns,
TotalTime=stopwatch.Elapsed
});
}
return results;
};
loopResults.AddRange(test(HasColumnLoop));
exceptionResults.AddRange(test(HasColumnException));
}
}
"Loop".Dump();
loopResults.Dump();
"Exception".Dump();
exceptionResults.Dump();
var combinedResults = loopResults.Join(exceptionResults,l => l.ResultKey, e=> e.ResultKey, (l, e) => new{ResultKey = l.ResultKey, LoopResult=l.TotalTime, ExceptionResult=e.TotalTime});
combinedResults.Dump();
combinedResults
.Chart(r => r.ResultKey, r => r.LoopResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
.AddYSeries(r => r.ExceptionResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
.Dump();
}
public static bool HasColumnLoop(IDataRecord dr, string columnName)
{
for (int i = 0; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
public static bool HasColumnException(IDataRecord r, string columnName)
{
try
{
return r.GetOrdinal(columnName) >= 0;
}
catch (IndexOutOfRangeException)
{
return false;
}
}
public class Results
{
public double NotFoundRate { get; set; }
public double TargetNotFoundRate { get; set; }
public int ColumnCount { get; set; }
public double ResultKey {get => ColumnCount + TargetNotFoundRate;}
public TimeSpan TotalTime { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
143928 次 |
| 最近记录: |