Resolviendo "La instancia de ObjectContext ha sido eliminada y ya no se puede usar para operaciones que requieren una conexión" InvalidOperationException
Estoy intentando completar un GridView
uso de Entity Frameworkm pero cada vez aparece el siguiente error:
"El descriptor de acceso a la propiedad 'LoanProduct' en el objeto 'COSIS_DAL.MemberLoan' arrojó la siguiente excepción: la instancia de ObjectContext se eliminó y ya no se puede usar para operaciones que requieren una conexión".
Mi código es:
public List<MemberLoan> GetAllMembersForLoan(string keyword)
{
using (CosisEntities db = new CosisEntities())
{
IQueryable<MemberLoan> query = db.MemberLoans.OrderByDescending(m => m.LoanDate);
if (!string.IsNullOrEmpty(keyword))
{
keyword = keyword.ToLower();
query = query.Where(m =>
m.LoanProviderCode.Contains(keyword)
|| m.MemNo.Contains(keyword)
|| (!string.IsNullOrEmpty(m.LoanProduct.LoanProductName) && m.LoanProduct.LoanProductName.ToLower().Contains(keyword))
|| m.Membership.MemName.Contains(keyword)
|| m.GeneralMasterInformation.Description.Contains(keyword)
);
}
return query.ToList();
}
}
protected void btnSearch_Click(object sender, ImageClickEventArgs e)
{
string keyword = txtKeyword.Text.ToLower();
LoanController c = new LoanController();
List<COSIS_DAL.MemberLoan> list = new List<COSIS_DAL.MemberLoan>();
list = c.GetAllMembersForLoan(keyword);
if (list.Count <= 0)
{
lblMsg.Text = "No Records Found";
GridView1.DataSourceID = null;
GridView1.DataSource = null;
GridView1.DataBind();
}
else
{
lblMsg.Text = "";
GridView1.DataSourceID = null;
GridView1.DataSource = list;
GridView1.DataBind();
}
}
El error es mencionar la LoanProductName
columna del Gridview
. Mencionado: Estoy usando C#, ASP.net, SQL-Server 2008 como base de datos back-end.
Soy bastante nuevo en Entity Framework. No puedo entender por qué recibo este error. ¿Alguien puede ayudarme por favor?
De forma predeterminada, Entity Framework utiliza carga diferida para las propiedades de navegación. Es por eso que estas propiedades deben marcarse como virtuales: EF crea una clase de proxy para su entidad y anula las propiedades de navegación para permitir la carga diferida. Por ejemplo, si tiene esta entidad:
public class MemberLoan
{
public string LoandProviderCode { get; set; }
public virtual Membership Membership { get; set; }
}
Entity Framework devolverá el proxy heredado de esta entidad y proporcionará una instancia de DbContext a este proxy para permitir la carga diferida de membresía más adelante:
public class MemberLoanProxy : MemberLoan
{
private CosisEntities db;
private int membershipId;
private Membership membership;
public override Membership Membership
{
get
{
if (membership == null)
membership = db.Memberships.Find(membershipId);
return membership;
}
set { membership = value; }
}
}
Entonces, la entidad tiene una instancia de DbContext que se usó para cargar la entidad. Ese es tu problema. Tiene using
un bloqueo en torno al uso de CosisEntities. Que elimina el contexto antes de que se devuelvan las entidades. Cuando más tarde algún código intenta utilizar la propiedad de navegación cargada de forma diferida, falla porque el contexto se elimina en ese momento.
Para corregir este comportamiento, puede utilizar la carga inmediata de propiedades de navegación que necesitará más adelante:
IQueryable<MemberLoan> query = db.MemberLoans.Include(m => m.Membership);
Eso precargará todas las membresías y no se utilizará la carga diferida. Para obtener más información, consulte el artículo Carga de entidades relacionadas en MSDN.
La CosisEntities
clase es tuya DbContext
. Cuando crea un contexto en un using
bloque, está definiendo los límites para su operación orientada a datos.
En su código, intenta emitir el resultado de una consulta desde un método y luego finalizar el contexto dentro del método. La operación a la que le pasa el resultado intenta acceder a las entidades para completar la vista de cuadrícula. En algún momento del proceso de vinculación a la cuadrícula, se accede a una propiedad cargada de forma diferida y Entity Framework está intentando realizar una búsqueda para obtener los valores. Falla porque el contexto asociado ya ha finalizado.
Tienes dos problemas:
Estás cargando entidades de forma diferida cuando te vinculas a la cuadrícula. Esto significa que está realizando muchas operaciones de consulta independientes en SQL Server, lo que ralentizará todo. Puede solucionar este problema haciendo que las propiedades relacionadas se carguen de forma predeterminada o solicitando a Entity Framework que las incluya en los resultados de esta consulta mediante el
Include
método de extensión.Estás terminando tu contexto prematuramente: a
DbContext
debe estar disponible en toda la unidad de trabajo que se está realizando, desechándolo solo cuando haya terminado con el trabajo en cuestión. En el caso de ASP.NET, una unidad de trabajo suele ser la solicitud HTTP que se maneja.
Línea de fondo
Su código ha recuperado datos (entidades) a través del marco de entidades con la carga diferida habilitada y después de que se haya eliminado DbContext, su código hace referencia a propiedades (entidades relacionadas/de relación/navegación) que no se solicitaron explícitamente.
Más específicamente
El InvalidOperationException
mensaje con este siempre significa lo mismo: está solicitando datos (entidades) del marco de entidades después de que se haya eliminado DbContext.
Un caso sencillo:
(estas clases se utilizarán para todos los ejemplos de esta respuesta y se supone que todas las propiedades de navegación se han configurado correctamente y tienen tablas asociadas en la base de datos)
public class Person
{
public int Id { get; set; }
public string name { get; set; }
public int? PetId { get; set; }
public Pet Pet { get; set; }
}
public class Pet
{
public string name { get; set; }
}
using (var db = new dbContext())
{
var person = db.Persons.FirstOrDefaultAsync(p => p.id == 1);
}
Console.WriteLine(person.Pet.Name);
La última línea arrojará InvalidOperationException
porque dbContext no ha deshabilitado la carga diferida y el código accede a la propiedad de navegación Pet después de que la declaración de uso haya eliminado el contexto.
Depuración
¿Cómo encuentras la fuente de esta excepción? Además de observar la excepción en sí, que se generará exactamente en el lugar donde ocurre, se aplican las reglas generales de depuración en Visual Studio: coloque puntos de interrupción estratégicos e inspeccione sus variables , ya sea colocando el mouse sobre sus nombres o abriendo un ( Ventana Quick)Watch o utilizando los distintos paneles de depuración como Locales y Automáticos.
Si desea saber dónde está o no la referencia, haga clic derecho en su nombre y seleccione "Buscar todas las referencias". Luego puede colocar un punto de interrupción en cada ubicación que solicite datos y ejecutar su programa con el depurador adjunto. Cada vez que el depurador falla en dicho punto de interrupción, debe determinar si su propiedad de navegación debería haberse completado o si los datos solicitados son necesarios.
Maneras de evitar
Deshabilitar la carga diferida
public class MyDbContext : DbContext
{
public MyDbContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
}
Ventajas: en lugar de generar InvalidOperationException, la propiedad será nula. Acceder a las propiedades de null o intentar cambiar las propiedades de esta propiedad generará una NullReferenceException .
Cómo solicitar explícitamente el objeto cuando sea necesario:
using (var db = new dbContext())
{
var person = db.Persons
.Include(p => p.Pet)
.FirstOrDefaultAsync(p => p.id == 1);
}
Console.WriteLine(person.Pet.Name); // No Exception Thrown
En el ejemplo anterior, Entity Framework materializará la Mascota además de la Persona. Esto puede resultar ventajoso porque se trata de una única llamada a la base de datos. (Sin embargo, también puede haber grandes problemas de rendimiento dependiendo de la cantidad de resultados devueltos y la cantidad de propiedades de navegación solicitadas; en este caso, no habría ninguna penalización en el rendimiento porque ambas instancias son solo un registro único y una combinación única).
o
using (var db = new dbContext())
{
var person = db.Persons.FirstOrDefaultAsync(p => p.id == 1);
var pet = db.Pets.FirstOrDefaultAsync(p => p.id == person.PetId);
}
Console.WriteLine(person.Pet.Name); // No Exception Thrown
En el ejemplo anterior, Entity Framework materializará la Mascota independientemente de la Persona realizando una llamada adicional a la base de datos. De forma predeterminada, Entity Framework rastrea los objetos que ha recuperado de la base de datos y, si encuentra propiedades de navegación que coinciden, completará automáticamente estas entidades. En este caso, debido a que PetId
el Person
objeto coincide con Pet.Id
, Entity Framework asignará el Person.Pet
al Pet
valor recuperado, antes de que el valor se asigne a la variable pet.
Siempre recomiendo este enfoque, ya que obliga a los programadores a comprender cuándo y cómo el código solicita datos a través de Entity Framework. Cuando el código genera una excepción de referencia nula en una propiedad de una entidad, casi siempre puedes estar seguro de que no has solicitado explícitamente esos datos.
Es una respuesta muy tardía pero resolví el problema desactivando la carga diferida:
db.Configuration.LazyLoadingEnabled = false;