SQLDataReader的转换错误

6

我的网站正在使用企业库v5.0,主要是DAAB。一些函数,如executescalar,executedataset的工作符合预期。问题出现在我开始使用读取器时。

我在我的包含类中有这个函数:

Public Function AssignedDepartmentDetail(ByVal Did As Integer) As SqlDataReader
    Dim reader As SqlDataReader
    Dim Command As SqlCommand = db.GetSqlStringCommand("select seomthing from somewhere where something = @did")
    db.AddInParameter(Command, "@did", Data.DbType.Int32, Did)
    reader = db.ExecuteReader(Command)
    reader.Read()
    Return reader
End Function

这是我的aspx.vb文件中的调用方式:

这是从我的aspx.vb文件中调用的:

reader = includes.AssignedDepartmentDetail(Did)
If reader.HasRows Then
    TheModule = reader("templatefilename")
    PageID = reader("id")
Else
    TheModule = "#"
End If

在db.ExecuteReader这一行会出现以下错误:

无法将类型为“Microsoft.Practices.EnterpriseLibrary.Data.RefCountingDataReader”的对象强制转换为类型“System.Data.SqlClient.SqlDataReader”。

是否有人能够解释一下如何让它正常工作?当通过entlib处理读取器时,我是否总是会遇到问题?

6个回答

3
我建议对这个实现谨慎处理。Enterprise Library Codeplex网站上有一个帖子解释了这一背景: http://entlib.codeplex.com/Thread/View.aspx?ThreadId=212973 Chris Tavares解释说,仅仅返回.InnerReader是不好的,因为这会破坏Enterprise Library的连接跟踪(他在5月20日下午5:39的回复): “那种方法会完全破坏你的连接管理。包装器的整个原因是为了我们能够执行额外的代码来在释放时清理东西。获取内部读取器并丢弃外部将泄漏连接!”
所以,是的,处理这个问题有点麻烦,我们处于同样的情况。
敬礼, Mike

1
我已经考虑了ctavars在http://entlib.codeplex.com/discussions/212973http://entlib.codeplex.com/discussions/211288发布的评论和代码,得出了以下通用方法来获取SQL数据读取器。
通常情况下,在using语句中使用IDataReader,然后在可以直接使用该引用时直接使用它。当需要特定于SQL的内容时,请在其上调用AsSqlDataReader
将此扩展类添加到某个位置:
/// <summary>
/// Obtains an <see cref="SqlDataReader"/> from less derived data readers in Enterprise Library
/// </summary>
/// <remarks>
/// See http://entlib.codeplex.com/discussions/212973 and http://entlib.codeplex.com/discussions/211288
/// for a discussion of why this is necessary
/// </remarks>
public static class SqlDataReaderExtension
{
    /// <summary>
    /// Allows the internal <see cref="SqlDataReader"/> of a <see cref="RefCountingDataReader"/> to be accessed safely
    /// </summary>
    /// <remarks>
    /// To ensure correct use, the returned reference must not be retained and used outside the scope of the input
    /// reference. This is so that the reference counting does not get broken. In practice this means calling this method
    /// on the base reader every time a reference to it is required.
    /// </remarks>
    public static SqlDataReader AsSqlDataReader(this RefCountingDataReader reader)
    {
        return (SqlDataReader)(reader.InnerReader);
    }

    /// <summary>
    /// Allows the internal <see cref="SqlDataReader"/> of a <see cref="IDataReader"/> to be accessed safely
    /// </summary>
    /// <remarks>
    /// To ensure correct use, the returned reference must not be retained and used outside the scope of the input
    /// reference. This is so that the reference counting does not get broken. In practice this means calling this method
    /// on the base reader every time a reference to it is required.
    /// </remarks>
    public static SqlDataReader AsSqlDataReader(this IDataReader reader)
    {
        return (SqlDataReader)(((RefCountingDataReader)(reader)).InnerReader);
    }
}

如果要使用SQLReader读取数据,请按照以下步骤操作:

using (IDataReader reader = db.ExecuteReader(command))
{
    while (reader.Read())
    {
        reader.GetInt32(reader.GetOrdinal("SomeColumn")),
        reader.GetInt32(reader.GetOrdinal("SomeOtherColumn")),
        reader.GetInt32(reader.GetOrdinal("SomeFurtherColumn")),
        // Obtain the SQL data reader each time it is used
        // (Note that GetDateTimeOffset is not on the standard IDataReader)
        reader.AsSqlDataReader().GetDateTimeOffset(reader.GetOrdinal("SQLSpecificColumn"))
        reader.AsSqlDataReader().GetDateTimeOffset(reader.GetOrdinal("AnotherSQLSpecificColumn"))
        reader.GetString(reader.GetOrdinal("SomeAdditionalColumn"))
    }
}

1
在源代码中花费了数小时后,我相信这确实是正确的方法。必须强调,如果使用任何System.Transaction(TransactionScope),上面接受的答案将允许内存泄漏。 - mdisibio

1

Enterprise Library 中的 ExecuteReader 将 IDataReader 包装成 RefCountingDataReader,而 SqlDataReader 实现了 IDataReader 接口。

RefCountingDataReader 有 InnerReader 属性,您可以将其转换为 SqlDataReader。下面的示例是使用 C# 编写的,但您可以轻松地将其转换为 VB.NET。

SqlDataReader reader;
reader = ((RefCountingDataReader)db.ExecuteReader(command)).InnerReader as SqlDataReader;
if (reader != null)
    reader.Read();
return reader;

希望能有所帮助


3
不要这样做!这将导致数据库连接泄露! - Chris Tavares

1

我遇到了泄漏连接的问题,因为我的所有DA方法都需要一个SqlDataReader。 现在我必须返回内部的RefCountingDataReader,并且永远不能关闭外部读取器。 旧的Enterprise Library通过返回SqlDataReader正常工作。


实际上,旧的Entlib并没有正常工作,这就是为什么我们进行了更改。问题很微妙,但在使用TransactionScopes时,在多线程应用程序中会导致一些非常恶心、难以追踪的异常情况。 - Chris Tavares
只是为了明确,InnerReader 是 SqlDataReader,而“外部”的 reader 是 RefCountingDataReader... - mdisibio

0
你应该使用接口,而不是具体类。
Public Function AssignedDepartmentDetail(ByVal Did As Integer) As IDataReader
    Dim reader As IDataReader
    Dim Command As SqlCommand = db.GetSqlStringCommand("select seomthing from somewhere where something = @did")
    db.AddInParameter(Command, "@did", Data.DbType.Int32, Did)
    reader = db.ExecuteReader(Command)
    reader.Read()
    Return reader
End Function

还有使用方法。个人而言,我不会在展示层页面中使用数据读取器,但我想每个人都有自己的选择。

Private Const TemplateFileName_Select_Column_Ordinal As Integer = 0
Private Const Id_Select_Column_Ordinal As Integer = 1

Private Sub DoSomething()
dim reader as IDataReader
reader = includes.AssignedDepartmentDetail(Did)
If reader.HasRows Then
    TheModule = reader(TemplateFileName_Select_Column_Ordinal)
    PageID = reader(Id_Select_Column_Ordinal)
Else
    TheModule = "#"

    reader.Close()  ''Dude, close your reader(s)

End If

使用接口是最佳实践和最具提供程序无关性的方法,但有时您需要提供程序特定类的额外功能。 Stephan的答案演示了如何在不破坏DAAB的基础连接管理的情况下完成此操作。 - mdisibio

0

我认为我有一个可行的解决方案。

enter code here

    ' Create the Database object, using the default database service. The
    ' default database service is determined through configuration.
    Dim db As Microsoft.Practices.EnterpriseLibrary.Data.Database = EnterpriseLibraryContainer.Current.GetInstance(Of Microsoft.Practices.EnterpriseLibrary.Data.Database)(DatabaseName)

    Dim dbCommand As DbCommand
    dbCommand = db.GetStoredProcCommand(StoredProcedureName)

    'create a new database connection based on the enterprise library database connection
    Dim dbConnection As System.Data.Common.DbConnection
    dbConnection = db.CreateConnection
    dbConnection.Open()

    'set the dbCommand equal to the open dbConnection
    dbCommand.Connection = dbConnection

    'return a ADO sqlDatareader but still managed by the EnterpriseLibrary
    Return dbCommand.ExecuteReader(CommandBehavior.CloseConnection)

这将返回未包装的IDataReader,如果需要,可以将其转换为特定于提供程序的读取器,例如SqlDataReader。您正确指出这不是RefCountingDataReader,并且不会遇到OP的转换错误。但是,重要的是要指出RefCountingDataReader实现的主要原因之一...(请参见下一个评论)... - mdisibio
如果正在使用System.Transaction(例如,将数据库调用包装在TransactionScope()中),则无论您是否实际修改任何内容,它都会将“dbConnection.Open()”视为第二个事务。然后,使用第二个连接,它将启动MS分布式事务协调器来管理事务。这是一个边角情况,但如果发生,那么仅仅获取一些数据就需要巨大的开销(而且DTC服务可能甚至没有在您的服务器上运行)。 - mdisibio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接