На объектный объект нельзя ссылаться несколькими экземплярами 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";
        }
Smily
источник

Ответы:

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методе методу этого сервиса.

Slauma
источник
4
Мне нравится, как вы поняли это, даже если ответ не включал некоторую справочную информацию.
Даниэль Кмак
Похоже, это решит мою проблему, я просто не знаю, как написать новый экземпляр контекста :(
Ortund
12
Абстрагирование ORM похоже на наложение желтой помады на какашка.
Ронни Оверби
Я мог бы что-то здесь упустить, но в некоторых ORM (особенно EntityFramework) контекст данных всегда должен быть кратковременным. Введение статического или повторно используемого контекста создаст целый ряд других проблем и проблем.
Maritim
@ 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
Допустим, вы хотели использовать contextTwo? (может быть, из-за проблем с областью или что-то в этом роде) как вы отсоединяетесь от contextOne и присоединяетесь к contextTwo?
NullVoxPopuli
Если вам нужно сделать что-то подобное, скорее всего, вы делаете это неправильно ... Я предлагаю использовать один контекст.
Павел Шклейник
3
Есть случаи, когда вы хотите использовать другой экземпляр, например, при указании на другую базу данных.
Джей
1
Это полезное упрощение проблемы; но это не дает реального ответа.
BrainSlugs83
9

Это старый поток, но другое решение, которое я предпочитаю, это просто обновить cityId и не назначать модель дырки City для Employee ..., чтобы Employee должен выглядеть так:

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

Тогда достаточно присвоить:

e1.CityId=city1.ID;
user3484623
источник
5

В качестве альтернативы инъекции и, что еще хуже, Singleton, вы можете вызвать метод Detach перед 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.

Контекст Singleton Class:

Ссылка: 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;
  [...]

Существуют и другие решения, такие как создание экземпляра контекста один раз и передача его в конструкторы уровней вашего сервиса / хранилища, или другое, о котором я читал, которое реализует шаблон Unit of Work. Я уверен, что есть еще ...

kmullings
источник
9
... разве это не сломается, как только вы попробуете использовать многопоточность?
CaffGeek
8
Контекст не должен оставаться открытым дольше, чем необходимо, поэтому использование Singleton для его сохранения навсегда - это последнее, что вы хотите сделать.
Энзи
3
Я видел хорошие реализации этого за запрос. Использование ключевого слова Static неверно, но если вы создадите этот шаблон для создания экземпляра контекста в начале запроса и распоряжения им в конце запроса, это будет правильным решением.
Айдин
1
Это действительно плохой совет. Если вы используете DI (я не вижу доказательств здесь?), Тогда вы должны позволить вашему DI-контейнеру управлять временем жизни контекста, и это, вероятно, должно быть для каждого запроса.
Кейси
3
Это плохо. ПЛОХОЙ. ПЛОХОЙ. ПЛОХОЙ. ПЛОХОЙ. Особенно, если это веб-приложение, поскольку статические объекты совместно используются всеми потоками и пользователями. Это означает, что несколько одновременно работающих пользователей вашего сайта будут топтать ваш контекст данных, потенциально портя его, сохраняя изменения, которые вы не планировали, или даже просто создавая случайные сбои. DbContexts НИКОГДА не должен быть общим для всех потоков. Тогда есть проблема, что статика никогда не разрушается, поэтому она будет сидеть и продолжать использовать все больше и больше памяти ...
Эрик Фанкенбуш
3

В моем случае я использовал ASP.NET Identity Framework. Я использовал встроенный UserManager.FindByNameAsyncметод для получения ApplicationUserобъекта. Затем я попытался сослаться на эту сущность на новую созданную сущность на другую DbContext. Это привело к исключению, которое вы изначально видели.

Я решил это, создав новую ApplicationUserсущность только Idс UserManagerметодом from и ссылаясь на эту новую сущность.

Джастин Скилс
источник
1

У меня была та же проблема, и я мог решить создать новый экземпляр объекта, который я пытался обновить. Затем я передал этот объект в свое хранилище.

karolanet333
источник
Можете ли вы помочь с примером кода. ? так что будет понятно, что ты пытаешься сказать
Би Джей Патель
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);
Kjata30
источник
0

Используйте один и тот же объект DBContext во всей транзакции.

Налан Мадхесваран
источник
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

Надеюсь, кто-то экономит драгоценное время

Борн Коло
источник
Я не уверен, что это отвечает на вопрос. Возможно, какой-то контекст поможет.
Стюарт Зиглер