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

Lección 2: realizando consultas y actualizando con Entity Framework

Ejecutando consultas
Cuando se ejecuta una consulta LINQ, LINQ to Entities las convierte a un grupo de comandos SQL. Estas se ejecutan en ObjectContext, y los objetos devueltos pueden ser utilizados tanto por el Entity Framework y LINQ.

Entity Framework el sigue este procedimiento al ejecutar una consulta LINQ:

1. ObjectContext crea una instancia de ObjectQuery. La clase ObjectQuery implementa la interfaz IQueryable, que define muchos métodos que pueden utilizarse para crear un objeto que se consulta y se enumeran para obtener el resultado de la consulta.

2. Mediante un ObjectQuery, ObjectContext se crea una consulta LINQ to Entities en Visual Basic o C #: Por ejemplo:

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

Dim db As New NorthwindEntities()

Dim customers As ObjectQuery(Of Customer) = db.Customers

Dim result As IQueryable(Of Customer) =

From c In customers

Where c.CustomerID.StartsWith("S")

Select c

gv.DataSource = result.ToList()

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

var db = new NorthwindEntities();

ObjectQuery<Customer> customers = db.Customers;

IQueryable<Customer> result = from c in customers

where c.CustomerID.StartsWith("S")

select c;

gv.DataSource = result.ToList();

3. La consulta se ejecuta contra el data source. Las consultas de LINQ to Entities pueden proporcionar la ejecución diferida.
4. Devuelve los resultados de las consultas como objetos. Esto se conoce como materialización.
Materialización (no devuelve filas de datos).

 

Diferencia en la ejecución de consultas
Debido a que LINQ to Entities es un subconjunto de LINQ, las siguientes son las áreas donde no es totalmente compatible y pueden lanzar un excepción NotSupportedException:

Proyecciones: son las transformación de los datos devueltos . Las proyecciones se apoyan en los métodos de extensión Select y SelectMany. La mayoría de las sobrecargas de Select y SelectMany son compatibles con LINQ to Entities, con la excepción de las que aceptan como argumento un índice posicional.
Filtros: proporciona la capacidad de filtrar.La mayoría de las sobrecargas de Where son compatibles con LINQ to Entities, excepto las que aceptan como argumento un índice posicional.

Ordenar: soporta OrderBy, OrderByDescending, ThenBy ThenByDescending, y Reverse. La mayoría de las sobrecargas son compatibles, salvo las sobrecargas que aceptan un IComparer porque esto no puede ser trasladado al data source.

Uniones: Join y GroupJoin. La mayoría de las sobrecargas son compatibles, salvo las sobrecargas que aceptan un IEqualityComparer porque no puede ser trasladado al data source.

Paginación: una operación de paginación genera un elemento único y  o una secuencia. Los métodos admitidos son los siguientes: First, FirstOrDefault, Skip y Take.Los métodos no compatibles se ElementAt, ElementAtOrDefault, Last, LastOrDefault, Single, SingleOrDefault, SkipWhile y TakeWhile.

Agrupamiento: El método de agrupamiento es GroupBy. La mayoría de las sobrecargas son compatibles, con excepción de las que utilizan un IEqualityComparer porque no puede ser trasladado al data source.

El siguiente ejemplo de código utiliza el método AsEnumerable para evitar la excepción que se puede producir en los ejemplos listados anteriormente:

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

Dim db As New NorthwindEntities()

Dim result = db.Customers.AsEnumerable() _

.Where(Function(c, i) i Mod 2 = 0)

gv.DataSource = result.ToList()

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

var db = new NorthwindEntities();

var result = db.Customers.AsEnumerable()

.Where((c, i) => i % 2 == 0);

gv.DataSource = result.ToList();

Introducción de Entity SQL
Entity Framework permite a los desarrolladores escribir consultas basadas en objetos que se dirigen a una entidad del modelo de datos , sin la necesidad de conocer el esquema lógico de la base de datos.

Entity SQL (ESQL) proporciona un lenguaje de consulta que nos permite escribir consultas en términos de abstracciones de Entity Data Model. ESQL fue diseñado para responder a esta necesidad mediante el apoyo a los tipos de una manera limpia y expresiva.
ESQL no es SQL, pero se puede utilizar para pasar consultas SQL a la base de datos. Proporciona acceso a datos a colecciones EntitySet de un tipo de entidad dado.

Cuando se trabaja con ESQL, se está trabajando con el proveedor de datos EntityClient. Las consultas escritas en ESQL se pueden reutilizar en base de datos diferentes.

clip_image001

Hay dos formas de ejecutar consultas con ESQL: mediante un EntityCommand o un ObjectQuery, pero primero tenemos que saber cómo abrir una conexión a una entidad.

Abriendo una conexión ESQL
Para utilizar ESQL, se necesita un objeto EntityConnection. que está en el espacio de nombres System.Data.EntityClient .  Se puede utilizar un EntityConnectionStringBuilder  para crear una cadena de conexión, esta cadena debe incluir los metadatos que hacen referencia a los archivos .csdl (Conceptual schema definition language), .ssdl  (store schema definition language) y los .msl (Mapping specification language).

Si ya se tiene una cadena de conexión almacenada en el archivo de configuración de la aplicación, se puede establecer la cadena de conexión fácilmente mediante el uso de su nombre , en este caso :"name = NorthwindEntities".

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

Using conn As New EntityConnection("name=NorthwindEntities")

conn.Open()

MessageBox.Show("Conectado!")

End Using

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

using (EntityConnection conn =

new EntityConnection("name=NorthwindEntities"))

{

conn.Open();

MessageBox.Show("Conectado!");

}

El objeto EntityCommand
Cuando se utiliza EntityCommand, no hay materialización de la entidad. Esto significa que la consulta no devuelve objetos, sólo se obtendrá un DbDataReader, que da un flujo de datos en filas.

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

Using conn As New EntityConnection("name = NorthwindEntities")

conn.Open()

Dim myQuery = "SELECT c.CustomerID FROM NorthwindEntities.Customers AS c"

Using cmd As New EntityCommand(myQuery, conn)

Dim customerIds = New List(Of String)()

Dim reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)

While reader.Read()

customerIds.Add(reader.GetString(0))

End While

gv.DataSource = (From id In customerIds

Select New With {.ID = id}).ToList()

End Using

End Using

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

using (EntityConnection conn = new EntityConnection("name = NorthwindEntities"))

{

conn.Open();

string myQuery = "SELECT c.CustomerID FROM NorthwindEntities.Customers AS c";

using (EntityCommand cmd =

new EntityCommand(myQuery, conn ))

{

var customerIds = new List<string>();

var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);

while (reader.Read())

{

customerIds.Add((string)reader[0]);

}

gv.DataSource = (from id in customerIds

select new {ID=id}).ToList();

}

}

Este ejemplo recupera una lista de los ID de cliente mediante el uso del contenedor NorthwindEntities que se definió en el Entity Data Model y se puede utilizar para acceder a la colección EntitySet clientes. Cuando se crea un alias, se debe utilizar la palabra clave AS.

Para mostrar los customerIds en la cuadrícula de datos, una consulta de LINQ crea una lista genérica de los tipos anónimos que tienen una propiedad ID. En ESQL, no se puede utilizar el asterisco (*) para indicar todas las columnas. Cuando ExecuteReader se llama para recuperar un flujo de filas, se debe incluir CommandBehavior.SequentialAccess.

La clase ObjectQuery

La clase ObjectQuery permite escribir consultas que utilizan las entidades del modelo conceptual. A diferencia de la clase EntityCommand, que puede utilizar la clase ObjectQuery para recuperar las entidades materializadas:

Using ctx = New ObjectContext("name=NorthwindEntities")

ctx.Connection.Open()

Dim myQuery = "SELECT c.CustomerID FROM NorthwindEntities.Customers AS c"

Dim customerIds = New ObjectQuery(Of DbDataRecord)(myQuery, ctx).ToList()

gv.DataSource = (From id In customerIds

Select New With {.ID = id.GetString(0)}).ToList()

End Using

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

using (ObjectContext ctx = new ObjectContext("name=NorthwindEntities"))

{

ctx.Connection.Open();

string myQuery = "SELECT c.CustomerID FROM NorthwindEntities.Customers AS c";

var customerIds = new ObjectQuery<DbDataRecord>(myQuery, ctx).ToList();

gv.DataSource = (from id in customerIds

select new { ID = id.GetString(0) }).ToList();

}

La función ROW
El modelo de entidad  de datos define tres clases de tipos: tipos primitivos como Int32 y String, tipos nominales que se definen en el esquema, tales como la entidad y relación, y los tipos de transitorios que son tipos anónimos, como la colleccion, fila, y ref.
Ejemplo de la creación de una instancia de fila:

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

Using ctx = New ObjectContext("name=NorthwindEntities")

ctx.Connection.Open()

Dim myQuery = "ROW(1 AS MyIndex, 'ALFKI' AS MyId)"

Dim myStuff = New ObjectQuery(Of DbDataRecord)(myQuery, ctx).ToList()

gv.DataSource = (From i In myStuff

Select New With

{

.Index = i.GetInt32(0),

.Id = i.GetString(1)

}).ToList()

End Using

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

using (ObjectContext ctx = new ObjectContext("name=NorthwindEntities"))

{

ctx.Connection.Open();

string myQuery = "ROW(1 AS MyIndex, 'ALFKI' AS MyId)";

var myStuff = new ObjectQuery<DbDataRecord>(myQuery, ctx).ToList();

gv.DataSource = (from item in myStuff

select new

{

Index = item.GetInt32(0),

Id = item.GetString(1)

}).ToList();

}

La colección MULTISET
Las colecciones pueden ser creadas usando la palabra clave MULTISET. Por ejemplo,MULTISET (1,2,3,4,5) crea una colección de cinco números enteros. También se pueden crear colecciones simplemente mediante el uso de llaves {}.
Por ejemplo, {1,2,3,4,5} es lo mismo que MULTISET (1,2,3,4,5).

Using ctx = New ObjectContext("name=NorthwindEntities")

ctx.Connection.Open()

Dim myQuery = "SELECT i FROM {1,2,3,4,5} AS i"

Dim myStuff = New ObjectQuery(Of DbDataRecord)(myQuery, ctx).ToList()

gv.DataSource = (From i In myStuff

Select New With

{

.Index = i.GetInt32(0)

}).ToList()

End Using

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

using (ObjectContext ctx = new ObjectContext("name=NorthwindEntities"))

{

ctx.Connection.Open();

string myQuery = "SELECT i FROM {1,2,3,4,5} AS i";

var myStuff = new ObjectQuery<DbDataRecord>(myQuery, ctx).ToList();

gv.DataSource = (from item in myStuff

select new

{

Index = item.GetInt32(0),

}).ToList();

}

Trabajando con las funciones REF, CREATEREF y DEREF

La función REF devuelve una referencia a una entidad persistente. Una referencia de entidad se compone de la clave de le entidad y el nombre de un conjunto de entidades. Diferentes conjuntos entidades pueden estar basados en el mismo tipo de entidad, por lo que una clave de entidad en particular puede aparecer en múltiples conjuntos de entidades, pero una referencia a una entidad es siempre única.

Se puede eliminar la referencia que mediante el uso de la función DEREF de forma explícita, o se puede eliminar la referencia de forma implícita con sólo la invocación de una propiedad de la entidad. En el ejemplo siguiente se utiliza REF para obtener una referencia a un cliente y despues se des referencia implícitamente utilizando el punto (.) Para acceder a CompanyName.

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

Using ctx = New ObjectContext("name=NorthwindEntities")

ctx.Connection.Open()

Dim myQuery = _

"SELECT REF(c).CompanyName FROM NorthwindEntities.Customers as c"

Dim myStuff = New ObjectQuery(Of DbDataRecord)(myQuery, ctx).ToList()

gv.DataSource = (From i In myStuff

Select New With

{

.Name = i.GetString(0)

}).ToList()

End Using

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

using (ObjectContext ctx = new ObjectContext("name=NorthwindEntities"))

{

ctx.Connection.Open();

string myQuery =

"SELECT REF(c).CompanyName FROM NorthwindEntities.Customers as c";

var myStuff = new ObjectQuery<DbDataRecord>(myQuery, ctx).ToList();

gv.DataSource = (from item in myStuff

select new

{

Name = item.GetString(0),

}).ToList();

}

La función CREATEREF también devuelve una referencia a una entidad persistente, pero esta función requiere un parámetro adicional, que es la clave de la entidad que se desea localizar. El parámetro clave se pasa como una función de fila.

El siguiente ejemplo muestra el uso de CREATEREF para crear una referencia a un solo cliente, pasando en el valor de clave principal como el segundo parámetro. Después, utilizar la función DEREF para devolver el objeto real del cliente.

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

Using ctx = New ObjectContext("name=NorthwindEntities")

ctx.Connection.Open()

Dim myQuery = _

"DEREF(CREATEREF(NorthwindEntities.Customers, ROW('ALFKI')))"

gv.DataSource = New ObjectQuery(Of Customer)(myQuery, ctx).ToList()

End Using

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

using (ObjectContext ctx = new ObjectContext("name=NorthwindEntities"))

{

ctx.Connection.Open();

string myQuery = "DEREF(CREATEREF(NorthwindEntities.Customers, ROW('ALFKI')))";

gv.DataSource = new ObjectQuery<Customer>(myQuery, ctx).ToList();

}

Trabajando con Entity Sets (conjunto de entidades)

Un conjunto de entidades es como una tabla dentro del Entity Data Model. Un contenedor de entidades es como una base de datos. Si se desea recuperar todas las entidades en un conjunto de entidades, se puede escribir un ESQL simple:

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

Using ctx = New ObjectContext("name=NorthwindEntities")

ctx.Connection.Open()

Dim myQuery = "NorthwindEntities.Customers"

gv.DataSource = New ObjectQuery(Of Customer)(myQuery, ctx).ToList()

End Using

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

using (ObjectContext ctx = new ObjectContext("name=NorthwindEntities"))

{

ctx.Connection.Open();

string myQuery = "NorthwindEntities.Customers";

gv.DataSource = new ObjectQuery<Customer>(myQuery, ctx).ToList();

}

Utilizando los Métodos Query Builder
Cuando se utilizan estos métodos, puede utilizar la KeyWord para hacer referencia a la entidad actual. El siguiente ejemplo muestra el uso de los métodos del generador de consultas para filtrar un conjunto de entidades clientes y luego ordenar y crea runa proyección que devuelve el nombre de la empresa y nombre de contacto.

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

Using ctx = New ObjectContext("name=NorthwindEntities")

ctx.Connection.Open()

Dim myQuery = "NorthwindEntities.Customers"

Dim results = New ObjectQuery(Of DbDataRecord)(myQuery, ctx) _

.Where("StartsWith(it.CompanyName,'A')") _

.Select("it.CompanyName, it.ContactName") _

.OrderBy("it.ContactName") _

.ToList()

gv.DataSource = (From i In results

Select New With

{

.Company = i(0),

.Contact = i(1)

}).ToList()

End Using

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

using (ObjectContext ctx = new ObjectContext("name=NorthwindEntities"))

{

ctx.Connection.Open();

string myQuery = "NorthwindEntities.Customers";

var results = new ObjectQuery<DbDataRecord>(myQuery, ctx)

.Where("StartsWith(it.CompanyName,'A')")

.Select("it.CompanyName, it.ContactName")

.OrderBy("it.ContactName")

.ToList();

gv.DataSource = (from i in results

select new

{

Company=(string)i[0],

Contact=(string)i[1]

}).ToList();

}

Utilizando un ObjectContext para enviar los cambios a la base de datos
Modificando entidades existentes
Se puede simplemente modificar una entidad y llamar al método SaveChanges en el ObjectContext para modificar la entidad:

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

Dim db = New NorthwindEntities()

Dim customer = (From c In db.Customers

Where c.CustomerID = "ALFKI"

Select c).First()

customer.ContactTitle = "Presidente " + DateTime.Now

db.SaveChanges()

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

var db = new NorthwindEntities();

var customer = (from c in db.Customers

where c.CustomerID=="ALFKI"

select c).First();

customer.ContactTitle = "Presidente " + DateTime.Now;

db.SaveChanges();

Agregando nuevas entidades a un ObjectContext
Se puede utilizar la palabra clave new para crear una instancia de la clase y llenar todas las propiedades. Si se está trabajando con las clases generadas por Entity Framework, existe un método estático CreateObjectName que crear una instancia de la clase. Este método acepta parámetros para cada propiedad no nulable.

Cuando se crea una nueva entidad, su EntityState inicial es Detached. Se puede utilizar cualquiera de los siguientes métodos para conectar una entidad a un ObjectContext:

·                     El método AddToXxx en ObjectContext, donde xxx es el nombre de la entidad, y el método acepta un solo parámetro de tipo entidad.

·                     El método AddObject sobre ObjectSet con un parámetro, la entidad .

·                     El método AddObject sobre ObjectContext con 2 parámetros: el nombre de la entidad y la entidad.

·                     El método Add sobre EntityCollection. Para entidades generadas por Entity Framework y objetos proxy, las entidades agregadas se asocian al contexto cuando el objeto principal está asociado. Las entidades POCO se asocian al llamar al método DetectChanges.

Si la base de datos no está configurada para generar un key value, se debe asignar un valor único. Si hay varios objetos tienen el mismo valor especificados, se genera una InvalidOperationException cuando se llama a SaveChanges.

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

Dim db = New NorthwindEntities()

Dim emp = Employee.CreateEmployee(-1, "John", "Smith")

db.Employees.AddObject(emp)

db.SaveChanges()

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

var db = new NorthwindEntities();

var emp = Employee.CreateEmployee(-1, "John", "Smith");

db.Employees.AddObject(emp);

db.SaveChanges();

Asociando entidades a un ObjectContext
se puede utilizar cualquiera de estos métodos para attachar una entidad que tiene clave a un ObjectContext:

·                     Attach: método que acepta un solo parámetro, la entidad.

·                     AttachTo: acepta dos parámetros: el nombre de la entidad y la entidad que se quiere

·                     Attach: en ObjectSet, acepta un parámetro que contiene la entidad a agregar.

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

Dim db = New NorthwindEntities()

Dim customer = (From c In db.Customers

Where c.CustomerID = "ALFKI"

Select c).First()

MessageBox.Show(customer.EntityState.ToString())

db.Detach(customer)

MessageBox.Show(customer.EntityState.ToString())

db = New NorthwindEntities()

db.Customers.Attach(customer)

MessageBox.Show(customer.EntityState.ToString())

customer.ContactTitle = "Pres " + DateTime.Now

MessageBox.Show(customer.EntityState.ToString())

db.SaveChanges()

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

var db = new NorthwindEntities();

var customer = (from c in db.Customers

where c.CustomerID == "ALFKI"

select c).First();

MessageBox.Show(customer.EntityState.ToString());

db.Detach(customer);

MessageBox.Show(customer.EntityState.ToString());

db = new NorthwindEntities();

db.Customers.Attach(customer);

MessageBox.Show(customer.EntityState.ToString());

customer.ContactTitle = "Pres " + DateTime.Now;

MessageBox.Show(customer.EntityState.ToString());

db.SaveChanges();

 

Eliminando entidades

Cuando se quiere eliminar una entidad, se puede llamar al método DeleteObject del ObjectSet o del ObjectContext para marcar la entidad a eliminar. La fila no se borra de la base de datos hasta  que se llama SaveChanges.

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

Dim db = New NorthwindEntities()

Dim itemToDelete = db.Order_Details.First()

db.Order_Details.DeleteObject(itemToDelete)

db.SaveChanges()

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

var db = new NorthwindEntities();

var itemToDelete = db.Order_Details.First();

db.Order_Details.DeleteObject(itemToDelete);

db.SaveChanges();

Borrado en cascada

Cuando una entidad tiene asociaciones con otras entidades, se debe tener en cuanta lo siguiente:

·                     Si la entidad que eliminada tiene entidades dependientes (asociación de clave externa), y la clave externa de las entidades puede ser nula, Entity Framework establece la clave externa en nula cuando la entidad principal es eliminada.

·                     Si la clave principal de la entidad principal es parte de la clave principal de la entidad dependiente, se eliminan todas las entidades dependientes en cascada implícita, no es necesario definir una eliminación en cascada sobre la relación.

·                     También se puede establecer una regla de eliminación en el diseñador de Entity Data Model, haciendo clic en la relación deseada y, en la ventana Propiedades establecer la propiedad Ondelete = Cascade:

clip_image002

Es importante tener en cuenta que una eliminación en cascada en Entity Framework sólo funciona si las entidades dependientes tambien se cargan. Esto se puede lograr utilizando los métodos Include o Load, de otra forma se lanza una excepcion UpdateException.

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

Dim db = New NorthwindEntities()

Dim itemToDelete = db.Customers _

.Include("Orders") _

.Include("Orders.Order_Details") _

.First()

db.Customers.DeleteObject(itemToDelete)

db.SaveChanges()

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

var db = new NorthwindEntities();

var itemToDelete = db.Customers

.Include("Orders")

.Include("Orders.Order_Details")

.First();

db.Customers.DeleteObject(itemToDelete);

db.SaveChanges();

Resumen de la lección

  • Las consultas de LINQ to Entities se convierten en comandos que se ejecutan en el ObjectContext.
  • Se utiliza ObjectQuery para componer una consulta LINQ to Entities.
  • No todas las implementaciones de los operadores de LINQ se admiten en LINQ to Entities, debido a los diferentes tipos de almacenes de datos que soporta LINQ to Entities.
  • En lugar de utilizar AsEnumerable (que se ejecuta en el cliente), se puede implementar un procedimiento almacenado que se ejecuta del lado del servidor y podría funcionar mejor para resolver un problema.
  • ESQL proporciona una sintaxis similar a SQL para crear consultas que son independientes de la base de datos.
  • Las consultas ESQL pueden dirigirse a los Servicios de objetos y al proveedor de datos EntityClient.
  • El ObjectContext puede enviar cambios a la base de datos.
  • El método SaveChanges del ObjectContext envía la inserción apropiada, actualización o eliminación de objetos mediante comandos a la base de datos.
  • La entidad tiene una propiedad EntityState que se puede configurar como Detached, Unchanged, Modified, Added, o Deleted.
  • La Eliminación de una entidad simplemente marca la entidad para su eliminación. La entidad no se elimina hasta que se llama al método SaveChanges en el ObjectContext.
  • ObjectContext envía automáticamente las actualizaciones dentro de un contexto de transacción.

No hay comentarios:

Publicar un comentario