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

Я использую GraphDiff для обновления графов отдельных объектов и получаю указанное выше исключение при сохранении родителя и его дети.

Модели и отображение:

public class Group
{
    public int Id { get; set; }
    public virtual ICollection<GroupUser> Users{ get; set; }
}

public class GroupUser
{
    public int Id { get; set; }
    public int GroupId { get; set; }
    public int UserId { get; set; }
    public virtual User User { get; set; }
}

public class GroupMap : EntityTypeConfiguration<Group>
{
    public GroupMap()
    {
        this.ToTable("groups");
        this.HasKey(t => t.Id).Property(t => t.Id).HasColumnName("id").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        this.HasMany(t => t.Users)
            .WithOptional()
            .HasForeignKey(d => d.GroupId)
            .WillCascadeOnDelete();
    }
}

public class GroupUserMap : EntityTypeConfiguration<GroupUser>
{
    public GroupUserMap ()
    {
        this.ToTable("groups_users");
        this.HasKey(t => t.Id).Property(t => t.Id).HasColumnName("id").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.Property(t => t.GroupId).HasColumnName("group_id").IsRequired();
        this.Property(t => t.UserId).HasColumnName("user_id").IsRequired();

        this.HasRequired(t => t.User)
            .WithMany()
            .HasForeignKey(d => d.UserId)
            .WillCascadeOnDelete(false);
    }
}

Для вставок и обновлений у меня есть следующий метод в моем репозитории:

public override Group Update(Group entity)
{
    if (entity.Users != null)
    {
        entity.Users.ToList()
                    .ForEach(x =>
                    {
                        x.GroupId = x.Id == 0 ? 0 : entity.Id;
                        x.UserId = x.User.Id;
                    });
    }

    return dbContext.UpdateGraph<Group>
        (entity,
            map => map.OwnedCollection(x => x.Users, with => with.AssociatedEntity(c => c.Users))
        );
}

Я делаю следующее:

  1. Добавьте новую группу, сохраните и перезагрузите
  2. Добавьте пользователя в группу, сохраните и перезагрузите
  3. Добавьте второго пользователя, сохраните и перезагрузите
  4. Добавьте третьего пользователя, попробуйте сохранить -> выдается исключение

Во время отладки состояние добавляемых пользовательских объектов всегда отсоединено, а GroupId и Id устанавливаются на 0 для новых объектов. Первые 2 добавленных пользователя успешно сохранены. Однако на третьем выбрасывается исключение.

Если я всегда использую один и тот же метод сохранения, почему он не всегда работает? Это проблема EF или GraphDiff и есть ли способ решить эту проблему?

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


person Ivan-Mark Debono    schedule 14.04.2016    source источник


Ответы (3)


Вероятно, это связано с тем, что UserId не может быть нулевым. Как вы знаете, UserId используется EF для записи User. Если пользователь имеет значение null (может), EF не может записать сущность. Также это может быть связано с User.GroupId, который используется EF для сопоставления коллекции Group.Users.

Кроме того, я не могу понять вашу модель. У пользователя есть одна и только одна группа. В группе может быть более одного пользователя.

Если это правда, почему бы вам просто не упростить модель, удалив объект ассоциации? ЭФу это не нужно...

public class Group
{
    public int Id { get; set; }
    public virtual ICollection<User> Users{ get; set; }
}

public class User
{
    public int Id { get; set; }
    public virtual Group Group { get; set; }
}

В этом случае вам нужен только UserMap.

public class UserMap : EntityTypeConfiguration<User>
{
    public UserMap()
    {
        // You don't need to set primary key. EF take Id and create an identity column
        //HasKey(t => t.Id); 

        // Table & Column Mappings
        ToTable("users");
        // No column mappings in this case.

        // Relationships
        HasRequired(t => t.Group)
            .WithMany(t => t.Users)
            .Map(d => d.MapKey("user_group"));

    }
}

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

public class User
{
    public int Id { get; set; }
    public virtual ICollection<Group> Groups { get; set; }
}

EF понимает, что это отношение «многие ко многим», создаст таблицу ассоциаций (UsersGroups), но вам не нужен объект ассоциации.

ИЗМЕНИТЬ

Редко вам нужно установить db.Entry(myEntity).State = EntityState.Modified; с EF6. Прокси работают достаточно хорошо.

person bubi    schedule 29.04.2016

Вы пытались увидеть, изменяется ли состояние объекта, когда EF не отслеживает его?

Пытаться

db.Entry(currentUser).State = EntityState.Modified;

Функция ToList() также может быть причиной случайного изменения пользовательского состояния.

person user3145912    schedule 24.04.2016
comment
Изменение ToList() на нормальное foreach не имело никакого значения. - person Ivan-Mark Debono; 24.04.2016
comment
Если "Состояние" добавляется и не изменяется, это звучит как проблема с отключением сущностей. Я предполагаю, что EF воссоздает «GroupUser», а затем обнуляет предыдущий «GroupUser» при попытке сохранения. Вы пытались распечатать значения предыдущих добавленных пользователей? - person user3145912; 24.04.2016

Пожалуйста, попробуйте это. Это не проверено, хотя

public override Group Update(Group entity)
{
    if (entity.Users != null)
    {
        foreach(var user in entity.Users){
          user.GroupId = x.Id == 0 ? 0 : entity.Id;
          user.UserId = x.User.Id;
         dbContext.Entry(child).State = EntityState.Modified;
        }

    }

    return dbContext.UpdateGraph<Group>
        (entity,
            map => map.OwnedCollection(x => x.Users, with => with.AssociatedEntity(c => c.Users))
        );
}
person suulisin    schedule 28.04.2016