O objeto de entidade não pode ser referenciado por várias instâncias do IEntityChangeTracker. ao adicionar objetos relacionados à entidade no Entity Framework 4.1

165

Estou tentando salvar os detalhes dos funcionários, que têm referências com a cidade. Mas sempre que tento salvar meu contato, que é validado, recebo a exceção "ADO.Net Entity Framework Um objeto de entidade não pode ser referenciado por várias instâncias do IEntityChangeTracker"

Eu tinha lido muitos posts, mas ainda não entendi exatamente o que fazer ... meu código de clique no botão Salvar é mostrado abaixo

protected void Button1_Click(object sender, EventArgs e)
    {
        EmployeeService es = new EmployeeService();
        CityService cs = new CityService();

        DateTime dt = new DateTime(2008, 12, 12);
        Payroll.Entities.Employee e1 = new Payroll.Entities.Employee();

        Payroll.Entities.City city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));

        e1.Name = "Archana";
        e1.Title = "aaaa";
        e1.BirthDate = dt;
        e1.Gender = "F";
        e1.HireDate = dt;
        e1.MaritalStatus = "M";
        e1.City = city1;        

        es.AddEmpoyee(e1,city1);
    }

e Código Employeeservice

public string AddEmpoyee(Payroll.Entities.Employee e1, Payroll.Entities.City c1)
        {
            Payroll_DAO1 payrollDAO = new Payroll_DAO1();
            payrollDAO.AddToEmployee(e1);  //Here I am getting Error..
            payrollDAO.SaveChanges();
            return "SUCCESS";
        }
Smily
fonte

Respostas:

241

Porque essas duas linhas ...

EmployeeService es = new EmployeeService();
CityService cs = new CityService();

... não use um parâmetro no construtor, acho que você cria um contexto dentro das classes. Quando você carrega o city1...

Payroll.Entities.City city1 = cs.SelectCity(...);

... você anexa o city1ao contexto em CityService. Posteriormente, você adiciona a city1como referência ao novo Employee e1e inclui a e1 inclusão dessa referênciacity1 no contexto EmployeeService. Como resultado, você city1anexou dois contextos diferentes, dos quais a exceção reclama.

Você pode corrigir isso criando um contexto fora das classes de serviço e injetando e usando-o nos dois serviços:

EmployeeService es = new EmployeeService(context);
CityService cs = new CityService(context); // same context instance

Suas classes de serviço se parecem um pouco com repositórios responsáveis ​​por apenas um tipo de entidade. Nesse caso, você sempre terá problemas assim que os relacionamentos entre as entidades estiverem envolvidos ao usar contextos separados para os serviços.

Você também pode criar um único serviço responsável por um conjunto de entidades estreitamente relacionadas, como um EmployeeCityService(que possui um único contexto) e delegar toda a operação do seu Button1_Clickmétodo a um método desse serviço.

Slauma
fonte
4
Gosto da maneira como você descobriu, mesmo que a resposta não incluísse algumas informações básicas.
Daniel Kmak
Parece que este vai resolver o meu problema, eu simplesmente não tenho idéia de como escrever a nova instância contexto :(
Ortund
12
Abstrair ORM é como colocar batom amarelo em um pedaço.
Ronnie Overby 24/16
Talvez esteja faltando alguma coisa aqui, mas em alguns ORMs (especialmente EntityFramework) o contexto dos dados sempre deve ter vida curta. A introdução de um contexto estático ou reutilizado apresentará todo um outro conjunto de desafios e problemas.
Maritim
@ Maritim depende do uso. Em aplicativos da Web, normalmente é uma ida e volta. Nos aplicativos de área de trabalho, você também pode usar um por Form(no entanto, apenas representa uma unidade de trabalho) por Thread(porque DbContextnão é garantido que seja seguro para threads).
precisa saber é o seguinte
30

As etapas para reprodução podem ser simplificadas para isso:

var contextOne = new EntityContext();
var contextTwo = new EntityContext();

var user = contextOne.Users.FirstOrDefault();

var group = new Group();
group.User = user;

contextTwo.Groups.Add(group);
contextTwo.SaveChanges();

Código sem erro:

var context = new EntityContext();

var user = context.Users.FirstOrDefault();

var group = new Group();
group.User = user; // Be careful when you set entity properties. 
// Be sure that all objects came from the same context

context.Groups.Add(group);
context.SaveChanges();

Usar apenas um EntityContextpode resolver isso. Consulte outras respostas para outras soluções.

Pavel Shkleinik
fonte
2
digamos que você queira usar o contextTwo? (talvez devido a problemas de escopo ou algo assim) como você se desanexa do contextOne e anexa ao contextTwo?
NullVoxPopuli
Se você precisa fazer algo assim, provavelmente está fazendo isso de maneira errada ... sugiro usar um contexto.
Pavel Shkleinik
3
Há instâncias em que você deseja usar uma instância diferente, como ao apontar para um banco de dados diferente.
Jay
1
Esta é uma simplificação útil do problema; mas não fornece uma resposta real.
BrainSlugs83
9

Este é um encadeamento antigo, mas outra solução, que eu prefiro, é apenas atualizar o cityId e não atribuir o modelo de furo City ao Employee ... para fazer com que o Employee se pareça com:

public class Employee{
    ...
    public int? CityId; //The ? is for allow City nullable
    public virtual City City;
}

Então basta atribuir:

e1.CityId=city1.ID;
user3484623
fonte
5

Como alternativa à injeção e, pior ainda, ao Singleton, você pode chamar o método Detach antes de Adicionar.

Enquadramento da entidade 6: ((IObjectContextAdapter)cs).ObjectContext.Detach(city1);

Enquadramento da entidade 4: cs.Detach(city1);

Existe ainda outra maneira, caso você não precise do primeiro objeto DBContext. Basta envolvê-lo usando a palavra-chave:

Payroll.Entities.City city1;
using (CityService cs = new CityService())
{
  city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));
}
Roman O
fonte
1
Eu usei o seguinte: dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached'para desanexar e, em seguida, fui capaz de usar dbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified;para atualizar. Trabalhou como um sonho
Peter Smith
Sim Peter. Eu devo mencionar para marcar State como Modified.
Roman O
Na lógica de inicialização do aplicativo (global.asax), eu estava carregando uma lista de widgets. Uma lista simples de objetos de referência que eu escondia na memória. Como eu estava fazendo o meu contexto EF dentro das instruções Using, pensei que não haveria problema mais tarde, quando meu controlador pudesse atribuir esses objetos a um gráfico comercial (ei, esse antigo contexto se foi, certo?) - essa resposta me salvou .
bkwdesign
4

Eu tive o mesmo problema, mas meu problema com a solução do @ Slauma (embora ótimo em alguns casos) é que ele recomenda que eu transmita o contexto para o serviço, o que implica que o contexto está disponível no meu controlador. Isso também força o acoplamento rígido entre meu controlador e as camadas de serviço.

Estou usando a Injeção de Dependência para injetar as camadas de serviço / repositório no controlador e, como tal, não têm acesso ao contexto do controlador.

Minha solução foi fazer com que as camadas de serviço / repositório usassem a mesma instância do contexto - Singleton.

Classe Singleton de contexto:

Referência: http://msdn.microsoft.com/en-us/library/ff650316.aspx
e http://csharpindepth.com/Articles/General/Singleton.aspx

public sealed class MyModelDbContextSingleton
{
  private static readonly MyModelDbContext instance = new MyModelDbContext();

  static MyModelDbContextSingleton() { }

  private MyModelDbContextSingleton() { }

  public static MyModelDbContext Instance
  {
    get
    {
      return instance;
    }
  }
}  

Classe de Repositório:

public class ProjectRepository : IProjectRepository
{
  MyModelDbContext context = MyModelDbContextSingleton.Instance;
  [...]

Existem outras soluções, como instanciar o contexto uma vez e transmiti-lo aos construtores de suas camadas de serviço / repositório ou outra que li sobre o que está implementando o padrão da Unidade de Trabalho. Tenho certeza que existem mais ...

kmullings
fonte
9
... isso não ocorre assim que você tenta usar o multithreading?
CaffGeek
8
Um contexto não deve permanecer aberto por mais tempo que o necessário, usar um Singleton para mantê-lo aberto para sempre é a última coisa que você deseja fazer.
enzi 30/07/2013
3
Vi boas implementações disso por solicitação. O uso da palavra-chave Static está errado, mas se você criar esse padrão para instanciar o contexto no início da solicitação e descartá-lo no final da solicitação, seria uma solução legítima.
precisa
1
Este é um péssimo conselho. Se você estiver usando DI (não vejo as evidências aqui?), Deixe seu contêiner de DI gerenciar o tempo de vida do contexto e provavelmente deve ser por solicitação.
Casey #
3
Isto é mau. RUIM. RUIM. RUIM. RUIM. Especialmente se for um aplicativo da Web, pois objetos estáticos são compartilhados entre todos os threads e usuários. Isso significa que vários usuários simultâneos do seu site estarão pisando no seu contexto de dados, potencialmente corrompendo-o, salvando as alterações que você não pretendia ou até mesmo criando falhas aleatórias. DbContexts NUNCA deve ser compartilhado entre threads. Depois, há o problema de que a estática não são destruídas, por isso vai sentar e continuar usando mais e mais memória ...
Erik Funkenbusch
3

No meu caso, eu estava usando o ASP.NET Identity Framework. Eu tinha usado o UserManager.FindByNameAsyncmétodo interno para recuperar uma ApplicationUserentidade. Eu tentei fazer referência a essa entidade em uma entidade recém-criada em uma diferente DbContext. Isso resultou na exceção que você viu originalmente.

Resolvi isso criando uma nova ApplicationUserentidade apenas com Ido UserManagermétodo from e fazendo referência a essa nova entidade.

Justin Skiles
fonte
1

Eu tinha o mesmo problema e poderia resolver criando uma nova instância do objeto que estava tentando atualizar. Depois passei esse objeto para o meu repositório.

karolanet333
fonte
Você pode ajudar com o código de exemplo. ? por isso vai ser claro o que você está tentando dizer
BJ Patel
1

Nesse caso, o erro é muito claro: o Entity Framework não pode rastrear uma entidade usando várias instâncias de IEntityChangeTrackerou, normalmente, várias instâncias de DbContext. As soluções são: use uma instância de DbContext; acessar todas as entidades necessárias por meio de um único repositório (dependendo de uma instância doDbContext ); ou desativar o rastreamento de todas as entidades acessadas por um repositório que não seja o que gera essa exceção específica.

Ao seguir uma inversão do padrão de controle na .Net Core Web API, frequentemente descubro que tenho controladores com dependências como:

private readonly IMyEntityRepository myEntityRepo; // depends on MyDbContext
private readonly IFooRepository fooRepo; // depends on MyDbContext
private readonly IBarRepository barRepo; // depends on MyDbContext
public MyController(
    IMyEntityRepository myEntityRepo, 
    IFooRepository fooRepo, 
    IBarRepository barRepo)
{
    this.fooRepo = fooRepo;
    this.barRepo = barRepo;
    this.myEntityRepo = myEntityRepo;
}

e uso como

...
myEntity.Foo = await this.fooRepository.GetFoos().SingleOrDefaultAsync(f => f.Id == model.FooId);
if (model.BarId.HasValue)
{
    myEntity.Foo.Bar = await this.barRepository.GetBars().SingleOrDefaultAsync(b => b.Id == model.BarId.Value);
}

...
await this.myEntityRepo.UpdateAsync(myEntity); // this throws an error!

Como todos os três repositórios dependem de DbContextinstâncias diferentes por solicitação, tenho duas opções para evitar o problema e manter repositórios separados: altere a injeção do DbContext para criar uma nova instância apenas uma vez por chamada:

// services.AddTransient<DbContext, MyDbContext>(); <- one instance per ctor. bad
services.AddScoped<DbContext, MyDbContext>(); // <- one instance per call. good!

ou, se a entidade filha estiver sendo usada somente para leitura, desative o rastreamento nessa instância:

myEntity.Foo.Bar = await this.barRepo.GetBars().AsNoTracking().SingleOrDefault(b => b.Id == model.BarId);
Kjata30
fonte
0

Use o mesmo objeto DBContext ao longo da transação.

Nalan Madheswaran
fonte
0

Eu encontrei esse mesmo problema depois de implementar a IoC para um projeto (ASP.Net MVC EF6.2).

Normalmente, eu inicializava um contexto de dados no construtor de um controlador e usava o mesmo contexto para inicializar todos os meus repositórios.

No entanto, o uso da IoC para instanciar os repositórios fez com que todos tivessem contextos separados e comecei a receber esse erro.

Por enquanto, voltei a apenas atualizar os repositórios com um contexto comum, enquanto penso em uma maneira melhor.

Richard Moore
fonte
0

Foi assim que encontrei esse problema. Primeiro, preciso salvar o meu, Orderque precisa de uma referência à minha ApplicationUsertabela:

  ApplicationUser user = new ApplicationUser();
  user = UserManager.FindById(User.Identity.GetUserId());

  Order entOrder = new Order();
  entOrder.ApplicationUser = user; //I need this user before saving to my database using EF

O problema é que estou inicializando um novo ApplicationDbContext para salvar minha nova Orderentidade:

 ApplicationDbContext db = new ApplicationDbContext();
 db.Entry(entOrder).State = EntityState.Added;
 db.SaveChanges();

Portanto, para resolver o problema, usei o mesmo ApplicationDbContext em vez de usar o UserManager interno do ASP.NET MVC.

Em vez disso:

user = UserManager.FindById(User.Identity.GetUserId());

Eu usei minha instância existente ApplicationDbContext:

//db instance here is the same instance as my db on my code above.
user = db.Users.Find(User.Identity.GetUserId()); 
Willy David Jr
fonte
-2

Origem do erro:

ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.Name);
ApplicationDbContext db = new ApplicationDbContent();
db.Users.Uploads.Add(new MyUpload{FileName="newfile.png"});
await db.SavechangesAsync();/ZZZZZZZ

Espero que alguém economize um tempo precioso

Bourne Kolo
fonte
Não tenho certeza se isso responde à pergunta. Talvez algum contexto ajude.
Stuart Siegler