Подтвердить что ты не робот

Как остановить Entity Framework от попыток сохранить/вставить дочерние объекты?

Когда я сохраняю объект с сущностью, я, естественно, предполагал, что он попытается сохранить указанный объект. Тем не менее, он также пытается сохранить дочерние сущности этого объекта. Это вызывает все проблемы целостности. Как заставить EF сохранять только объект, который я хочу сохранить, и, следовательно, игнорировать все дочерние объекты?

Если я вручную установил свойства в значение null, я получаю сообщение об ошибке "Операция завершилась неудачно: отношение не может быть изменено, поскольку одно или несколько свойств внешнего ключа не могут быть обнулены". Это очень контрпродуктивно, так как я установил дочерний объект в значение null, поэтому EF оставит его в покое.

Почему я не хочу сохранять/вставлять дочерние объекты?

Поскольку это обсуждается в комментариях и комментариях, я дам некоторое обоснование, почему я хочу, чтобы мои дочерние объекты остались в покое.

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

Поскольку я сам заполнял эти объекты, EF предполагает, что это новые объекты и их необходимо вставить вместе с родительским объектом. Однако эти определения уже существуют, и я не хочу создавать дубликаты в базе данных. Я использую только объект EF для поиска и заполнения внешнего ключа в моем основном объекте таблицы.

Даже с дочерними объектами, которые являются реальными данными, мне нужно сначала сохранить родительский элемент и получить первичный ключ, или EF просто кажется бесполезным. Надеюсь, это дает некоторое объяснение.

4b9b3361

Ответ 1

Насколько я знаю, у вас есть два варианта.

Вариант 1)

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

Вариант 2)

Установите дочерние объекты как отдельные из контекста, используя следующий код

 context.Entry(yourObject).State = EntityState.Detached

Обратите внимание, что вы не можете отсоединить List/Collection. Вам нужно будет перебрать ваш список и отделить каждый элемент в своем списке так

foreach (var item in properties)
{
     db.Entry(item).State = EntityState.Detached;
}

Ответ 2

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

class Company{
    public int Id{get;set;}
    public Virtual Department department{get; set;}
}
class Department{
    public int Id{get; set;}
    public String Name{get; set;}
}

Сохранение базы данных:

 Company company = new Company();
 company.department = new Department(){Id = 45}; 
 //an Department object with Id = 45 exists in database.    

 using(CompanyContext db = new CompanyContext()){
      Department department = db.Departments.Find(company.department.Id);
      company.department = department;
      db.Companies.Add(company);
      db.SaveChanges();
  }

Microsoft зачисляет это как функцию, однако я нахожу это раздражающим. Если объект отдела, связанный с объектом компании, имеет идентификатор, который уже существует в базе данных, то почему EF просто не связывает объект компании с объектом базы данных? Почему мы должны сами заботиться об ассоциации? Уход за навигационным свойством при добавлении нового объекта - это что-то вроде перемещения операций с базой данных с SQL на С#, громоздкой для разработчиков.

Ответ 3

Короче говоря: используйте внешний ключ, и он сохранит ваш день.

Предположим, что у вас есть Школа и объект Город, и это отношения "много-к-одному", когда в городе есть много школ и школы. Город. И предположим, что Города уже существуют в таблице поиска, поэтому вы НЕ хотите, чтобы их снова вставляли при вставке новой школы.

Вначале вы можете определить такие объекты, как это:

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    [Required]
    public City City { get; set; }
}

И вы можете сделать такую ​​установку Школа, как это (предположим, что у вас уже есть свойство Город, назначенное newItem):

public School Insert(School newItem)
{
    using (var context = new DatabaseContext())
    {
        context.Set<School>().Add(newItem);
        // use the following statement so that City won't be inserted
        context.Entry(newItem.City).State = EntityState.Unchanged;
        context.SaveChanges();
        return newItem;
    }
}

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

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    [ForeignKey("City_Id")]
    public City City { get; set; }

    [Required]
    public int City_Id { get; set; }
}

Таким образом, вы явно определяете, что Школа имеет внешний ключ City_Id, и это относится к объекту Город. Поэтому, когда речь идет о вставке Школы, вы можете сделать:

    public School Insert(School newItem, int cityId)
    {
        if(cityId <= 0)
        {
            throw new Exception("City ID no provided");
        }

        newItem.City = null;
        newItem.City_Id = cityId;

        using (var context = new DatabaseContext())
        {
            context.Set<School>().Add(newItem);
            context.SaveChanges();
            return newItem;
        }
    }

В этом случае вы явно указываете City_Id новой записи и удаляете Город из графика, чтобы EF не потрудился добавить его в контекст наряду с School.

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

Надеюсь, это поможет вам.

Ответ 4

Сначала вам нужно знать, что есть два способа обновления объекта в EF.

  • Прикрепленные объекты

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

  • Отключенные объекты

    Если вы работаете с отключенными объектами, вы должны вручную управлять синхронизация.

В приложении, которое я создаю, объектная модель EF не загружается из базы данных, а используется как объекты данных, которые я заполняю при разборе плоского файла.

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

  • Добавить

    При добавлении нового объекта с существующим дочерним объектом (объектом, который существует в базе данных), если дочерний объект не отслеживается EF, дочерний объект будет вставлен повторно. Если вы не вручную прикрепляете дочерний объект сначала.

    db.Entity(entity.ChildObject).State = EntityState.Modified;
    db.Entity(entity).State = EntityState.Added;
    
  • Update

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

    db.Entity(entity).State = EntityState.Modified;
    

График Diff

Если вы хотите упростить код при работе с отключенным объектом, вы можете попробовать графическую библиотеку diff.

Вот введение, Представление GraphDiff для Entity Framework Code First - разрешение автоматических обновлений графика отдельных объектов.

Пример кода

  • Вставить объект, если он не существует, в противном случае обновление.

    db.UpdateGraph(entity);
    
  • Вставить объект, если он не существует, в противном случае обновить И вставить дочерний объект, если он не существует, в противном случае обновление.

    db.UpdateGraph(entity, map => map.OwnedEntity(x => x.ChildObject));
    

Ответ 5

Если вы хотите сохранить изменения в родительском объекте и не сохранять изменения в любом из его дочерних объектов, то почему бы просто не сделать следующее:

using (var ctx = new MyContext())
{
    ctx.Parents.Attach(parent);
    ctx.Entry(parent).State = EntityState.Added;  // or EntityState.Modified
    ctx.SaveChanges();
}

Первая строка прикрепляет родительский объект и весь график зависимых дочерних объектов к контексту в состоянии Unchanged.

Вторая строка изменяет состояние только для родительского объекта, оставляя его дочерние элементы в состоянии Unchanged.

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

Ответ 6

Лучший способ сделать это - переопределить функцию SaveChanges в вашем datacontext.

    public override int SaveChanges()
    {
        var added = this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added);

        // Do your thing, like changing the state to detached
        return base.SaveChanges();
    }

Ответ 7

Это сработало для меня:

// temporarily 'detach' the child entity/collection to have EF not attempting to handle them
var temp = entity.ChildCollection;
entity.ChildCollection = new HashSet<collectionType>();

.... do other stuff

context.SaveChanges();

entity.ChildCollection = temp;

Ответ 8

Мы добавили родительский элемент в dbset, отключили дочерние коллекции от родителя, убедившись, что текущие коллекции переместились в другие переменные, чтобы позволить им работать с ними позже, а затем заменять текущие дочерние коллекции новыми пустые коллекции. Установка дочерних коллекций на null/ничего, казалось, не сработало для нас. После этого добавьте родительский элемент в dbset. Таким образом, дети не добавляются до тех пор, пока вы их не захотите.

Ответ 9

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

Ignore(parentObject => parentObject.ChildObjectOrCollection);

В основном это будет означать, что EF исключает свойство "ChildObjectOrCollection" из модели, чтобы оно не отображалось в базе данных.

Ответ 10

У меня такая же проблема, когда я пытаюсь сохранить профиль, я уже приветствую столы и новые для создания профиля. Когда я вставляю профиль, он также включается в приветствие. Поэтому я попробовал это раньше, чем savechanges().

db.Entry(Profile.Salutation).State = EntityState.Unchanged;