2010-05-20 10 views
5

Mi sitio está utilizando enterprise library v 5.0. Principalmente el DAAB. Algunas funciones como executescalar, executedataset funcionan como se esperaba. Los problemas aparecen cuando comienzo a utilizar lectoresError de conversión en SQLDataReader

Tengo esta función en mi clase incluye:

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 

Esto se llama de mi aspx.vb así:

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

Esto le da al siguiente error en la línea db.ExecuteReader:

No se ha podido convertir el objeto del tipo 'Microsoft.Practices.EnterpriseLibrary.Data.RefCountingDataReader' para escribir 'Sys tem.Data.SqlClient.SqlDataReader '.

¿Alguien puede arrojar algo de luz sobre cómo hago esto para que funcione? ¿Siempre tendré problemas al tratar con lectores a través de entlib?

Respuesta

0

Creo que tengo una solución de trabajo.

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) 
+0

Esto devolverá el IDataReader no envuelto, que se puede convertir al lector específico del proveedor si es necesario, como el SqlDataReader. Tiene razón en que este no es un RefCountingDataReader y no encontrará el error de lanzamiento del OP. Sin embargo, es importante señalar una de las principales razones para la implementación de RefCountingDataReader ... (ver el siguiente comentario) ... – mdisibio

+0

Si hay un System.Transaction en uso (llamadas a la base de datos incluidas en TransactionScope(), por ejemplo) verá "dbConnection.Open()" como una segunda transacción, ya sea que esté o no modificando algo. Y con la segunda conexión, se comprometerá con el coordinador de transacciones distribuidas de MS para administrar la transacción. Es un caso de esquina, pero si ocurre, sería una sobrecarga tremenda solo para recuperar algunos datos (y el servicio DTC podría no ejecutarse en el servidor). – mdisibio

1

ExecuteReader en Enterprise Library ajusta IDataReader en RefCountingDataReader que como SqlDataReader implementa la interfaz IDataReader.

RefCountingDataReader tiene la propiedad InnerReader que puede convertir a SqlDataReader. El ejemplo siguiente está en C# pero puede convertirlo fácilmente a VB.NET.

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

creo que sirve

+3

no hacen esto! ¡Perderá las conexiones de la base de datos! –

3

que habría que tener cuidado con esta implementación. Hay un hilo en el sitio Enterprise Library CodePlex que explica, al fondo de esto: http://entlib.codeplex.com/Thread/View.aspx?ThreadId=212973

Chris Tavares explica que no es bueno para simplemente devolver el .InnerReader, porque entonces el seguimiento de conexiones de Enterprise Library es expulsado (su respuesta del 20 de mayo, 5:39 PM): "Ese enfoque arruinará por completo la administración de su conexión. La razón del envoltorio es que podemos ejecutar un código adicional para limpiar el contenido al momento de deshacerse de él. ¡tirar el exterior va a fugar las conexiones! "

Así que sí, esto es un poco difícil de manejar, estamos en la misma situación ción.

Saludos, Mike

1

estoy teniendo conexiones con fugas porque todos mis métodos DA requieren un SqlDataReader. Ahora tengo que devolver el RefCountingDataReader interno y nunca puedo cerrar el lector externo. La antigua biblioteca de Enterprise funcionaba bien al devolver un SqlDataReader.

+0

En realidad, el antiguo Entlib NO estaba funcionando bien, por eso hicimos el cambio. El problema fue sutil, pero dio lugar a algunas excepciones muy desagradables y difíciles de rastrear en aplicaciones de subprocesos múltiples al usar TransactionScopes. –

+0

Para que quede claro, InnerReader es el SqlDataReader y el lector 'externo' es RefCountingDataReader ... – mdisibio

0

Debe utilizar la interfaz, no la clase concreta.

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 

y el uso. Personalmente, nunca usaría un lector de datos en una página de capa de presentación, pero supongo que para cada uno de ellos.

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 
+0

El uso de la interfaz es la mejor práctica y la más independiente para el proveedor, pero a veces necesita las características adicionales de la clase específica del proveedor. La respuesta de Stephan demuestra cómo hacer esto sin romper la administración de conexión subyacente de DAAB. – mdisibio

1

que he tomado en cuenta los comentarios y código publicados por ctavars en http://entlib.codeplex.com/discussions/212973 y http://entlib.codeplex.com/discussions/211288, lo que resulta en el siguiente enfoque genérico para la obtención de un lector de datos SQL.

En general, usa IDataReader en la instrucción de uso, luego use esa referencia directamente cuando pueda. Llame al AsSqlDataReader cuando necesite algo específico de SQL.

Añadir esta clase de extensión en alguna parte:

/// <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); 
    } 
} 

... entonces para leer los datos con un SQLReader, hacer algo como esto:

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

Después de pasar varias horas en el código fuente, creo que este es realmente el enfoque correcto. Debe enfatizarse que la respuesta aceptada anteriormente permitirá fugas de memoria si se utiliza cualquier System.Transaction (TransactionScope). – mdisibio

Cuestiones relacionadas