Solución de "La instancia de ObjectContext se ha eliminado y ya no se puede utilizar para operaciones que requieren una conexión" InvalidOperationException

122

Estoy tratando de llenar un GridViewEntity Frameworkm usando pero cada vez recibo el siguiente error:

"El descriptor de acceso de propiedad 'LoanProduct' en el objeto 'COSIS_DAL.MemberLoan' produjo la siguiente excepción: la instancia de ObjectContext se ha eliminado 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 está mencionando la LoanProductNamecolumna de la Gridview. Mencionado: estoy usando C #, ASP.net, SQL-Server 2008 como base de datos de back-end.

Soy bastante nuevo en Entity Framework. No puedo entender por qué recibo este error. ¿Alguien puede ayudarme por favor?

Barsan
fuente
1
¿Está accediendo a alguna propiedad de navegación en la vista de cuadrícula? Si lo hace, también debe incluir esas tablas de navegación en la consulta. Me gustaquery.Include("SomeOtherTable")
Nilesh
Intente crear una clase proxy para alojar su entidad o al menos devolver un objeto anónimo. Desde mi punto de vista, el uso de ef requiere la creación de clases proxy para implementar sus lógicas, use edmx como la capa de acceso db, no como negocio.
Gonzix
Sí, en la vista de cuadrícula también obtengo otra columna de tabla. Que es LoanProviderName.
barsan
1
Intente db.MemberLoans.Include("LoanProduct").OrderByDescending()verificar la sintaxis porque no tengo VS frente a mí.
Nilesh
3
Solo necesita continuar incluyendo todas las propiedades de navegación a las que está accediendo fuera del contexto db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable). Compruebe las respuestas de @Tragedian y @lazyberezovsky
Nilesh

Respuestas:

174

De forma predeterminada, Entity Framework usa lazy-loading para las propiedades de navegación. Es por eso que estas propiedades deben marcarse como virtuales: EF crea una clase 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 la 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 usingbloqueado el uso de CosisEntities. Que dispone el contexto antes de que se devuelvan las entidades. Cuando algún código luego intenta usar la propiedad de navegación con carga lenta, falla, porque el contexto está dispuesto en ese momento.

Para corregir este comportamiento, puede usar la carga ansiosa de las 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 detalles, consulte el artículo Carga de entidades relacionadas en MSDN.

Sergey Berezovskiy
fuente
Muchas gracias por su útil explicación y respuesta. En realidad, aquí estoy incluyendo tres tablas, así que no sé cómo puedo agregar las tres tablas con INCLUDE. ¿Puedes ayudarme en esto por favor?
barsan
8
@barsan solo incluye todas las propiedades de navegación una por una. Por ejemplo, db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);eso generará consultas JOIN y devolverá todos los datos a la vez.
Sergey Berezovskiy
1
Muchas gracias lazyberezovsky. Te estoy muy agradecido. Me salvaste casi un día. De su explicación, estoy aprendiendo más sobre Entity Framework. Gracias mi amigo.
barsan
Gracias amigo, perfecto. Tenía una declaración de uso que limitaba la carga diferida. Gran respuesta.
ncbl
44
¿Qué sucede si no quiero incluir esas entidades relacionadas en mi consulta?
Ortund
32

La CosisEntitiesclase es tuya DbContext. Cuando crea un contexto en un usingbloque, está definiendo los límites para su operación orientada a datos.

En su código, está tratando de emitir el resultado de una consulta de 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 lugar del proceso de vinculación a la cuadrícula, se accede a una propiedad con carga lenta y Entity Framework está intentando realizar una búsqueda para obtener los valores. Falla porque el contexto asociado ya ha finalizado.

Tienes dos problemas:

  1. Son entidades de carga lenta cuando se unen a la cuadrícula. Esto significa que está realizando muchas operaciones de consulta por separado en SQL Server, que ralentizarán todo. Puede solucionar este problema haciendo que las propiedades relacionadas se carguen de manera predeterminada o solicitando a Entity Framework que las incluya en los resultados de esta consulta mediante el Includemétodo de extensión.

  2. Está finalizando su contexto prematuramente: a DbContextdebería estar disponible en toda la unidad de trabajo que se realiza, solo desechando 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.

Paul Turner
fuente
Muchas gracias por la información útil y buena explicación del problema. En realidad, soy muy nuevo en Entity Framework y en Linq, por lo que esta información es realmente una gran lección para mí.
barsan
20

Línea de fondo

Su código ha recuperado datos (entidades) a través del marco de entidad con la carga lenta habilitada y después de que se haya eliminado el 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 InvalidOperationExceptioncon este mensaje siempre significa lo mismo: está solicitando datos (entidades) de entity-framework después de que se haya eliminado el DbContext.

Un caso simple:

(estas clases se usarán para todos los ejemplos en esta respuesta, y suponen 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á el InvalidOperationExceptionporque dbContext no ha deshabilitado la carga diferida y el código está accediendo a la propiedad de navegación de Mascotas después de que el Contexto haya sido eliminado por la instrucción de uso.

Depuración

¿Cómo encuentra la fuente de esta excepción? Además de mirar la excepción en sí, que se lanzará 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 pasando el mouse sobre sus nombres, abriendo un ( Rápido) Mire la ventana o use los diversos paneles de depuración como Locales y Autos.

Si desea averiguar dónde se establece o no la referencia, haga clic con el botón 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 se interrumpe en dicho punto de interrupción, debe determinar si su propiedad de navegación debería haberse rellenado o si los datos solicitados son necesarios.

Formas de evitar

Deshabilitar carga diferida

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.LazyLoadingEnabled = false;
  }
}

Pros: en lugar de lanzar la InvalidOperationException, la propiedad será nula. Acceder a propiedades de nulo 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 ser ventajoso porque es una sola 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 penalización de rendimiento porque ambas instancias son solo un registro y una combinación).

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 al hacer 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, se rellenará automáticamente estas entidades. En este caso, debido a que el PetIden el Personobjeto coincide con el Pet.Id, Entity Framework asignará el Person.Petque el Petvalor recuperado, antes de que se le asigna el valor de la variable de mascota.

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 arroja una excepción de referencia nula en una propiedad de una entidad, casi siempre puede estar seguro de que no ha solicitado explícitamente esos datos.

Erik Philips
fuente
13

Es una respuesta muy tardía, pero resolví el problema desactivando la carga diferida:

db.Configuration.LazyLoadingEnabled = false;
Ricardo Pontual
fuente
Para mí, StackOverflow funciona de maravilla con un revestimiento. ¡Y esto lo hizo por mí, felicitaciones a ti!
Harold_Finch
Lo malo es que tienes que usar .Incluye y cosas así para cargar las propiedades de navegación.
boylec1986
1

En mi caso, estaba pasando todos los modelos 'Usuarios' a la columna y no estaba mapeado correctamente, así que simplemente pasé 'Users.Name' y lo arregló.

var data = db.ApplicationTranceLogs 
             .Include(q=>q.Users)
             .Include(q => q.LookupItems) 
             .Select(q => new { Id = q.Id, FormatDate = q.Date.ToString("yyyy/MM/dd"), ***Users = q.Users,*** ProcessType = q.ProcessType, CoreProcessId = q.CoreProcessId, Data = q.Data }) 
             .ToList();

var data = db.ApplicationTranceLogs 
             .Include(q=>q.Users).Include(q => q.LookupItems) 
             .Select(q => new { Id = q.Id, FormatDate = q.Date.ToString("yyyy/MM/dd"), ***Users = q.Users.Name***, ProcessType = q.ProcessType, CoreProcessId = q.CoreProcessId, Data = q.Data }) 
             .ToList();
Michael Mora Montero
fuente
1

La mayoría de las otras respuestas apuntan a una carga ansiosa, pero encontré otra solución.

En mi caso, tenía un objeto EF InventoryItemcon una colección de InvActivityobjetos secundarios.

class InventoryItem {
...
   // EF code first declaration of a cross table relationship
   public virtual List<InvActivity> ItemsActivity { get; set; }

   public GetLatestActivity()
   {
       return ItemActivity?.OrderByDescending(x => x.DateEntered).SingleOrDefault();
   }
...
}

Y como estaba sacando de la colección de objetos secundarios en lugar de una consulta de contexto (con IQueryable), la Include()función no estaba disponible para implementar una carga ansiosa. Entonces, en cambio, mi solución fue crear un contexto desde el que utilicé GetLatestActivity()y attach()el objeto devuelto:

using (DBContext ctx = new DBContext())
{
    var latestAct = _item.GetLatestActivity();

    // attach the Entity object back to a usable database context
    ctx.InventoryActivity.Attach(latestAct);

    // your code that would make use of the latestAct's lazy loading
    // ie   latestAct.lazyLoadedChild.name = "foo";
}

Por lo tanto, no está atrapado con la carga ansiosa.

Zorgarath
fuente
Esto es básicamente una carga ansiosa, has cargado el objeto a través de un contexto. Solo hay dos opciones; carga ansiosa y carga perezosa.
Erik Philips
@ErikPhilips bien, es una carga lenta con un nuevo contexto de datos
Zorgarath
1
@ErikPhilips - también hay carga explícita - docs.microsoft.com/en-us/ef/ef6/querying/…
Dave Black
1

Si está utilizando ASP.NET Core y asombro por la que recibe este mensaje en uno de sus métodos asincrónicos controlador, asegúrese de que regrese un Tasklugar de void- ASP.NET dispone núcleo de inyección presenta contextos.

(Estoy publicando esta respuesta ya que esta pregunta ocupa un lugar destacado en los resultados de búsqueda de ese mensaje de excepción y es un problema sutil, tal vez sea útil para las personas que lo buscan en Google).

Juan
fuente