Capitulo 6 - ADO.NET Entity Framework (Parte 2)

Almacenando información sobre los objetos y su Estado

Cuando se ejecuta una consulta, los objetos deben ser devueltos para detectar cambios en el estado, la información ChangeTracking de los objetos devueltos se almacenan en ObjectStateEntry, que el ObjectContext crea para cada objeto recuperado.La clase ObjectStateEntry esta en el espacio de nombres System.Data.Objects.

 

Nombre

Descripción

CurrentValues

Valores de propiedad actuales del objeto o la relación asociados a este ObjectStateEntry.

Entity

Objeto asociado a este ObjectStateEntry.

EntityKey

Clase EntityKey asociada al ObjectStateEntry.

EntitySet

El objeto EntitySetBase para el objeto o relación asociados a este objeto ObjectStateEntry.

IsRelationship

Valor booleano que indica si este ObjectStateEntry representa una relación.

ObjectStateManager

Obtiene ObjectStateManager para este ObjectStateEntry.

OriginalValues

versión de solo lectura de los valores originales del objeto o la relación asociados a este ObjectStateEntry. Para obtener valores originales actualizables, use GetUpdatableOriginalValues.

RelationshipManager

Devuelve una instancia de RelationshipManager para el objeto representado por la entrada.

State

estado de este ObjectStateEntry.

El ObjectContext  tiene el metodo SaveChanges para guardar los objetos y si se quiere saber que propiedades han cambiado, se puede realizar un query para obtener los objectos ObjectStateEntry usando el metodo GetModifiedProperties (debuelve un IEnumerable de string con las propiedades cambiadas).

Nombre de miembro

Descripción

Detached

El objeto existe pero no se está haciendo un seguimiento de él. Una entidad está en este estado inmediatamente después de crearla y antes de agregarla al contexto del objeto.

Unchanged

El objeto no se ha modificado desde que se adjuntó al contexto o desde la última vez que se llamó al método SaveChanges.

Added

El objeto es nuevo, se ha agregado al contexto del objeto y no se ha llamado al método SaveChanges.

Deleted

El objeto se ha eliminado del contexto del objeto. Una vez guardados los cambios, el estado del objeto cambia a Detached.

Modified

Se ha modificado una de las propiedades escalares del objeto y no se ha llamado al método SaveChanges.

 

Ciclo de Vida del ObjectContext
EL ObjectContext hace referencia a muchos objetos para proporcionar el control de cambios,la huella de la memoria podría llegar a ser bastante grande. Se deben tener en cuenta varias concideraciones:

  • Uso de la memoria: un ObjectContext crece mucho porque almacena  referencias a todas las entidades que maneja.
  • Dispose (borrado): no implementa la IDisposable , así que se debe interactuar con éste dentro de bloques.
  • Costo de construcción: pequeño.
  • Thread Safety (Seguridad para los subprocesos) : no es seguro para subprocesos. De forma se puede sincronizar el acceso a un ObjectContext compartido, pero hay que analizar y decidir si es mejor tener un ObjectContext por hilo o sincronizar un ObjectContext compartido .
  • Stateless (Sin estados): Si va a crear un entorno sin estado, como un servicio web, no se debe volver a utilizar un mismo ObjectContext para llamadas a varios métodos. Se debe ejecutar cada uno dentro de un bloque "using".
  • Lazy Loading vs Loading explícito vs eager loading
    Carga diferida (Lazy Loading) se refiere a retrasar la carga de datos hasta que los datos que se necesiten.Carga diferida también se conoce como carga justo a tiempo, inicialización perezosa, carga on-demand, y la carga diferida.  En Entity Framework 4, la carga diferida está activado por defecto, para desactivarlo, se debe hacer clic en la superficie del diseñador y, en la ventana Propiedades, establezcer la propiedad Lazy Loading Enabled en false.

-------------------------------------VB-----------------------------------------

Private Sub LazyLoadingToolStripMenuItem_Click( _

ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles LazyLoadingToolStripMenuItem.Click

MessageBox.Show("con Lazy Loading Enabled " & _

"la propiedad en true!")

Dim db As New NorthwindEntities()

Dim order = db.Orders.Where(Function(o) o.CustomerID = "ALFKI").First()

gv.DataSource = order.Order_Details.ToList()

End Sub

-------------------------------------CS-----------------------------------------

private void lazyLoadingToolStripMenuItem_Click(

object sender, EventArgs e)

{

MessageBox.Show("con Lazy Loading Enabled " +

"la propiedad en true!");

var db = new NorthwindEntities();

var order = db.Orders.Where(o => o.CustomerID == "ALFKI").First();

gv.DataSource = order.Order_Details.ToList();

}

Para usar la carga ansiosa (eager loading), se usa el método Include para incluir los objetos de detalle de la orden cuando se ejecuta la consulta:

-------------------------------------VB-----------------------------------------

Private Sub EagerLoadingToolStripMenuItem_Click( _

ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles EagerLoadingToolStripMenuItem.Click

MessageBox.Show("con Lazy Loading Enabled "& "la propiedad en false!")

Dim db As New NorthwindEntities()

Dim order = db.Orders.Include("Order_Details") _

.Where(Function(o) o.CustomerID = "ALFKI").First()

gv.DataSource = order.Order_Details.ToList()

End Sub

-------------------------------------CS-----------------------------------------

private void eagerLoadingToolStripMenuItem_Click(

object sender, EventArgs e)

{

MessageBox.Show("con Lazy Loading Enabled " + "la propiedad en false!");

var db = new NorthwindEntities();

var order = db.Orders.Include("Order_Details").Where(

o => o.CustomerID == "ALFKI").First();

gv.DataSource = order.Order_Details.ToList();

}

Para usar la carga explícita, se usa el método Load para cargar los objetos de los detalles del pedido antes de ejecutar el método ToList.

-------------------------------------VB-----------------------------------------

Private Sub ExplicitLoadingToolStripMenuItem_Click( _

ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles ExplicitLoadingToolStripMenuItem.Click

MessageBox.Show("con Lazy Loading Enabled "& "la propiedad en false!")

Dim db As New NorthwindEntities()

Dim orders = db.Orders.Where(Function(o) o.CustomerID = "ALFKI").First()

orders.Order_Details.Load()

gv.DataSource = orders.Order_Details.ToList()

End Sub

-------------------------------------CS-----------------------------------------

private void explicitLoadingToolStripMenuItem_Click(

object sender, EventArgs e)

{

MessageBox.Show("con Lazy Loading Enabled " + "la propiedad en false!");

var db = new NorthwindEntities();

var order = db.Orders.Where(o => o.CustomerID == "ALFKI").First();

order.Order_Details.Load();

gv.DataSource = order.Order_Details.ToList();

}

Más de Modelado y Diseño

Trabajando con tipos complejos

Los tipos complejos son propiedades no escalares de una entidad que permiten organizar las propiedades escalares dentro de las entidades. Al igual que las entidades, los tipos complejos están compuestos de propiedades escalares u otras propiedades de tipos complejos. Dado que estos no tienen claves, Entity Framework no puede administrarlos excepto a traves del objeto que las contiene.

Se puede crear y modificar un tipo completo usando la ventana del diseñador EDMX, clic derecho , crear tipo complejo. Después de que el tipo complejo se ha añadido, puede le agregar propiedades escalares o complejas.

También se puede crear un tipo complejo a partir de propiedades existentes en una entidad, seleccionando una o más propiedades de una entidad, haga clic derecho,(Refactor Into New Complex Type) Refactorizar en un nuevo tipo complejo. Esto agrega un nuevo tipo de complejo con las propiedades seleccionadas al Explorador de modelos.

Los tipos complejos no admiten la herencia y no puede contener las propiedades de navegación. No pueden ser nulos, si se llama a SaveChanges en ObjectContext a con un tipo complejo nulo  se produce una InvalidOperationException.

Mapeo de procedimientos almacenados

Un procedimiento almacenado que se define en una base de datos se puede exponer en un modelo conceptual de dos maneras:

·                     Se puede crear una importación de función en el modelo conceptual que se asigne a un procedimiento almacenado.se define un método en ObjectContext para que ejecute el procedimiento almacenado en la base de datos.

·                     Asignar operaciones de inserción, actualización y eliminación para algún tipo de entidad a procedimientos almacenados. Esto permite definir comportamientos personalizados de inserción, actualización y eliminación para las entidades.

Para agregar un procedimiento almacenado al modelo conceptual , clic derecho en el diseñador de Entity Framework --> Update Model From Database.

clip_image001

En la pestaña Add --> seleccionar los stored procedures que queremos agregar , finish.

clip_image002

 

o, clic derecho--> add new--> Function import

 clip_image003

colocar un nombre, seleccionar el procedimiento almacenado que queremos agregar y el tipo que devuelve--> ok.

clip_image004

clip_image005

Clases parciales y métodos

Entity Framework genera clases que contienen las propiedades definidas en el modelo conceptual y no contienen ningún método. Estas clases generadas son parciales, lo que significa que se puede extender estas clases mediante la adición de sus propios métodos y propiedades de un archivo fuente independiente.

Para agregar el nuevo método, sin el riesgo de perderlo, cuando las clases se regeneran, se debe agregar un nuevo archivo al proyecto que contiene una clase parcial con el mismo nombre que la clase que se genera y que deben estar en el mismo espacio de nombres .

Por ejemplo se quiere agregar una propiedad DetailTotal para mostrar el total de los Order_Detail y usarlo como si existiera en la clase original.

-------------------------------------VB-----------------------------------------

Partial Public Class Order_Detail

Public ReadOnly Property DetailTotal() As Decimal

Get

Return (1 - CType(Discount, Decimal)) *

(Quantity * UnitPrice)

End Get

End Property

End Class

-------------------------------------CS-----------------------------------------

namespace EntityFrameworkSampleCode

{

public partial class Order_Detail

{

public double DetailTotal

{

get

{

return (1 - Discount) *

(double)(Quantity * UnitPrice);

}

}

}

}

Hay un par de métodos parciales llamados OnXxxChanging y OnXxxChanged para cada propiedad, en la que XXX es el nombre de la propiedad. El método OnXxxChanging se ejecuta antes de que la propiedad ha cambiado, y el método OnXxxChanged se ejecuta después de que la propiedad ha cambiado.

En el siguiente ejemplo de código, los métodos y OnQuantityChanging OnQuantityChanged se han implementado en la clase parcial del ejemplo anterior.

-------------------------------------VB-----------------------------------------

Private Sub OnQuantityChanging(ByVal value As Global.System.Int16)

Debug.WriteLine(String.Format( _

"Changing quantity from {0} to {1}", Quantity, value))

End Sub

Private Sub OnQuantityChanged()

Debug.WriteLine(String.Format( _

"Changed quantity to {0}", Quantity))

End Sub

-------------------------------------CS-----------------------------------------

partial void OnQuantityChanging(global::System.Int16 value)

{

Debug.WriteLine(string.Format(

"Changing quantity from {0} to {1}", Quantity, value));

}

partial void OnQuantityChanged()

{

Debug.WriteLine(string.Format(

"Changed quantity to {0}", Quantity));

}

 

Implementando la herencia en el Entity Facebook
Uno de los problemas con la conexión de una aplicación orientada a objetos a una base de datos relacional es que la base de datos relacional no tiene ningún concepto de la herencia. Hay tres formas básicas de resolver este problema: tabla por jerarquía de clases (TPH), la tabla por tipo (TPT), y la tabla por clase concreta (TPC).

TPH (tabla por jerarquía):

La más simple y más fácil de implementar, también se conoce como herencia de tabla única. Para implementar esta solución, todos los tipos concretos en la jerarquía de herencia se almacenan en una tabla. Entity Framework necesita saber qué tipo de cada fila, por lo que debe definir una columna discriminadora para las identidades del tipo concreto se asignen.
Aunque esta solución es simplista desde la perspectiva de un administrador de base de datos, este modelo no está normalizado, ya que suelen tener las columnas que tienen muchos valores nulos. Esto se debe a que algunos tipos de columnas que necesitan los demás no necesitan, por lo que debe marcar las columnas que son únicas como nulable.

Este enfoque no es tan eficiente con el espacio en disco consumido, pero la compensación es la simplicidad y pocos joins a otras tablas, lo que puede obtener un mejor rendimiento.

en el sigugiente ejemplo: Vehícle es abstracta y contiene Id, Vin, Make, Model, y Year. Car hereda de Vehícle y tiene una propiedad TireSize. Boat hereda de Vehícle y tiene una propiedad PropellerSize.

en la base de datos la veríamos así:

clip_image006

Después de crear la tabla en la base, agregamos un TablePerHierarchyModel.edmx ADO.NET Entity Data Model, y, en el asistente, clic en generar a partir de la base de datos, y seleccionamos la tabla Vehículos.

Hacer clic derecho en la superficie del diseñador y seleccione Agregar | Entidad. Car y el tipo de base vehícle, de la misma forma con Boat

clip_image007

En este punto, ya podemos ver la jerarquía de herencia, pero el TireSize y PropSize todavía están en la clase vehícle.
Con el Botón derecho del ratón y seleccione Cortar TireSize, y en la entidad Car , Pegar.
Clic en PropSize y Cortar, haga clic derecho en el nodo de propiedades en la entidad boat y seleccione Pegar.

clip_image008

También se debe especificar cuando una fila se debe asignar a una entidad Car. haciendo clic en Add A Condition y, en la lista desplegable, seleccione el tipo. A continuación,haga clic en <Empty String> y tipo Car.

clip_image009

y de forma similar con la propiedad PropSize.

Si se intenta generar la aplicación,se verá el siguiente error:

"Error 159:" TablePerHierarchyModel.Vehicle 'EntityType no tiene una clave definida "

Se debe definir la clave para el EntityType. clic en la propiedad Id y, en la ventana Propiedades, se debe establecer la propiedad clave de entidad en true.

Si se intenta generar de nuevo, verá un nuevo error: Error 3023: "Problemas de fragmentos de mapeo a partir de las líneas 56, 66, 73: En la columna Vehícles . Tipo no tiene ningún valor predeterminado y no se puede anular. "Para corregir este error, se debe seleccionar la entidad de vehícle y, en la ventana Propiedades, establezca la propiedad Abstract en true.

Al generar de nuevo, se verá un nuevo error: "Error 3032: El problema de fragmentos de mapeo a partir de la línea 56: 'Vehicles.Type". Para corregir este problema, se debe quitar la propiedad Type de la entidad de 'Vehiclesya que la propiedad discriminadora no debe asignarse. A continuación, debería poderse generar el proyecto.

Ejemplo de la tabla vehicles:

ID      

TYPE

VIN

MAKE

MODEL

YEAR

TIRESIZE

PROPSIZE

1

Car

ABC123

BMW

Z-4

2009

  225/45R17

NULL

2

Boat

DEF234

SeaRay

SunDeck

2005

NULL

14.75 x 21 SS

3

Car

GHI345

VW

Bug

2007

205/55R16

NULL

4

Boat

JKL456

Harris

FloatBoat

2000

NULL

14-1/2” x 18” LH

 

Ejemplo de como mostrar los autos (car) en un datagrid :

-------------------------------------VB-----------------------------------------

Private Sub tPHDisplayCarsToolStripMenuItem_Click( _

ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles tPHDisplayCarsToolStripMenuItem.Click

Dim db = New TablePerHierarchy.TablePerHierarchyEntities()

gv.DataSource = (From b In db.Vehicles.OfType(Of TablePerHierarchy.Car)()

Select b).ToList()

End Sub

-------------------------------------CS-----------------------------------------

private void tPHDisplayCarsToolStripMenuItem_Click(object sender, EventArgs e)

{

var db = new TablePerHierarchy.TablePerHierarchyEntities();

gv.DataSource = (from c in db.Vehicles.OfType< TablePerHierarchy.Car>()

select c).ToList();

}

Con los botes es de una manera similar, solo cambiando el tipo OfType< TablePerHierarchy.Boat>()

TPT (tabla por tipo)
Es la más eficiente. Para implementar esta solución, cada tipo en la jerarquía de la herencia se almacena en su propia tabla. Con una asignación uno a uno, esta solución solución suele ser la más normalizada.

Uno de los posibles inconvenientes de esta solución es el aumento en el número de entradas que necesita para obtener los datos. Los Joins son caros, pero si se tiene cuidado sobre la creación de índices de claves externas cuando sea necesario, los problemas de rendimiento pueden ser minimizados.

clip_image010

Id es la clave principal para todas estas tablas. En la tabla de los vehícles, la propiedad ID está configurada para ser una columna auto-numérica (de identidad). En las tablas de cars y boats, la propiedad ID, recibe su valor de la columna de identificación del vehículo. Vin, make, model y year son comunes a todos, por lo que están contenidos en una clase de base vehícle. TireSize es obligatoria para todos los autos y se define Cars (no nulos). PropSize es obligatoria para todo los barcos y se define en Boats (no nulos).

El modelo de Entity Framework, TablePerTypeModel.edmx, quedaria:

clip_image011

Se debe seleccionar la entidad Boat, en la ventana de Propiedades establecer la propiedad Base Type a Vehicles. Lo mismo para Car y después eliminar las asociaciones y los propiedades Id de cada una, ya que las heredan de Vehicles. Por último, clic en el vehícles y establecer la propiedad Abstract en true.

Las tablas en el diseñador se verán de esta forma:

clip_image012

Se pueden mostrar los barcos en un datagrid de esta forma:

-------------------------------------VB-----------------------------------------

Dim db = New TablePerType.TablePerTypeEntities()

gv.DataSource = (From b In db.Vehicles.OfType(Of TablePerType.Boat)()

Select b).ToList()

-------------------------------------CS-----------------------------------------

var db = new TablePerType.TablePerTypeEntities();

gv.DataSource = (from b in db.Vehicles.OfType<TablePerType.Boat>()

select b).ToList();

TPC (tabla por clase concreta)
En esta solución, se proporciona una tabla para cada clase concreta, pero no para la abstracta. Para aclarar la terminología, la clase abstracta es la clase base desde la cual las clases hijas concretas se heredan.

Uno de los posibles inconvenientes de esta solución es la duplicación de las columnas de cada clase concreta. Si se agrega una nueva propiedad a la clase abstracta, debe agregar una columna para cada tabla concreta. Además, si se necesita acceder a los valores únicos  que están en la clase abstracta, se debe crear una consulta que realiza la unión de todas las tablas. Por ejemplo, si el VIN, make, model y year estan en la clase abstracta, y desea una lista de todos los vehículos (cars y boats) cuyo año es 2000, es necesario la unión de todas las tablas para obtener ese resultado.

Esta solución de herencia no se utiliza muy seguido, en relación con TPH y TPT.

clip_image013

TPC  no es compatible con el diseñador de Entity Framework, pero se puede implementar mediante la edición del edmx con un editor de XML.

Cuando se genera el Entity Data Model, el problema es que crea las tablas por separado y se desea utilizar herencia para las columnas que existen en ambas entidades.

Para solucionar esto se puede añadir a la Entidad Abstracta vehícles , en la ventana Propiedades, establecer la propiedad abstracta en true. Ya sea de boat o car, seleccionar las propiedades compartidas , copiarlas y clic en pegar en la entidad vehícles. En las entidades boats y car, clic en la ventana Propiedades, elijir el tipo base y seleccionar vehíccles. Ahora que boats y cars heredan de vehícle, puede eliminar las propiedades pasadas a vehicle.

clip_image014

Para mapear las propiedades que tiene vehicle en boat y car, se debe cerrar el archivo EDMX y abrirlo con un editor de xml .

clip_image015

El esquema del almacén de lenguaje de definición (SSDL) esta bien. El contenido CSDL  es el que necesita un cambio, porque este representa el modelo conceptual que se acaba de modificar en el diseñador.

El modelo conceptual actual tiene la definición de la clase vehícle  en el ObjectContext, pero es necesario agregar la clase car y la clase boats.

XML anterior:

<EntityContainer Name="TablePerConcreteEntities"

annotation:LazyLoadingEnabled="true">

<EntitySet Name="Vehicles" EntityType="TablePerConcreteModel.Vehicle" />

</EntityContainer>

 

XML al que le agregamos las 2 clases:

<EntityContainer Name="TablePerConcreteEntities"

annotation:LazyLoadingEnabled="true">

<EntitySet Name="Cars" EntityType="TablePerConcreteModel.Car" />

<EntitySet Name="Boats" EntityType="TablePerConcreteModel.Boat" />

</EntityContainer>

Hay dos problemas con la sección CDSL. En primer lugar, que no contiene las asignaciones para todas las columnas de las tablas Car y Boat a las columnas de la clase abstracta. En segundo lugar, las tablas car y boat se muestran anidadas dentro de la tabla vehícle, para arreglar esto se deben poner de manera explicita:

<edmx:Mappings>

<Mapping Space="C-S"

xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">

<EntityContainerMapping

StorageEntityContainer="TablePerConcreteModelStoreContainer"

CdmEntityContainer="TablePerConcreteEntities">

<EntitySetMapping Name="Cars">

<EntityTypeMapping

TypeName="IsTypeOf(TablePerConcreteModel.Car)">

<MappingFragment StoreEntitySet="Cars">

<ScalarProperty Name="Id" ColumnName="Id" />

<ScalarProperty Name="Vin" ColumnName="Vin" />

<ScalarProperty Name="Make" ColumnName="Make" />

<ScalarProperty Name="Model" ColumnName="Model" />

<ScalarProperty Name="Year" ColumnName="Year" />

<ScalarProperty Name="TireSize" ColumnName="TireSize" />

</MappingFragment>

</EntityTypeMapping>

</EntitySetMapping>

<EntitySetMapping Name="Boats">

<EntityTypeMapping

TypeName="IsTypeOf(TablePerConcreteModel.Boat)">

<MappingFragment StoreEntitySet="Boats">

<ScalarProperty Name="Id" ColumnName="Id" />

<ScalarProperty Name="Vin" ColumnName="Vin" />

<ScalarProperty Name="Make" ColumnName="Make" />

<ScalarProperty Name="Model" ColumnName="Model" />

<ScalarProperty Name="Year" ColumnName="Year" />

<ScalarProperty Name="PropSize" ColumnName="PropSize" />

</MappingFragment>

</EntityTypeMapping>

</EntitySetMapping>

</EntityContainerMapping>

</Mapping>

</edmx:Mappings>

 

No hay comentarios:

Publicar un comentario