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.
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
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…
@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
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
@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.
seria bueno si un dia te pusieras a hacer unas pruebas con el dottrace, asi como carlos lo muestra en su post 🙂 http://msmvps.com/blogs/cwalzer/archive/2007/09/24/anti-pr-225-cticas-i-acceso-a-datos-con-ado-net.aspx , y nos comentes los resultados ;);
Salu2
Ddaz
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
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
»’
»’
»’ Conexión que utilizará el objeto.
Public Overrides Function GetBinder() As Dal.ObjectLoader
InitDataBinder()
Return _DataBinder
End Function
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.
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.
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
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
@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
mandame tu direccion a la mia : Fine_dust y luego
Hotmail.com y te mando un zip con el codigo y un generador de andar por casa que tengo.
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
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.
Este es un fragmeto del codigo de la capa de Negocio:
»’
»’
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
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.
Bueno he currado un poquillo y aqui teneis un ejemplo de mi «Framework».
http://rcastroblog.blogspot.com/
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.
Acabo de adjuntarlo estara en la seccion de downloads.
un saludo y espero esta semana hablar del cojoframerwork