varias instancias de IEntityChangeTracker no pueden hacer referencia al objeto de entidad. al agregar objetos relacionados a la entidad en Entity Framework 4.1

165

Estoy tratando de guardar los detalles del empleado, que tiene referencias con la ciudad. Pero cada vez que intento guardar mi contacto, que está validado, aparece la excepción "ADO.Net Entity Framework. Un objeto de entidad no puede ser referenciado por múltiples instancias de IEntityChangeTracker"

Había leído muchas publicaciones pero aún no tenía la idea exacta de qué hacer ... mi código de clic del botón Guardar se muestra a continuación

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

y código de servicio del empleado

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

Respuestas:

241

Porque estas dos líneas ...

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

... no tome un parámetro en el constructor, supongo que crea un contexto dentro de las clases. Cuando cargas el city1...

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

... adjuntas el city1al contexto en CityService. Más tarde, agrega un city1como referencia a lo nuevo Employee e1y agrega e1 esta referencia alcity1 contexto en EmployeeService. Como resultado, se ha city1vinculado a dos contextos diferentes, de los que se queja la excepción.

Puede solucionar esto creando un contexto fuera de las clases de servicio e inyectándolo y utilizándolo en ambos servicios:

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

Sus clases de servicio se parecen un poco a los repositorios que son responsables de un solo tipo de entidad. En tal caso, siempre tendrá problemas tan pronto como intervengan las relaciones entre entidades cuando utilice contextos separados para los servicios.

También puede crear un servicio único que sea responsable de un conjunto de entidades estrechamente relacionadas, como un EmployeeCityService(que tiene un contexto único) y delegar toda la operación en su Button1_Clickmétodo a un método de este servicio.

Slauma
fuente
44
Me gusta la forma en que lo descubriste, incluso si la respuesta no incluye información de fondo.
Daniel Kmak
Parece que esto resolverá mi problema, simplemente no tengo idea de cómo escribir la nueva instancia de contexto :(
Ortund
12
Resumir ORM es como poner un lápiz labial amarillo en un turd.
Ronnie Overby
Puede que me falte algo aquí, pero en algunos ORM (especialmente EntityFramework) el contexto de datos siempre debe ser breve. La introducción de un contexto estático o reutilizado introducirá otro conjunto de desafíos y problemas.
Maritim
@ Maritim depende del uso. En aplicaciones web, generalmente es un viaje de ida y vuelta. En las aplicaciones de escritorio, también puede usar uno por Form(lo que sea, solo representa una unidad de trabajo) por Thread(porque DbContextno se garantiza que sea seguro para subprocesos).
LuckyLikey
30

Los pasos para reproducir se pueden simplificar a esto:

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 sin error:

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

Usando solo uno EntityContext puede resolver esto. Consulte otras respuestas para otras soluciones.

Pavel Shkleinik
fuente
2
digamos que quisieras usar contextTwo? (tal vez debido a problemas de alcance o algo así) ¿cómo se separa de contextOne y se adjunta a contextTwo?
NullVoxPopuli
Si necesita hacer algo así, lo más probable es que lo esté haciendo de manera incorrecta ... Sugiero usar un contexto.
Pavel Shkleinik
3
Hay instancias en las que desearía usar una instancia diferente, como cuando apunta a una base de datos diferente.
Jay
1
Esta es una simplificación útil del problema; pero no proporciona una respuesta real.
BrainSlugs83
9

Este es un hilo antiguo, pero otra solución, que prefiero, es simplemente actualizar cityId y no asignar el modelo de agujero City a Employee ... para hacer eso, Employee debería verse así:

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

Entonces es suficiente asignar:

e1.CityId=city1.ID;
usuario3484623
fuente
5

Alternativamente a la inyección y, lo que es peor, a Singleton, puede llamar al método Separar antes de Agregar.

EntityFramework 6: ((IObjectContextAdapter)cs).ObjectContext.Detach(city1);

EntityFramework 4: cs.Detach(city1);

Hay otra forma, en caso de que no necesite el primer objeto DBContext. Simplemente envuélvelo usando la palabra clave:

Payroll.Entities.City city1;
using (CityService cs = new CityService())
{
  city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));
}
O romano
fuente
1
Usé lo siguiente: dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached'para separar y luego pude usar dbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified;para actualizar. Trabajó como un sueño
Peter Smith
Si Peter Debo mencionar marcar el estado como modificado.
Roman O
En la lógica de inicio de mi aplicación (global.asax) estaba cargando una lista de widgets ... una simple lista de objetos de referencia que guardo en la memoria. Como estaba haciendo mi contexto EF en el uso de declaraciones, pensé que no habría problemas más tarde cuando mi controlador asignara esos objetos a un gráfico de negocios (oye, ese contexto anterior desapareció, ¿verdad?) - esta respuesta me salvó .
bkwdesign
4

Tuve el mismo problema, pero mi problema con la solución de @ Slauma (aunque excelente en algunos casos) es que recomienda pasar el contexto al servicio, lo que implica que el contexto está disponible desde mi controlador. También fuerza un fuerte acoplamiento entre mi controlador y las capas de servicio.

Estoy usando Dependency Injection para inyectar las capas de servicio / repositorio en el controlador y, como tal, no tengo acceso al contexto desde el controlador.

Mi solución fue hacer que las capas de servicio / repositorio usaran la misma instancia del contexto: Singleton.

Contexto Singleton Class:

Referencia: http://msdn.microsoft.com/en-us/library/ff650316.aspx
y 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;
    }
  }
}  

Clase de repositorio:

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

Existen otras soluciones, como instanciar el contexto una vez y pasarlo a los constructores de sus capas de servicio / repositorio u otro que leí sobre el que está implementando el patrón de Unidad de trabajo. Estoy seguro de que hay más ...

kmullings
fuente
9
... ¿no se rompe esto tan pronto como intentas usar multihilo?
CaffGeek
8
Un contexto no debe permanecer abierto más tiempo del necesario, lo último que desea hacer es usar un Singleton para mantenerlo abierto para siempre.
enzi
3
He visto buenas implementaciones de esto por solicitud. El uso de la palabra clave Estática es incorrecto, pero si realiza este patrón para crear una instancia del contexto al comienzo de la solicitud y eliminarlo al final de la solicitud, sería una solución legítima.
Aidin
1
Este es realmente un mal consejo. Si está utilizando DI (¿no veo la evidencia aquí?), Entonces debe permitir que su contenedor DI administre la vida útil del contexto y probablemente debería ser por solicitud.
Casey
3
Esto es malo. MALO. MALO. MALO. MALO. Particularmente si se trata de una aplicación web, ya que los objetos estáticos se comparten entre todos los hilos y usuarios. Esto significa que múltiples usuarios simultáneos de su sitio web pisotearán su contexto de datos, posiblemente corrompiéndolo, guardando cambios que no pretendía o incluso creando bloqueos aleatorios. DbContexts NUNCA debe compartirse entre subprocesos. Luego está el problema de que la estática nunca se destruye, por lo que se quedará y seguirá usando más y más memoria ...
Erik Funkenbusch
3

En mi caso, estaba usando ASP.NET Identity Framework. Había usado el UserManager.FindByNameAsyncmétodo incorporado para recuperar una ApplicationUserentidad. Luego intenté hacer referencia a esta entidad en una entidad recién creada en una diferente DbContext. Esto resultó en la excepción que viste originalmente.

Resolví esto creando una nueva ApplicationUserentidad con solo Idel UserManagermétodo y haciendo referencia a esa nueva entidad.

Justin Skiles
fuente
1

Tuve el mismo problema y pude resolver haciendo una nueva instancia del objeto que estaba tratando de actualizar. Luego pasé ese objeto a mi repositorio.

karolanet333
fuente
¿Pueden ayudarme con un código de muestra? ? para que quede claro lo que estás tratando de decir
BJ Patel
1

En este caso, resulta que el error es muy claro: Entity Framework no puede rastrear una entidad utilizando múltiples instancias IEntityChangeTrackero, por lo general, múltiples instancias de DbContext. Las soluciones son: use una instancia de DbContext; acceder a todas las entidades necesarias a través de un único repositorio (dependiendo de una instancia de DbContext); o desactivar el seguimiento para todas las entidades a las que se accede a través de un repositorio que no sea el que arroja esta excepción particular.

Cuando sigo una inversión del patrón de control en .Net Core Web API, con frecuencia encuentro que tengo controladores con dependencias 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;
}

y 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!

Dado que los tres repositorios dependen de diferentes DbContextinstancias por solicitud, tengo dos opciones para evitar el problema y mantener repositorios separados: cambiar la inyección de DbContext para crear una nueva instancia solo una vez por llamada:

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

o, si la entidad secundaria se usa de manera de solo lectura, desactivando el seguimiento en esa instancia:

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

Use el mismo objeto DBContext en toda la transacción.

Nalan Madheswaran
fuente
0

Llegué a este mismo problema después de implementar IoC para un proyecto (ASP.Net MVC EF6.2).

Por lo general, inicializaría un contexto de datos en el constructor de un controlador y usaría el mismo contexto para inicializar todos mis repositorios.

Sin embargo, usar IoC para crear instancias de los repositorios hizo que todos tuvieran contextos separados y comencé a recibir este error.

Por ahora he vuelto a actualizar los repositorios con un contexto común mientras pienso en una mejor manera.

Richard Moore
fuente
0

Así es como me encontré con este problema. Primero necesito guardar mi Orderque necesita una referencia a mi ApplicationUsertabla:

  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

El problema es que estoy inicializando un nuevo ApplicationDbContext para guardar mi nueva Orderentidad:

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

Entonces, para resolver el problema, utilicé el mismo ApplicationDbContext en lugar de usar el UserManager incorporado de ASP.NET MVC.

En lugar de esto:

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

Usé mi instancia actual de 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
fuente
-2

Fuente de error:

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 alguien ahorre un tiempo precioso

Bourne Kolo
fuente
No estoy seguro de que esto responda la pregunta. Quizás algún contexto ayudaría.
Stuart Siegler