Код EF Во-первых, общее обновление завершается неудачно из-за «Не удается вставить дубликат ключа в объект» для дважды разделенного свойства навигации - PullRequest
2 голосов
/ 25 января 2012

решено: потому что каждый из моих классов обслуживания имел свой собственный связанный с ними контекст, я просто сохранил контекст в HttpContext и сделал DI в хранилище

У меня есть 3 класса:

 public class Group : Entity
{
    public Guid Id { get; set; }
    //other simple props
    public virtual GroupType GroupType { get; set; }

    public virtual ICollection<User> Users { get; set; }
}
 public class GroupType: Entity
{
    public Guid Id { get; set; }
    //other simple props
}

 public class User: Entity
{
    public Guid Id { get; set; }
    //other simple props

    public virtual ICollection<Group> Groups { get; set; }
 }

И мои довольно стандартные универсальные методы CRUD:

public void Insert(TClass entity)
    {
        if (_context.Entry(entity).State == EntityState.Detached)
            _context.Set<TClass>().Attach(entity);

        _context.Set<TClass>().Add(entity);
        _context.SaveChanges();
    }

public void Update(TClass entity)
    {
        DbEntityEntry<TClass> oldEntry = _context.Entry(entity);

        if (oldEntry.State == EntityState.Detached)
            _context.Set<TClass>().Attach(oldEntry.Entity);

        oldEntry.CurrentValues.SetValues(entity);

        _context.SaveChanges();
    }

public bool Exists(TClass entity)
    {
        bool exists = false;

        PropertyInfo info  = entity.GetType().GetProperty(GetKeyName());
        if (_context.Set<TClass>().Find(info.GetValue(entity, null)) != null)
            exists = true;

        return exists;
    }

public void Save(TClass entity)
    {
        if (entity != null)
        {
            if (Exists(entity))
                _repository.Update(entity);
            else
                _repository.Insert(entity);
        }
    }

Я пытаюсь запустить этот код:

public string TestCRUD()
    {

        UserService userService = UserServiceFactory.GetService();
        User user = new User("Test", "Test", "Test", "TestUser") { Groups = new Collection<Group>() };

        userService.Save(user);

        GroupTypeService groupTypeService = GroupTypeServiceFactory.GetService();
        GroupType groupType = new GroupType("TestGroupType2", null);

        groupTypeService.Save(groupType);

        GroupService groupService = GroupServiceFactory.GetService();
        Group group = new Group("TestGroup2", null) { GroupType = groupType };
        groupService.Save(group);

        user.Groups.Add(group);
        userService.Save(user);

    }

Когда я добираюсь до последней строки:

 userService.Save(user);

Я получаю эту ошибку:

Нарушение ограничения PRIMARY KEY 'PK_ GroupTypes _00551192'. Невозможно вставить дубликат ключа в объект «dbo.GroupTypes». Заявление было прекращено.

Похоже, он пытается вставить новый тип группы, даже если он уже был вставлен.

Редактировать: Все мои конструкторы классов автоматически устанавливают новый Guid для свойства Id.

EDIT:


Я только что выполнил тесты, дважды вызывая .Save() для каждого объекта, так что до того, как он существует в context / db и один раз после. Результаты кажутся странными:

  • При первом вызове .Save() на groupType объект: состояние Отсоединено (ожидается).
  • Второй раз без изменений: Состояние Без изменений (ожидалось и доказывает, что отслеживание изменений происходит в объекте в памяти, даже если я не запрашивал его из контекста).
  • При первом вызове .Save() для group object (с GroupType, установленным в уже сохраненный GroupType): состояние группы равно отключено (ожидается), Состояние GroupType: Отсоединено ( не ожидается, почему оно отсоединено вот когда я его уже сохранил и он отслеживался? )
  • Второй раз без изменений: статистика GroupType и GroupType без изменений (ожидается).
  • При первом вызове .Save() на пользователь объект: состояние Отсоединено (Ожидается).
  • Второй раз после вызова user.Groups.Add (group) с сохраненным group: пользователь без изменений (ожидаемый), группа добавленный (ожидаемый), GroupType добавленный ( не ожидаемый, почему он помечен как Добавлено, что оно было сохранено и предположительно отслеживается как индивидуально, так и в составе группового объекта? )
  • Наконец, я попытался пометить запись groupType как неизменную, и на этот раз для объекта Group возникла та же ошибка с повторяющимся ключом, так как она также добавлена, но не ожидается.

Итак, 1) почему существующие объекты действуют так, как будто они не отслеживаются при добавлении к свойству навигации по коллекции, но в более простом случае, например, при двойном вызове объекта для объекта группы, навигационная поддержка groupType по-прежнему отслеживается и не добавляется дважды, в обоих случаях я никогда не запрашиваю повторно, чтобы получить объект из базы данных.

Наконец, 2) как эффективно обойти это поведение? На самом деле я не хочу вручную устанавливать состояния сущностей свойств навигации как неизмененные, потому что не могу гарантировать, что они не пытаются добавить несуществующую сущность, но в то же время кажется неэффективным вызывать .Save() на каждая группа и каждый тип группы внутри пользователя .Save() ИЛИ запрашивают каждую из них из базы данных.

1 Ответ

1 голос
/ 25 января 2012

Я уверен, что если вы посмотрите на запись в базе данных, она покажет пустой guid (только нули).Чтобы «исправить» проблему, вам нужно сгенерировать guid и добавить его в свой класс Id.Самый простой способ исправить это - добавить конструктор по умолчанию и добавить Id = Guid.NewGuid(); таким образом, чтобы у ваших классов был собственный guid, и все должно работать как положено.

РЕДАКТИРОВАТЬ: так как оно GroupType, которое сохраняетсядля базы данных несколько раз с одним и тем же guid есть два сценария, о которых я могу подумать.1) Вы создали несколько групп и добавили одинаковые GroupType и попытаетесь сохранить.2) Вы создали GroupType вручную вместо того, чтобы получать его из базы данных, в которой 1) применяется снова.

Если у объекта GroupType нет EntityState из Unchanged или Modified, он попытаетсядобавляйте один и тот же GroupType снова и снова.Я думаю, что у вас есть два варианта:

  1. Перед сохранением вашей группы получите GroupType из базы данных и добавьте его в вашу группу.
  2. Измените EntityState GroupType на то, что вы думаете

EDIT2: Что произойдет, если вы запустите это?

public string TestCRUD()
{
    UserService userService = UserServiceFactory.GetService();

    User user = new User("Test", "Test", "Test", "TestUser") { Groups = new Collection<Group>() };
    GroupType groupType = new GroupType("TestGroupType2", null);
    Group group = new Group("TestGroup2", null) { GroupType = groupType };

    user.Groups.Add(group);
    userService.Save(user);

}

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

Причина, по которой это не будет вызывать дубликаты, заключается в том, что мы вызываем только userService.Save(user); один раз.Теперь, если бы мы хотели добавить больше групп для пользователя с тем же groupType (по какой-то причине?), Нам бы пришлось немного изменить наш код.

public string TestCRUD()
{
    //Expecting that we use IoC or a factory to share the same DbContext on both 
    //services to avoid already attached or any other weird problems
    UserService userService = UserServiceFactory.GetService();
    GroupTypeService groupTypeService = GroupTypeServiceFactory.GetService();

    GroupType groupType = new GroupType("TestGroupType", null);
    groupTypeService.Save(groupType);
    /*now if the groupType object has changed its EntityState to Unchanged
    we won't do anything, if not: 
    a) get it from the db or b) set the EntityState manually */

    User user = new User("Test", "Test", "Test", "TestUser") { Groups = new Collection<Group>() };
    Group group = new Group("TestGroup", null) { GroupType = groupType };
    Group group2 = new Group("TestGroup2", null) { GroupType = groupType };

    user.Groups.Add(group);
    user.Groups.Add(group2);
    userService.Save(user);
}

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...