Вопрос по c#, asp.net, entity-framework, ado.net – На объектный объект нельзя ссылаться несколькими экземплярами IEntityChangeTracker. при добавлении связанных объектов к сущности в Entity Framework 4.1

150

Я пытаюсь сохранить данные сотрудника, на которые есть ссылки с City. Но каждый раз, когда я пытаюсь сохранить свой контакт, который проверяется, я получаю исключение"ADO.Net Entity Framework An entity object cannot be referenced by multiple instances of IEntityChangeTracker"

Я прочитал так много постов, но до сих пор не понимаю, что делать ... мой код нажатия кнопки Сохранить приведен ниже

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

а такжеEmployeeservice Code

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

Ваш Ответ

9   ответов
3

встроенный вUserManager.FindByNameAsync метод для полученияApplicationUser юридическое лицо. Затем я попытался сослаться на эту сущность на вновь созданную сущность на другомDbContext, Это привело к исключению, которое вы изначально видели.

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

4

вы можете позвонитьDetach Метод перед добавлением.

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

EntityFramework 4:cs.Detach(city1);

Существует еще один способ, если вам не нужен первый объект DBContext. Просто оберните этоusing ключевое слово:

Payroll.Entities.City city1;
using (CityService cs = new CityService())
{
  city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));
}
Да Питер Я должен упомянуть, чтобы отметить состояние как измененный.
Я использовал следующее:dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached& APOS; отсоединить, а затем смог использоватьdbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified; обновлять. Работал как мечта
В логике запуска моего приложения (global.asax) я загружал список виджетов ... простой список ссылочных объектов, которые я прячу в память. Поскольку я делал свой EF-контекст внутри операторов Using, я подумал, что у меня не будет проблем позже, когда мой контроллер получит возможность назначить эти объекты в бизнес-графе (эй, этот старый контекст исчез, верно?) - этот ответ спас меня ,
5

но моя проблема с решением @ Slauma (хотя в некоторых случаях это здорово) заключается в том, что он рекомендует передать контекст в службу, что подразумевает, что контекст доступен из моего контроллера. Это также вызывает тесную связь между моим контроллером и сервисными уровнями.

Я использую Dependency Injection, чтобы внедрить уровни обслуживания / хранилища в контроллер и, таким образом, не иметь доступа к контексту из контроллера.

Мое решение состояло в том, чтобы слои сервиса / репозитория использовали один и тот же экземпляр контекста - Singleton.

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

Repository Class:

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

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

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

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

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

Тогда достаточно назначить:

e1.CityId=city1.ID;
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);
224

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

... не берите параметр в конструкторе, я полагаю, что вы создаете контекст внутри классов. Когда вы загружаетеcity1...

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

... вы прикрепляетеcity1 к контексту вCityService, Позже вы добавляетеcity1 как ссылка на новыйEmployee e1 и добавитьe1 including this reference to city1 к контексту вEmployeeService, В результате у вас естьcity1 в двух разных контекстах, на которые жалуется исключение.

Это можно исправить, создав контекст вне классов обслуживания, добавив и использовав его в обеих службах:

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

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

Вы также можете создать один сервис, который отвечает за набор тесно связанных объектов, таких какEmployeeCityService (который имеет единственный контекст) и делегировать всю операцию в вашемButton1_Click метод к методу этого сервиса.

Я мог бы что-то здесь упустить, но в некоторых ORM (особенно EntityFramework) контекст данных всегда должен быть кратковременным. Введение статического или повторно используемого контекста создаст целый ряд других проблем и проблем.
Абстрагирование ORM похоже на наложение желтой помады на какашка.
@ Maritim это зависит от использования. В веб-приложениях, как правило, один туда и обратно. В настольных приложениях вы также можете использовать одинForm (что когда-либо, это просто представляет одну единицу работы) вThread (так какDbContext не гарантированно является потокобезопасным).
Похоже, это решит мою проблему, я просто не знаю, как написать новый экземпляр контекста :(
Мне нравится, как вы поняли это, даже если в ответ не была включена некоторая справочная информация.
22

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

var user = contexOne.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();
................
Если вам нужно сделать что-то подобное, скорее всего, вы делаете это неправильно ... Я предлагаю использовать один контекст.
Есть случаи, когда вы хотите использовать другой экземпляр, например, при указании на другую базу данных.
Допустим, вы хотели использовать contextTwo? (может быть, из-за проблем с областью или что-то в этом роде) как вы отсоединяетесь от contextOne и присоединяетесь к contextTwo?
Это полезное упрощение проблемы; но это не дает реального ответа.
1

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

Можете ли вы помочь с примером кода. ? так будет понятно что ты пытаешься сказать
-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

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

Я не уверен, что это отвечает на вопрос. Возможно, какой-то контекст поможет.

Похожие вопросы