DataSets, DataTables y cambios continuos

Siempre recordaré cuando jugábamos de pequeños, en todos los grupos siempre había un «listillo» o «aventajado» que cuando estábamos en plena partida siempre se inventaba alguna nueva regla o norma para intentar ganar, eso siempre nos obligaba a hacer cambios en nuestras estrategias, para intentar ser el ganador del juego.


Algo parecido me ha pasado desde que trabajo con datos, primero con los recordsets de VB y ahora con los datasets y datatables del Visual Studio .net; es decir, siempre accediendo a los datos de las consultas a través de cadenas de texto, pero claro siempre hay un «listillo», en estos casos los administradores de bases de datos o los jefes o cualquier persona con poder suficiente para decirte: «bien ahora que tienes el campo Codigo de la tabla clientes que seguro que usas en 3 sitios muy concretos, se va a llamar Id_Cliente, así que en 1 hora estará todo cambiado, así que venga a por ello». Tu como buen, rápido y eficiente empleado ctrl + h o replace «Codigo» por «Id_cliente», justo cuando lo vas a hacer cumpliendo plazos (te sientes el rey del mundo), te das cuenta de que «mierda a quien se le ocurriría poner como campo principal de la tabla proveedores Codigo también», mi gozo en un pozo, ahora toca revisar todos los códigos y ver donde se puede cambiar. Tras las 8 horas de rigor llega el jefe y te pregunta: «Que ya acabaste eso?, si es un cambio sencillo, se hace de 2 patadas», y tu acordándote de la persona que le cambio los pañales.


Seguro que esta situación la hemos vivido, la vivimos y la viviremos muchos desarrolladores en nuestro dia a día.


Una forma que hace tiempo «descubrí» se trata de un pequeño truquito, que gracias al EDM averiguamos, es, crear un enumerador por cada tabla de la BBDD con todos los campos de la misma, entonces cuando queremos acceder a un campo tenemos que hacer algo como:


String field = dTable.Rows[0][Clientes.Codigo.ToString()].ToString() //Accedemos al valor de la fila 0, de la columna código de un datatable


Si ya tenemos una clase con los datos de las columnas (segun el modelo de aplicacion que usamos en Maldivas) , podemos insertar un enumerador que se llame por ejemplo Fields y que contenga lo de antes. Esto tiene una pega y es que los que useis FxCop os dara un warning en la definición de la clase por tener el enumerador dentro de la clase, entonces nos quedaria así:


String field = dTable.Rows[0][Clientes.Fields.Codigo.ToString()].ToString()


Con esto podemos estar preparados para cuando llegue el malvado administrador de base de datos o tu jefe y diga: «Cambia el campo», y con un simple refactoring del enumerador, tendríamos en todos los sitios del programa nuestro campo cambiado.


Ahora llega el momento de los «menos trabajadores» que estarán pensando, ya pero claro yo que tengo 1000 tablas de 1000 campos cada una, tengo que mantener eso….¡¡¡INMANTENIBLE!!! Para eso tenemos una herramienta que se llama CodeDom, desde la cual se puede generar código simplemente leyendo información de la base de datos, generando el fichero de código a través de los datos obtenidos de las columnas.


Otra ventaja que tenemos con este metodo es que tenemos un «intellisense» para poder acceder de forma facil a los campos que queremos que contenga la fila.


Otra forma de resolver este metodo es creando constantes de tipo string y con el nombre del campo como valor de la constate. Esto tiene algunos problemas, mucho lio, muchas variables, y que perdemos ese intellisense que con el otro caso tendríamos.


Espero que os sea de utilidad y si conoceis otros metodos que sirvan para resolver este tema, me encantaria comentarlo con vosotros.

21 comentarios sobre “DataSets, DataTables y cambios continuos”

  1. una pregunta, desarrollas usando capas? ya que en teoria eso deberia disminuir el trabajo, los datasets que usas son via wizard o codigo ? .
    usas SP en las aplicaciones?.

    Salu2

    Ddaz

  2. Pues yo que tenemos mapeado a mano toda la P*** Base de Datos y tenemos el acceso a datos Con Enterprise Library en una capa por entidad (Los antiguos Data Access Application BlocK) y luego en otra capa la logica de la misma que uso en las aplicaciones…

    Que vamos un currazoooo…

  3. @ddaz, Si estamos desarollando en capas: http://geeks.ms/blogs/jirigoyen/archive/2008/10/30/maldivas.aspx
    los dataset los creamos como medida de solucion donde nuestro modelo de edm no llega, al usar los grid de devexpress, pero me he dado cuenta de que eso se puede aplicar. Los DataTables que es lo principal que creamos los generamos en tiempo de ejecucion en base a las necesidades de cada momento, no tenemos dataset generados con el wizard.
    Si usamos muchos sp en la aplicacion, basamos mucho modelo en ellos.

    @Sergio, a veces hay que buscar el simplificar no solo el desarrollo sino el posterior mantenimiento de las mismas, que los cambios no sean para cortarse las venas sino que podamos dar una respuesta rapida. por eso he propuesto esta solucion al uso de los datatables

  4. je, si leo la serie de maldivas, no me fije que tbm estabas en el mismo proyecto ;).

    de todas maneras hiciste pruebas de carga, para ver que tanto restaba esta solucion que propones?

    Salu2

    Ddaz

  5. @ddaz: La verdad es que no, no hicimos mucha prueba de carga pero los dataset solo les usamos para cargar los grid, creo que no reste demasiado el pasar directamente el string al datarow que tostring del enumerador de la clase.

  6. Yo desaconsejo totalmente los DataSet.

    Creo que la mejor solución es la de crear los objetos de la capa de negocio con los siguientes metodos basicos: Save(), Update(), Delete(),Carga(),CargaAll(), etc.

    En la capa de Datos tener una clase por cada entidad que devuelva IdataReader para cada una de las consultas necesarias: GetItem(id), GetItems(),GetItems(idParent),etc.

    Y ademas un artilugio software «DataLoader» que sepa como cargar un objeto u colección de objetos con el correspondiente IdataReader.

    algo asi:

    Public Function Carga(ByVal id As Integer) As Envio
    Using D__ As New Dal.Envios(ConnectionProvider, TransactionProvider)
    Dal.Loader.LoadObject(Me, D__.GetItem(id))
    End Using
    Return Me
    End Function

  7. Se me olvidaba:

    Private Shared Sub InitDataBinder()
    If _DataBinder Is Nothing Then
    With New Dal.ObjectLoader()
    _DataBinder = .this
    .Add(New Dal.BindItem(«id», «Id», 0,GetType(Integer)))
    .Add(New Dal.BindItem(«idEnvio», «IdEnvio», 1,GetType(String)))
    .Add(New Dal.BindItem(«observaciones», «Observaciones», 2,GetType(String)))
    .Add(New Dal.BindItem(«numeroDeRegistro», «NumeroDeRegistro», 3,GetType(String)))
    .Add(New Dal.BindItem(«fechaDeRegistro», «FechaDeRegistro», 4,GetType(Date)))
    .Add(New Dal.BindItem(«fecha», «Fecha», 5,GetType(Date)))
    .Add(New Dal.BindItem(«usuario», «Usuario», 6,GetType(String)))
    End with
    End If
    End Sub

    Shared _DataBinder As Dal.ObjectLoader = Nothing

    »’

    »’ Constructor de instancias de proporcionando en la conexión
    »’

    »’ Conexión que utilizará el objeto.
    Public Overrides Function GetBinder() As Dal.ObjectLoader
    InitDataBinder()
    Return _DataBinder
    End Function

  8. Siento a todos el retraso en comentar, recientemente me he operado de la vista y me han quitado mas o menos el pc.
    @ddaz, hare las pruebas esta semana y os lo pongo.
    @FineDust, realmente dataset no creamos, y los datatables los usamos para cargar los grid de devexpress, el resto usamos las entidades como ha comentado Juan en sus post.

  9. Es un poco complicado explicar todo esto en pocas lineas ya que de lo que hablo es el resultado de bastantes (alrededor de 8) años de trabajo y de busqueda de información. ¿Para qué? Pues para curran menos, sencillamente.

    La idea es:

    1.- Generar automaticamente el codigo de las operaciones que SIEEEEMPRE se repiten en toda entidad: insert,update, delete y consultas. Tanto de la capa de Negocio como de la DAL.
    2.- Para esto sigo una serie de premisas:
    – Todas la tablas tienen un Id unico y es autonumerico.
    – De entrada no tengo inner joins aunque el modelo lo permite.
    – Utilizo el Lazy load que se dice 🙂 con mecanismos de caheo si es necesario.
    3.- Los objetos deben poder compartir conexion y transaccion. De forma que si cargo 200 objetos empleados y los modifico en un blucle pueda realizar un rollback.

    Lo dicho es dificil exponer TODO os pongo trozos de «mi» Framework. Bueno mio y de muchos más que me ha dado las ideas.

  10. Imports System.Reflection

    Namespace Dal

    Public Delegate Sub BeginLoadDelegate()
    Public Delegate Sub EndLoadDelegate(ByVal fields As Integer, ByVal objects As Integer)
    Public Delegate Function CommandHandler(Of T)(ByVal cmd As IDbCommand) As T
    Public Delegate Function ConnectionProviderDelegate() As IDbConnection
    Public Delegate Function TransactionProviderDelegate() As Data.IDbTransaction

    Public Class Loader
    ‘ Delegados para informar del comienzo y fin de la carga de los objetos
    Public Shared OnBeginLoad As BeginLoadDelegate = Nothing
    Public Shared OnEndLoad As EndLoadDelegate = Nothing

    Private Delegate Function FillObjectsDelegate(Of T)(ByVal dr As IDataReader, ByVal c As Dal.ConnectionProviderDelegate, ByVal t As Dal.TransactionProviderDelegate) As T
    Private Delegate Function FillObjectDelegate(Of T)(ByVal target As T, ByVal dr As IDataReader) As T

    Private Sub New()
    ‘ Constructor privado para evitar que se creen instancias de esta clase.
    End Sub

    #Region » Cargar un objeto »
    Public Shared Sub LoadObject(Of T As {Class, New, ILoader})(ByVal Target As T, ByVal dr As IDataReader, ByVal Loader_ As ObjectLoader)
    Try
    If OnBeginLoad IsNot Nothing Then OnBeginLoad()
    If dr.Read() Then PopulateObject(Target, dr, Loader_)
    If OnEndLoad IsNot Nothing Then OnEndLoad(Loader_.Campos.Count, 1)
    Catch
    Finally
    If dr.IsClosed = False Then dr.Close()
    End Try
    End Sub

    Public Shared Sub LoadObject(Of T As {Class, New, ILoader})(ByVal Target As T, ByVal dr As IDataReader)
    Try
    If OnBeginLoad IsNot Nothing Then OnBeginLoad()
    If dr.Read() Then PopulateObject(Target, dr)
    If OnEndLoad IsNot Nothing Then OnEndLoad(Target.GetObjectLoader.Campos.Count, 1)
    Catch
    Finally
    If dr.IsClosed = False Then dr.Close()
    End Try
    End Sub
    #End Region

    #Region » Creacion y carga de colecciones de objetos »
    Public Shared Function LoadObjects(Of T As {Class, New, ILoader})(ByVal Target As Generic.List(Of T), ByVal dr As IDataReader) As Generic.List(Of T)
    Return LoadObjects(Target, dr, New T().GetObjectLoader)
    End Function

    Public Shared s As Boolean = False
    Public Shared Function LoadObjects(Of T As {Class, New, ILoader})(ByVal Target As Generic.List(Of T), ByVal dr As IDataReader, ByVal Loader_ As ObjectLoader) As Generic.List(Of T)
    Try

    Dim iLoader As ILoader = DirectCast(Target, ILoader) ‘.GetObjectLoader

    ‘ Notificamos el inicio de la carga
    If OnBeginLoad IsNot Nothing Then OnBeginLoad()
    ‘ Se cachea el delegado creado dinamicamente
    If Loader_.FillObjectsDelegate Is Nothing Then Loader_.FillObjectsDelegate = MakeFillObjectsDelegate(Of T)(Loader_)
    Dim D As FillObjectsDelegate(Of T) = Loader_.FillObjectsDelegate
    Do Until dr.Read() = False
    Target.Add(D(dr, iLoader.ConnectionProvider, iLoader.transactionProvider))
    Loop
    Catch
    Finally
    If dr.IsClosed = False Then dr.Close()
    End Try
    ‘ Notificamos el fin de la carga
    If OnEndLoad IsNot Nothing Then OnEndLoad(Loader_.Campos.Count, Target.Count)
    Return Target
    End Function
    #End Region

    Private Shared Function PopulateObject(Of T As {Class, New, ILoader})(ByVal Target As T, ByVal dr As IDataReader) As T
    Return PopulateObject(Target, dr, Target.GetObjectLoader)
    End Function

    Private Shared Function PopulateObject(Of T As {Class, New})(ByVal Target As T, ByVal dr As IDataReader, ByVal Cargador As ObjectLoader) As T
    Try
    If Cargador.FillObjectDelegate Is Nothing Then Cargador.FillObjectDelegate = MakeFillObjectDelegate(Of T)(Cargador)
    DirectCast(Cargador.FillObjectDelegate, FillObjectDelegate(Of T))(Target, dr)
    Catch ex As Exception
    Finally
    If dr.IsClosed = False Then dr.Close()
    End Try
    Return Target
    End Function

    Private Shared Function MakeFillObjectsDelegate(Of T)(ByVal loader As ObjectLoader) As FillObjectsDelegate(Of T)
    Dim Metodo As New Emit.DynamicMethod(«», GetType(T), New Type() {GetType(IDataRecord), GetType(ConnectionProviderDelegate), GetType(TransactionProviderDelegate)}, GetType(T), True)

    With Metodo.GetILGenerator()
    ‘ Declarar Objeto de salida, Crearlo y Almacenarlo
    .DeclareLocal(GetType(T))
    .Emit(Emit.OpCodes.Newobj, GetType(T).GetConstructor(Type.EmptyTypes))
    .Emit(Emit.OpCodes.Stloc_0)

    For Each F As BindItem In loader.Campos
    Dim OmitirNullos As Emit.Label = .DefineLabel()
    ‘ Comprobar si el valor del campo es DbNull
    .Emit(Emit.OpCodes.Ldarg_0) ‘ DataRecord
    .Emit(Emit.OpCodes.Ldc_I4, F.fieldInfo.Index) ‘ Indice
    ‘ Llamada a la funcion
    .Emit(Emit.OpCodes.Callvirt, GetType(IDataRecord).GetMethod(«IsDBNull»))
    ‘ Saltar a la etiqueta si hay null
    .Emit(Emit.OpCodes.Brtrue, OmitirNullos)

    ‘ Recuperar el valor del campo
    .Emit(Emit.OpCodes.Ldloc_0) ‘ Objeto Negocio
    .Emit(Emit.OpCodes.Ldarg_0) ‘ DataRecord
    .Emit(Emit.OpCodes.Ldc_I4, F.fieldInfo.Index) ‘ Indice
    .Emit(Emit.OpCodes.Callvirt, GetType(IDataRecord).GetMethod(«get_Item», New Type() {GetType(Integer)}))
    Select Case True
    Case F.fieldInfo.DbType Is GetType(Integer)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Int32))
    Case F.fieldInfo.DbType Is GetType(Double)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Double))
    Case F.fieldInfo.DbType Is GetType(Decimal)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Decimal))
    Case F.fieldInfo.DbType Is GetType(Boolean)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Boolean))
    Case F.fieldInfo.DbType Is GetType(Date)
    .Emit(Emit.OpCodes.Callvirt, GetType(Object).GetMethod(«ToString», Type.EmptyTypes))
    End Select

    .Emit(Emit.OpCodes.Stfld, GetType(T).GetField(«_» + F.PropertyName, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.IgnoreCase))
    ‘.Emit(Emit.OpCodes.Callvirt, GetType(T).GetProperty(F.PropertyName).GetSetMethod)
    .MarkLabel(OmitirNullos)

    Next
    .Emit(Emit.OpCodes.Ldloc_0)
    .Emit(Emit.OpCodes.Ldarg_1)
    .Emit(Emit.OpCodes.Callvirt, GetType(T).GetProperty(«ConnectionProvider», BindingFlags.Public Or BindingFlags.Instance Or BindingFlags.IgnoreCase).GetSetMethod)
    ‘.Emit(Emit.OpCodes.Stfld, GetType(T).GetField(«_ConnectionProvider», BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.IgnoreCase))

    .Emit(Emit.OpCodes.Ldloc_0)
    .Emit(Emit.OpCodes.Ldarg_2)
    .Emit(Emit.OpCodes.Callvirt, GetType(T).GetProperty(«TransactionProvider», BindingFlags.Public Or BindingFlags.Instance Or BindingFlags.IgnoreCase).GetSetMethod)
    ‘.Emit(Emit.OpCodes.Stfld, GetType(T).GetField(«_TransactionProvider», BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.IgnoreCase))

    ‘ Cargar el objeto y devolverlo
    .Emit(Emit.OpCodes.Ldloc_0)
    .Emit(Emit.OpCodes.Ret)
    End With
    Return Metodo.CreateDelegate(GetType(FillObjectsDelegate(Of T)))
    End Function

    Private Shared Function MakeFillObjectDelegate(Of T)(ByVal loader As ObjectLoader) As FillObjectDelegate(Of T)
    Dim Metodo As New Emit.DynamicMethod(«», GetType(T), New Type() {GetType(T), GetType(IDataRecord)}, GetType(T), True)

    With Metodo.GetILGenerator()
    For Each F As BindItem In loader.Campos
    Dim OmitirNullos As Emit.Label = .DefineLabel()
    ‘ Comprobar si el valor del campo es DbNull
    .Emit(Emit.OpCodes.Ldarg_1) ‘ DataRecord
    .Emit(Emit.OpCodes.Ldc_I4, F.fieldInfo.Index) ‘ Indice
    ‘ Llamada a la funcion
    .Emit(Emit.OpCodes.Callvirt, GetType(IDataRecord).GetMethod(«IsDBNull»))
    ‘ Saltar a la etiqueta si hay null
    .Emit(Emit.OpCodes.Brtrue, OmitirNullos)

    ‘ Recuperar el valor del campo
    .Emit(Emit.OpCodes.Ldarg_0) ‘ Objeto Negocio
    .Emit(Emit.OpCodes.Ldarg_1) ‘ DataRecord
    .Emit(Emit.OpCodes.Ldc_I4, F.fieldInfo.Index) ‘ Indice
    .Emit(Emit.OpCodes.Callvirt, GetType(IDataRecord).GetMethod(«get_Item», New Type() {GetType(Integer)}))
    Select Case True
    Case F.fieldInfo.DbType Is GetType(Integer)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Int32))
    Case F.fieldInfo.DbType Is GetType(Double)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Double))
    Case F.fieldInfo.DbType Is GetType(Decimal)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Decimal))
    Case F.fieldInfo.DbType Is GetType(Boolean)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Boolean))
    Case F.fieldInfo.DbType Is GetType(Date)
    .Emit(Emit.OpCodes.Callvirt, GetType(Object).GetMethod(«ToString», Type.EmptyTypes))
    End Select

    .Emit(Emit.OpCodes.Stfld, GetType(T).GetField(«_» + F.PropertyName, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.IgnoreCase))
    ‘.Emit(Emit.OpCodes.Callvirt, GetType(T).GetProperty(F.PropertyName).GetSetMethod)
    .MarkLabel(OmitirNullos)

    Next
    ‘ Cargar el objeto y devolverlo
    .Emit(Emit.OpCodes.Ldarg_0)
    .Emit(Emit.OpCodes.Ret)
    End With
    Return Metodo.CreateDelegate(GetType(FillObjectDelegate(Of T)))
    End Function

    End Class

    Public Interface ILoader
    Function GetObjectLoader() As ObjectLoader
    Property ConnectionProvider() As Dal.ConnectionProviderDelegate
    Property transactionProvider() As Dal.TransactionProviderDelegate
    End Interface

    Public Class ObjectLoader
    Public FillObjectDelegate As [Delegate]
    Public FillObjectsDelegate As [Delegate]

    Public C As Dal.ConnectionProviderDelegate = Nothing
    Public T As Dal.TransactionProviderDelegate = Nothing

    Private _Items As Generic.List(Of BindItem)

    Public Sub New()
    _Items = New Generic.List(Of BindItem)
    End Sub
    Public Sub Add(ByVal value As BindItem)
    _Items.Add(value)
    End Sub

    Default Public ReadOnly Property Item(ByVal Index As Integer) As BindItem
    Get
    Return _Items(Index)
    End Get
    End Property

    Public Function Campos() As Generic.List(Of BindItem)
    Return _Items
    End Function

    Public Function this() As ObjectLoader
    Return Me
    End Function

    End Class

    Public Class BindItem
    Public PropertyName As String
    Public fieldInfo As DbFieldInfo
    Public Sub New(ByVal propertyName As String, ByVal fieldName As String, ByVal fieldIndex As Integer, ByVal fieldDbType As Type)
    Me.PropertyName = propertyName
    With fieldInfo
    .Name = fieldName
    .Index = fieldIndex
    .DbType = fieldDbType
    End With
    End Sub
    End Class

    Public Structure DbFieldInfo
    Public Name As String
    Public Index As Integer
    Public DbType As Type
    End Structure

    End Namespace

  11. Namespace Dal
    Public MustInherit Class ObjetoBase : Implements IDisposable
    Public ConnectionProvider As Dal.ConnectionProviderDelegate = Nothing
    Public TransactionProvider As Dal.TransactionProviderDelegate = Nothing

    #Region » Constructores »
    Public Sub New()
    End Sub
    Public Sub New(ByVal conexion_ As IDbConnection)
    _ConexionActual = conexion_
    End Sub

    Public Sub New(ByVal connectionProvider As ConnectionProviderDelegate, ByVal transactionProvider As TransactionProviderDelegate)
    Me.ConnectionProvider = connectionProvider
    Me.TransactionProvider = transactionProvider
    End Sub
    #End Region

    ‘ Destructor
    Protected Overrides Sub Finalize()
    Dispose(False)
    End Sub

    Public Sub Dispose() Implements System.IDisposable.Dispose
    Dispose(True)
    GC.SuppressFinalize(True)
    End Sub

    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
    If ConnectionProvider IsNot Nothing Then Return
    If disposing = False Then _ConexionActual = Nothing : Return
    If _ConexionActual Is Nothing Then Return
    If ConnectionProvider Is Nothing Then
    _ConexionActual.Close()
    _ConexionActual.Dispose()
    _ConexionActual = Nothing
    End If
    End Sub

    Private _ConexionActual As IDbConnection = Nothing
    Protected ReadOnly Property ConexionEnUso() As IDbConnection
    Get
    ‘ Primero se intenta usar la conexion asignada al objeto
    If _ConexionActual IsNot Nothing Then Return _ConexionActual
    ‘ Luego se intenta usar el proveedor de conexion
    If Me.ConnectionProvider IsNot Nothing Then
    _ConexionActual = ConnectionProvider.Invoke()
    Return _ConexionActual
    End If
    ‘ Si la conexion compartida no existe se crea una nueva (normalmente para uso con WebForm)
    If DataServer.Connection Is Nothing Then
    _ConexionActual = DataServer.GetNewConection()
    Return _ConexionActual
    End If
    Return DataServer.Connection() ‘ Se devuelve la connexion compartida (Esta ser� la usada normalmente por WindowsForm)
    End Get
    End Property

    Private _Transaction As IDbTransaction = Nothing
    Protected ReadOnly Property TransaccionEnUso() As IDbTransaction
    Get
    ‘ Primero se intenta usar la _Transaction asignada al objeto
    If _Transaction IsNot Nothing Then Return _Transaction
    ‘ si hay un delegado lo llamamos
    If TransactionProvider IsNot Nothing Then
    _Transaction = TransactionProvider.Invoke()
    Return _Transaction
    End If
    ‘ Se devuelve la transaccion compartida
    Return DataServer.Transaction
    End Get
    End Property

    Protected Function CreateCommand() As System.Data.IDbCommand
    Dim cmd As IDbCommand = ConexionEnUso.CreateCommand()
    cmd.Connection = ConexionEnUso
    cmd.Transaction = TransaccionEnUso
    Return cmd
    End Function

    End Class

    End Namespace

  12. @Finedust, si quieres pasame un correo con el codigo y pongo el en un fichero y lo adjunto, pero ¿generar codigo por cada entidad?, no es mas facil algo generico? una sola clase para lo generico y solo variar aquello que cambia? nosotros usamos generics para solvertar esto

  13. Reflection bien hecho no implica apenas perdida de rendimiento. El truco está en realizar la reflexsion una sola vez o saber que es lo que datos queremos

    Aqui un ejemplo y un punto sobre el que investigar.

    Private Shared Function MakeFillObjectsDelegate(Of T)(ByVal loader As ObjectLoader) As FillObjectsDelegate(Of T)
    Dim Metodo As New Emit.DynamicMethod(«», GetType(T), New Type() {GetType(IDataRecord), GetType(ConnectionProviderDelegate), GetType(TransactionProviderDelegate)}, GetType(T), True)

    With Metodo.GetILGenerator()
    ‘ Declarar Objeto de salida, Crearlo y Almacenarlo
    .DeclareLocal(GetType(T))
    .Emit(Emit.OpCodes.Newobj, GetType(T).GetConstructor(Type.EmptyTypes))
    .Emit(Emit.OpCodes.Stloc_0)

    For Each F As BindItem In loader.Campos
    Dim OmitirNullos As Emit.Label = .DefineLabel()
    ‘ Comprobar si el valor del campo es DbNull
    .Emit(Emit.OpCodes.Ldarg_0) ‘ DataRecord
    .Emit(Emit.OpCodes.Ldc_I4, F.fieldInfo.Index) ‘ Indice
    ‘ Llamada a la funcion
    .Emit(Emit.OpCodes.Callvirt, GetType(IDataRecord).GetMethod(«IsDBNull»))
    ‘ Saltar a la etiqueta si hay null
    .Emit(Emit.OpCodes.Brtrue, OmitirNullos)

    ‘ Recuperar el valor del campo
    .Emit(Emit.OpCodes.Ldloc_0) ‘ Objeto Negocio
    .Emit(Emit.OpCodes.Ldarg_0) ‘ DataRecord
    .Emit(Emit.OpCodes.Ldc_I4, F.fieldInfo.Index) ‘ Indice
    .Emit(Emit.OpCodes.Callvirt, GetType(IDataRecord).GetMethod(«get_Item», New Type() {GetType(Integer)}))
    Select Case True
    Case F.fieldInfo.DbType Is GetType(Integer)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Int32))
    Case F.fieldInfo.DbType Is GetType(Double)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Double))
    Case F.fieldInfo.DbType Is GetType(Decimal)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Decimal))
    Case F.fieldInfo.DbType Is GetType(Boolean)
    .Emit(Emit.OpCodes.Unbox_Any, GetType(Boolean))
    Case F.fieldInfo.DbType Is GetType(Date)
    .Emit(Emit.OpCodes.Callvirt, GetType(Object).GetMethod(«ToString», Type.EmptyTypes))
    End Select

    .Emit(Emit.OpCodes.Stfld, GetType(T).GetField(«_» + F.PropertyName, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.IgnoreCase))
    ‘.Emit(Emit.OpCodes.Callvirt, GetType(T).GetProperty(F.PropertyName).GetSetMethod)
    .MarkLabel(OmitirNullos)

    Next
    .Emit(Emit.OpCodes.Ldloc_0)
    .Emit(Emit.OpCodes.Ldarg_1)
    .Emit(Emit.OpCodes.Callvirt, GetType(T).GetProperty(«ConnectionProvider», BindingFlags.Public Or BindingFlags.Instance Or BindingFlags.IgnoreCase).GetSetMethod)
    ‘.Emit(Emit.OpCodes.Stfld, GetType(T).GetField(«_ConnectionProvider», BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.IgnoreCase))

    .Emit(Emit.OpCodes.Ldloc_0)
    .Emit(Emit.OpCodes.Ldarg_2)
    .Emit(Emit.OpCodes.Callvirt, GetType(T).GetProperty(«TransactionProvider», BindingFlags.Public Or BindingFlags.Instance Or BindingFlags.IgnoreCase).GetSetMethod)
    ‘.Emit(Emit.OpCodes.Stfld, GetType(T).GetField(«_TransactionProvider», BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.IgnoreCase))

    ‘ Cargar el objeto y devolverlo
    .Emit(Emit.OpCodes.Ldloc_0)
    .Emit(Emit.OpCodes.Ret)
    End With
    Return Metodo.CreateDelegate(GetType(FillObjectsDelegate(Of T)))
    End Function

  14. Como se puede ve en el codigo de arriba el «metodo generado» asigna el proveedor de conexion y el de transaccion a cada uno de los objetos cargados:

    GetProperty(«ConnectionProvider»)

    Con este mecanismo estaremos en condiciones de que todos los objetos cargados compartan una conexion a la hora de moficar el precio de todos dentro de un bucle por ejemplo.

    Dim th As New TransactionHelper(Dal.DataServer.GetNewConection())
    Try
    th.BeginTransaction()
    For Each e As Envio In Factory.Crear(Of Envios)(th).Carga()
    e.Importe = 45
    e.Save()
    Next
    th.Commit()
    Catch ex As Exception
    th.RollBack()
    Finally
    th.Dispose()
    End Try

    Esto a mi modo de ver es elegancia en el codigo.

  15. Este es un fragmeto del codigo de la capa de Negocio:

    »’

    »’ Carga todos los elementos del tipo Envio desde la base de datos en la colección
    »’

    Public Function Carga() As Envios
    Using D__ As New Dal.Envios(ConnectionProvider, TransactionProvider)
    Return Dal.Loader.LoadObjects(Of Envio)(Me, D__.GetItems())
    End Using
    End Function

  16. Todos los objetos de la la capa de negocio tanto los «elementos» como las «coleccionesDeElementos» tienen dos propiedades: un proveedor de conexion y uno de transaccion. sencillamente los objetos son los responsables de «pasar» dichas propiedades a los objetos que ellos creen y estos a su vez a sus «hijo».

    La capa de negocio pasa estas propiedades a la de datos que simplemente las utiliza si tienen algun valor:
    En el encargado de establecer estos valores es el metodo CreateCommad().

    Public Function GetItems() As IDataReader
    Using cmd As IDbCommand = CreateCommand()
    cmd.CommandText = «Select * from [TMA_Envio]»
    Return DataServer.ExecuteReader(cmd)
    End Using
    End Function

    En la capa de negocio bastará con asignar los valores a los objetos que creemos

    dim Envios = New Envios().Carga()

    with Envios(0)
    dim Otro = .this.CargaOtro()
    Otro.ConnectionProvider= Envios.ConnectionProvider
    Otro.TransactionProvider= Envios.TransactionProvider

    Otro.id =56
    Otro.save()

    end with

    Resumiendo: Cuando se cargan los objetos en algun metodo de la capa de negocio sencillamente se les pasan las «provider» que tenemos o nothing.

  17. Efectivamente, solo hay dos capas: Negocio y Dal. Creo que tener más es complicar las cosas, pero todo depende de las circustancias y de los requerimientos del proyecto.

Responder a anonymous Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *