Resolvendo “A instância ObjectContext foi descartada e não pode mais ser usada para operações que requerem uma conexão” InvalidOperationException

122

Estou tentando preencher um GridViewEntity Frameworkm usando, mas sempre que recebo o seguinte erro:

"O acessador de propriedade 'LoanProduct' no objeto 'COSIS_DAL.MemberLoan' lançou a seguinte exceção: A instância ObjectContext foi descartada e não pode mais ser usada para operações que requerem uma conexão."

Meu código é:

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();
    }
}

O erro está mencionando a LoanProductNamecoluna do Gridview. Mencionado: estou usando C #, ASP.net, SQL-Server 2008 como banco de dados de back-end.

Eu sou bastante novo no Entity Framework. Não consigo entender por que estou recebendo esse erro. Alguém pode me ajudar por favor?

barsan
fonte
1
Você está acessando propriedades de navegação na grade? Se você fizer isso, também precisará incluir essas tabelas de navegação na consulta. Comoquery.Include("SomeOtherTable")
Nilesh
Tente criar uma classe proxy para hospedar sua entidade ou, pelo menos, retornar um objeto anônimo. Do meu ponto de vista, o uso de ef requer a criação de classes proxy para implementar suas lógicas, use o edmx assim como a camada de acesso ao banco de dados e não os negócios.
Gonzix 23/08/13
sim no gridview estou recebendo outra coluna da tabela também. Qual é LoanProviderName.
Barsan
1
Tente db.MemberLoans.Include("LoanProduct").OrderByDescending()verificar a sintaxe, pois não tenho o VS na minha frente.
Nilesh
3
Você só precisa continuar incluindo todas as propriedades de navegação que está acessando fora do contexto db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable). Verifique as respostas de @Tragedian e @lazyberezovsky
Nilesh

Respostas:

174

Por padrão, o Entity Framework usa carregamento lento para propriedades de navegação. É por isso que essas propriedades devem ser marcadas como virtuais - o EF cria uma classe de proxy para sua entidade e substitui as propriedades de navegação para permitir o carregamento lento. Por exemplo, se você possui esta entidade:

public class MemberLoan
{
   public string LoandProviderCode { get; set; }
   public virtual Membership Membership { get; set; }
}

O Entity Framework retornará o proxy herdado dessa entidade e fornecerá a instância DbContext a esse proxy para permitir o carregamento lento da associação posteriormente:

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; }
    }
}

Portanto, a entidade possui uma instância do DbContext que foi usada para carregar a entidade. Esse é o seu problema. Você usingbloqueou o uso do CosisEntities. Que descarta o contexto antes que as entidades sejam retornadas. Quando algum código tenta posteriormente usar a propriedade de navegação com carregamento lento, ele falha, porque o contexto é descartado naquele momento.

Para corrigir esse comportamento, você pode usar o carregamento rápido das propriedades de navegação, necessárias mais tarde:

IQueryable<MemberLoan> query = db.MemberLoans.Include(m => m.Membership);

Isso pré-carregará todas as associações e o carregamento lento não será usado. Para obter detalhes, consulte o artigo Carregando entidades relacionadas no MSDN.

Sergey Berezovskiy
fonte
Muito obrigado pela sua explicação e resposta úteis. Na verdade, aqui estou incluindo três tabelas, então não sei como posso adicionar as três tabelas com INCLUDE. você pode por favor me ajudar nisso por favor.
Barsan
8
O @barsan inclui apenas todas as propriedades de navegação, uma por uma. Por exemplo, db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);isso gerará a consulta JOIN e retornará todos os dados de uma só vez.
Sergey Berezovskiy
1
Muito obrigado lazyberezovsky. Sou muito grato a você. Você me salvou quase um dia. Pela sua explicação, estou aprendendo mais sobre o Entity Framework. Obrigada meu amigo
Barsan
Obrigado companheiro, perfeito. Eu tinha uma declaração de uso que estava limitando o carregamento lento. Ótima resposta.
Ncbl 08/11
4
E se eu não quiser incluir essas entidades relacionadas na minha consulta?
Ortund
32

A CosisEntitiesturma é sua DbContext. Ao criar um contexto em um usingbloco, você define os limites para sua operação orientada a dados.

No seu código, você está tentando emitir o resultado de uma consulta de um método e finalizar o contexto dentro do método. A operação para a qual você passa o resultado tenta acessar as entidades para preencher a visualização em grade. Em algum lugar do processo de ligação à grade, uma propriedade de carregamento lento está sendo acessada e o Entity Framework está tentando executar uma pesquisa para obter os valores. Falha, porque o contexto associado já terminou.

Você tem dois problemas:

  1. Você é uma entidade de carregamento lento quando liga à grade. Isso significa que você está executando muitas operações de consulta separadas no SQL Server, o que atrasará tudo. Você pode corrigir esse problema, tornando as propriedades relacionadas prontamente carregadas por padrão ou solicitando que o Entity Framework as inclua nos resultados dessa consulta usando o Includemétodo de extensão

  2. Você está encerrando seu contexto prematuramente: a DbContextdeve estar disponível em toda a unidade de trabalho que está sendo executada, descartando-a somente quando você terminar o trabalho em mãos. No caso do ASP.NET, uma unidade de trabalho normalmente é a solicitação HTTP sendo manipulada.

Paul Turner
fonte
Muito obrigado pelas informações úteis e boa explicação do problema. Na verdade, eu sou tão novo no Entity Framework, bem como no Linq, então essas informações são realmente uma ótima lição para eu aprender.
Barsan
20

Bottom Line

Seu código recuperou dados (entidades) por meio de estrutura de entidade com carregamento lento ativado e depois que o DbContext foi descartado, seu código está fazendo referência a propriedades (entidades relacionadas / de relacionamento / entidades de navegação) que não foram solicitadas explicitamente.

Mais especificamente

O InvalidOperationExceptioncom esta mensagem sempre significa a mesma coisa: você está solicitando dados (entidades) de entidade-quadro após a DbContext foi descartado.

Um caso simples:

(essas classes serão usadas para todos os exemplos nesta resposta e assumirão que todas as propriedades de navegação foram configuradas corretamente e têm tabelas associadas no banco de dados)

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);

A última linha lançará o InvalidOperationExceptionporque o dbContext não desativou o carregamento lento e o código está acessando a propriedade de navegação Pet após o contexto ser descartado pela instrução using.

Depuração

Como você encontra a fonte dessa exceção? Além de observar a exceção em si, que será lançada exatamente no local em que ocorre, as regras gerais de depuração no Visual Studio se aplicam: coloque pontos de interrupção estratégicos e inspecione suas variáveis , passando o mouse sobre seus nomes, abrindo um ( Rápido) Observe a janela ou use os vários painéis de depuração, como Locals e Autos.

Se você deseja descobrir onde a referência está ou não está definida, clique com o botão direito do mouse no nome e selecione "Localizar todas as referências". Você pode colocar um ponto de interrupção em qualquer local que solicite dados e executar seu programa com o depurador conectado. Sempre que o depurador interrompe esse ponto de interrupção, é necessário determinar se sua propriedade de navegação deveria ter sido preenchida ou se os dados solicitados são necessários.

Maneiras de evitar

Desativar carregamento lento

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

Prós: em vez de lançar InvalidOperationException, a propriedade será nula. Acessar propriedades null ou tentar alterar as propriedades dessa propriedade lançará uma NullReferenceException .

Como solicitar explicitamente o objeto quando necessário:

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

No exemplo anterior, o Entity Framework materializará o animal de estimação além da pessoa. Isso pode ser vantajoso porque é uma chamada única para o banco de dados. (No entanto, também pode haver enormes problemas de desempenho, dependendo do número de resultados retornados e do número de propriedades de navegação solicitadas, nesse caso, não haverá penalidade no desempenho, pois ambas as instâncias são apenas um registro e uma associação única).

ou

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

No exemplo anterior, o Entity Framework materializará o Pet independentemente da Pessoa, fazendo uma chamada adicional ao banco de dados. Por padrão, o Entity Framework rastreia os objetos recuperados do banco de dados e, se encontrar propriedades de navegação correspondentes, preencherá automaticamente essas entidades. Nesse caso, porque PetIdon do Personobjeto corresponde ao Pet.Id, o Entity Framework atribui o Person.Petao Petvalor recuperado, antes que o valor seja atribuído à variável pet.

Eu sempre recomendo essa abordagem, pois força os programadores a entender quando e como o código solicita dados via Entity Framework. Quando o código lança uma exceção de referência nula na propriedade de uma entidade, quase sempre você pode ter certeza de que não solicitou esses dados explicitamente.

Erik Philips
fonte
13

É uma resposta muito tardia, mas resolvi o problema desativando o carregamento lento:

db.Configuration.LazyLoadingEnabled = false;
Ricardo Pontual
fonte
Para mim, o StackOverflow faz maravilhas com um revestimento. E isso fez por mim, parabéns a você!
Harold_Finch 14/08
A desvantagem é que você precisa usar o .Include e coisas assim para carregar as propriedades de navegação.
boylec1986
1

No meu caso, eu estava passando todos os modelos 'Users' para a coluna e não foram mapeados corretamente, então passei apenas 'Users.Name' e ele foi corrigido.

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
fonte
1

A maioria das outras respostas aponta para um carregamento ansioso, mas encontrei outra solução.

No meu caso, eu tinha um objeto EF InventoryItemcom uma coleção de InvActivityobjetos filho.

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();
   }
...
}

E como eu estava retirando da coleção de objetos filho em vez de uma consulta de contexto (com IQueryable), a Include()função não estava disponível para implementar o carregamento antecipado. Então, em vez disso, minha solução foi criar um contexto a partir do qual eu utilizei GetLatestActivity()e attach()o objeto retornado:

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";
}

Portanto, você não está preso a um carregamento ansioso.

Zorgarath
fonte
Isso é basicamente um carregamento ansioso, você carregou o objeto através de um contexto. Existem apenas duas opções; carregamento ansioso e carregamento preguiçoso.
Erik Philips
@ErikPhilips certo, é o carregamento lento com um novo contexto de dados
Zorgarath
1
@ErikPhilips - também há carregamento explícito - docs.microsoft.com/en-us/ef/ef6/querying/…
Dave Black
1

Se você estiver usando o ASP.NET Core e se perguntar por que recebeu essa mensagem em um dos métodos de controlador assíncrono, retorne um Taskpoucovoid - o ASP.NET Core descarta os contextos injetados.

(Estou postando esta resposta, pois essa pergunta é alta nos resultados da pesquisa para essa mensagem de exceção e é um problema sutil - talvez seja útil para as pessoas que pesquisam no Google.)

John
fonte