Об'єкт сутності не може посилатися на кілька екземплярів IEntityChangeTracker. під час додавання суміжних об'єктів до сутності в Entity Framework 4.1


165

Я намагаюся зберегти дані працівника, які мають посилання на City. Але кожного разу, коли я намагаюся зберегти свій контакт, який підтверджений, я отримую виняток "ADO.Net Entity Framework" Об'єкт сутності не може бути посилається на кілька екземплярів IEntityChangeTracker "

Я прочитав стільки публікацій, але все ще не отримую точного уявлення про те, що робити ... мій код натискання кнопки "Зберегти" наведено нижче

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

та Код обслуговування службовців

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

Відповіді:


241

Тому що ці два рядки ...

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

... не приймайте параметр у конструкторі, я думаю, що ви створюєте контекст у класах. Коли ви завантажуєте city1...

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

... ви додаєте city1до контексту в CityService. Пізніше ви додаєте city1як посилання на нове Employee e1і додаєте, e1 включаючи це посилання, наcity1 контекст у EmployeeService. В результаті ви city1приєдналися до двох різних контекстів, на що скаржиться виняток.

Ви можете виправити це, створивши контекст поза класами обслуговування та ввівши його та використовуючи в обох службах:

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

Ваші класи обслуговування схожі на сховища, які відповідають лише за один тип сутності. У такому випадку у вас завжди будуть проблеми, як тільки стосунки між сутностями будуть задіяні, коли ви використовуєте окремі контексти для служб.

Ви також можете створити єдиний сервіс, який відповідає за набір тісно пов’язаних сутностей, наприклад EmployeeCityService(який має єдиний контекст) і делегувати всю операцію у вашому Button1_Clickметоді методу цієї служби.


4
Мені подобається, як ти це зрозумів, навіть якщо відповідь не містила деякої довідкової інформації.
Даніель Кмак

Схоже, це вирішить мою проблему, я просто не маю уявлення, як написати новий екземпляр контексту :(
Ortund

12
Абстрагувати ORM - це як надягати жовту помаду на каструлю.
Ронні Овербі

Можливо, мені щось тут не вистачає, але в деяких ORM (особливо EntityFramework) контекст даних завжди повинен бути недоліком. Введення статичного або повторно використаного контексту введе цілий інший набір викликів і проблем.
Марітим

@Maritim це залежить від використання. У веб-додатках це звичайно один напрямок. У Настільних додатках ви також можете використовувати один на кожний Form(що завгодно , це лише одна одиниця роботи) на кожну Thread(тому що DbContextне гарантується безпека потоку).
LuckyLikey

30

Кроки до відтворення можна спростити до цього:

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

Код без помилки:

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

Використовуючи лише один, EntityContextможна вирішити це. Інші відповіді див. Для інших рішень.


2
скажемо, що ви хочете використовувати контекстДво? (можливо, через проблеми із сферою чи щось), як відірватися від контексту одного та приєднатись до контексту?
NullVoxPopuli

Якщо вам потрібно зробити щось подібне, швидше за все, ви робите це неправильно ... Я пропоную використовувати один контекст.
Павло Шклейник

3
Є випадки, коли ви хочете використовувати інший екземпляр, наприклад, при вказівці на іншу базу даних.
Джей

1
Це корисне спрощення проблеми; але це не дає реальної відповіді.
BrainSlugs83

9

Це стара нитка, але іншим рішенням, яке я віддаю перевагу, є просто оновити cityId і не призначити дірову модель City для співробітника ... для цього співробітник повинен виглядати так:

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

Тоді досить призначити:

e1.CityId=city1.ID;

5

Альтернативно для ін'єкцій та ще гірше Сінглтона, ви можете зателефонувати за методом від'єднання перед Add.

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

EntityFramework 4: cs.Detach(city1);

Є ще один спосіб, якщо вам не потрібен перший об’єкт DBContext. Просто оберніть його з допомогою ключового слова:

Payroll.Entities.City city1;
using (CityService cs = new CityService())
{
  city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));
}

1
Я використав наступне: dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached'для від'єднання, а потім зміг dbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified;оновити. Працював як мрія
Пітер Сміт

Так, Пітер. Я мушу зазначити, що Держава відмічена як Змінена.
Роман О

У своїй логіці запуску програми (global.asax) я завантажував список віджетів .. простий список довідкових об'єктів я зберігаю в пам'яті. Оскільки я робив свій контекст EF всередині використання операторів, я подумав, що пізніше не виникне жодної проблеми, коли мій контролер обійдеться призначити ці об’єкти бізнес-графіку (ага, цей старий контекст відсутній, правда?) - ця відповідь врятувала мене .
bkwdesign

4

У мене була така ж проблема, але моя проблема з рішенням @ Slauma (хоча в деяких випадках є великою) полягає в тому, що він рекомендує передати контекст у службу, що означає, що контекст доступний у мого контролера. Це також примушує щільну зв'язок між моїм контролером та сервісними шарами.

Я використовую Dependency Injection для введення службових / репозиторних шарів у контролер і як такий не маю доступу до контексту від контролера.

Моє рішення полягало в тому, щоб шари служби / репозиторію використовували той самий екземпляр контексту - Singleton.

Контекст одиночного класу:

Довідка: http://msdn.microsoft.com/en-us/library/ff650316.aspx
та 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;
    }
  }
}  

Клас сховища:

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

Існують і інші рішення, такі як інстанціювання контексту один раз і передача його в конструктори шарів вашого сервісу / сховища чи інший, який я читав, що реалізує схему блоку роботи. Я впевнений, що є більше ...


9
... це не виходить з ладу, як тільки ви спробуєте використовувати багатопотокове читання?
CaffGeek

8
Контекст не повинен залишатися відкритим довше, ніж це потрібно, використання Singleton для його збереження відкритим назавжди - це останнє, що ви хочете зробити.
enzi

3
Я бачив гарні варіанти реалізації цього запиту. Використання ключового слова Static є неправильним, але якщо ви зробите цю модель для створення екземпляра контексту на початку запиту та розпорядження ним в кінці запиту, це було б законним рішенням.
Айдін

1
Це дійсно погана порада. Якщо ви використовуєте DI (я не бачу доказів тут?), Тоді ви повинні дозволити вашому контейнерові DI керувати контекстним життям, і це, ймовірно, має бути на запит.
Кейсі

3
Це погано. БАД. БАД. БАД. БАД. Особливо, якщо це веб-додаток, оскільки статичні об'єкти діляться між усіма потоками та користувачами. Це означає, що декілька одночасних користувачів вашого веб-сайту будуть тупотіти над вашим контекстом даних, потенційно пошкоджуючи його, зберігаючи зміни, які ви не мали наміру, або навіть просто створювали випадкові збої. DbContexts НІКОЛИ не слід ділити по потоках. Тоді є проблема в тому, що статику ніколи не руйнують, тому вона буде сидіти і продовжувати використовувати більше і більше пам’яті ...
Ерік Функенбуш

3

У моєму випадку я використовував рамку ідентичності ASP.NET. Я використовував вбудований UserManager.FindByNameAsyncметод для отримання ApplicationUserоб'єкта. Потім я спробував вказати цю сутність на новостворену сутність на іншому DbContext. Це призвело до виключення, яке ви бачили спочатку.

Я вирішив це, створивши нову ApplicationUserсутність лише Idз UserManagerметоду та посилаючись на цю нову сутність.


1

У мене була така ж проблема, і я міг вирішити створення нового екземпляра об'єкта, який я намагався оновити. Тоді я передав цей об’єкт у свій ремонтарій.


Чи можете ви допомогти з прикладом коду. ? тож буде зрозуміло, що ви намагаєтесь сказати
BJ Patel

1

У цьому випадку виявляється, що помилка дуже зрозуміла: Entity Framework не може відстежувати об'єкт, використовуючи кілька екземплярів IEntityChangeTrackerабо, як правило, декілька екземплярів DbContext. Рішення: використовувати один екземпляр DbContext; отримати доступ до всіх необхідних об'єктів через одне сховище (залежно від одного примірника DbContext); або вимкнення відстеження для всіх об'єктів, до яких звертається через сховище, відмінне від того, яке передає саме цей виняток.

Дотримуючись інверсії схеми управління в .Net Core Web API, я часто виявляю, що у мене є контролери з залежностями, такими як:

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

та використання

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

Оскільки всі три сховища залежать від різних DbContextпримірників на запит, у мене є два варіанти, щоб уникнути проблеми та підтримувати окремі сховища: змінити ін'єкцію DbContext, щоб створити новий екземпляр лише один раз за виклик:

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

або, якщо дочірнє сукупність використовується в режимі лише для читання, вимикаючи відстеження в цьому випадку:

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


0

Я потрапив на цю ж проблему після впровадження IoC для проекту (ASP.Net MVC EF6.2).

Зазвичай я б ініціалізував контекст даних у конструкторі контролера і використовував той же контекст для ініціалізації всіх моїх сховищ.

Однак використання IoC для інстанціалізації сховищ призвело до того, що всі вони мали окремі контексти, і я почав отримувати цю помилку.

Поки що я повернувся до того, щоб просто створити сховища із загальним контекстом, але думаю про кращий спосіб.


0

Ось як я зіткнувся з цим питанням. Спочатку мені потрібно зберегти своє, Orderяке потребує посилання на мій ApplicationUserстіл:

  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

Проблема полягає в тому, що я ініціалізую новий ApplicationDbContext, щоб зберегти нову Orderсутність:

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

Тому для вирішення проблеми я використовував той же ApplicationDbContext замість того, щоб використовувати вбудований UserManager ASP.NET MVC.

Замість цього:

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

Я використовував свій існуючий екземпляр ApplicationDbContext:

//db instance here is the same instance as my db on my code above.
user = db.Users.Find(User.Identity.GetUserId()); 

-2

Джерело помилок:

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

Сподіваюся, хтось економить дорогоцінний час


Я не впевнений, що це відповідає на питання. Можливо, якийсь контекст допоможе.
Стюарт Зіглер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.