Как я могу использовать EF для добавления нескольких дочерних объектов к объекту, когда у ребенка есть ключ идентификации?

Мы используем EF5 и SQL Server 2012 следующие два класса:

public class Question
{
    public Question()
    {
        this.Answers = new List<Answer>();
    }
    public int QuestionId { get; set; }
    public string Title { get; set; }
    public virtual ICollection<Answer> Answers { get; set; }

}
public class Answer
{
    public int AnswerId { get; set; }
    public string Text { get; set; }
    public int QuestionId { get; set; }
    public virtual Question Question { get; set; }
}

Отображение выглядит следующим образом:

public class AnswerMap : EntityTypeConfiguration<Answer>
{
    public AnswerMap()
    {
        // Primary Key
        this.HasKey(t => t.AnswerId);

        // Identity
        this.Property(t => t.AnswerId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

База данных DDL

CREATE TABLE Answer (
    [AnswerId] INT IDENTITY (1, 1) NOT NULL,
    [QuestionId] INT NOT NULL,
    [Text] NVARCHAR(1000),
    CONSTRAINT [PK_Answer] PRIMARY KEY CLUSTERED ([AnswerId] ASC)
)";

Вот результаты того, что я пробовал:

Это работает для одного ребенка:

var a = new Answer{
    Text = "AA",
    QuestionId = 14
};
question.Answers.Add(a);
_uow.Questions.Update(question);
_uow.Commit();

Это не работает для более чем одного ребенка:

Ошибка: объект с тем же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одним и тем же ключом.

var a = new Answer{
    AnswerId = 0,
    Text = "AAA",
    QuestionId = 14
};
var b = new Answer {
    AnswerId = 0,
    Text = "BBB",
    QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();

Это не работает для более чем одного ребенка:

Он создает AnswerID 1000 и 1001, но я хочу, чтобы новый идентификатор был создан базой данных.

var a = new Answer{
    AnswerId = 1000,
    Text = "AAA",
    QuestionId = 14
};
var b = new Answer {
    AnswerId = 1001,
    Text = "BBB",
    QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();

Не работает:

Ошибка компилятора. Невозможно преобразовать null в int

var a = new Answer{
    AnswerId = null,
    Text = "AAA",
    QuestionId = 14    
};
var b = new Answer
{
    AnswerId = null,
    Text = "BBB",
    QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();

Не работает:

ObjectStateManager не может отслеживать несколько объектов с одним и тем же ключом.

var a = new Answer{
    Text = "AAA",
    QuestionId = 14
};
var b = new Answer
{
    Text = "BBB",
    QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();

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

public HttpResponseMessage PutQuestion(int id, Question question) {
            _uow.Questions.Update(question);
            _uow.Commit();

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

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

Примечания:

Вот как кодируется обновление:

public virtual void Update(T entity)
{
    DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
    if (dbEntityEntry.State == EntityState.Detached)
    {
        DbSet.Attach(entity);
    }  
    dbEntityEntry.State = EntityState.Modified;
}

Ответ 1

Вы упомянули, что добавляете a два раза...?!

question.Answers.Add(a);
question.Answers.Add(a);

Обычно для добавления элементов, которые являются идентификатором, вы должны пропустить установку идентификатора. Вы также должны добавить атрибут [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] к этим идентификаторам:

public class Answer
{
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int AnswerId { get; set; }
    public string Text { get; set; }
    public int QuestionId { get; set; }
    public virtual Question Question { get; set; }
}

И добавьте следующие данные:

var a = new Answer{
    Text = "AAA",
    QuestionId = 14
};

var b = new Answer
{
    Text = "BBB",
    QuestionId = 14
};

dbContext.Answers.Add(a);
dbContext.Answers.Add(b);

dbContext.SaveChanges();

// ...

Ответ 2

Я столкнулся с одним и тем же "ограничением". Оказывается, если вы добавите родителя и любых детей, EF сможет справиться с тем фактом, что родители и дети все вместе добавляются. У вас возникают проблемы при обновлении родительского элемента и одновременном вставке двух детей. Если вы присоедините родителя, EF автоматически заберет этих двух детей и прикрепит их, хотите ли вы этого или нет. Поскольку мы хотим, чтобы он автоматически генерировал Id, мы не будем устанавливать первичный ключ для детей. Тем не менее, EF не может обрабатывать элементы с одним и тем же основным ключом, когда родитель является обновлением и взрывается, поскольку оба имеют одинаковый PK равным 0 для обоих детей.

Единственный способ, которым я нашел это, - вручную установить идентификаторы детей на разные числа. Обычно я устанавливаю первый идентификатор пользователя равным -1, затем -2 для второго ребенка и т.д. Это приведет к тому, что EF сохранит детей, и ключ будет автоматически обновляться из-за того, что Identity работает в базе данных, потому что -1 и -2 не являются допустимыми значениями.

Однако это вызовет сильную боль, если у вас есть 3-й уровень или выше. Вам не нужно обновлять этот PK для каждого дочернего элемента, но тогда вам придется обновить FK для любого из своих дочерних элементов до этого нового значения -1 или -2. В противном случае сбой снова завершится!

Единственный другой вариант, который я вижу, - это просто вставить один ребенок за раз и вызвать save, чтобы контекст не имел дело с более чем одним объектом с тем же PK, но этот вид побеждает цель ORM...

Ответ 3

Попробуйте следующее:

  • используйте Create() метод DbSet
  • добавьте новые экземпляры в коллекцию Answers вашего Context

Вы правильно установили QuestionId для EF, чтобы реализовать взаимосвязь. Кроме того, явным образом не устанавливаю значение AnswerId равным нулю.

var a = new _uow.Answers.Create();
a.Text = "AAA";
a.QuestionId = 14;
_uow.Answers.Add(a);

var b = new _uow.Answers.Create();
b.Text = "BBB";
b.QuestionId = 14;
_uow.Answers.Add(a);

Вам может потребоваться сделать вызов _uow.ChangeTracker.DetectChanges(), если вы планируете опросить коллекцию Answers Question 14

Ответ 4

Если вы правильно объявили Id как ключ и являетесь DBGenerated identity. Затем EF позволит вам добавить ADD многие из них в контекст перед сохранением. Нельзя использовать элементы ATTACH с тем же ключом. Приложение предназначено для автономных данных, помещает в контекст, устанавливает его состояние и сохраняет типы сценариев.

Вы использовали один и тот же экземпляр дважды, а по умолчанию отслеживание EF вызвало беспорядок. Или как-то использовал ATTACH дважды. Убедитесь, что вы правильно обрабатываете свои экземпляры. *

например,

public class BaseEntityLongConfiguration<T> : EntityTypeConfiguration<T> where T : BaseObjectLong {
    public BaseEntityLongConfiguration(DatabaseGeneratedOption DGO = DatabaseGeneratedOption.Identity) {

        // Primary Key
        this.HasKey(t => t.Id);

        // Properties
        //Id is an indent allocated by DB
        this.Property(t => t.Id).HasDatabaseGeneratedOption(DGO); // default to db generated

        this.Property(t => t.RowVersion)   // for concurrency
            .IsRequired()
            .IsFixedLength()
            .HasMaxLength(8)
            .IsRowVersion();
    }
}

Просто попробовал простой тест, чтобы проверить его работу (в ef5)

public class ExampleLog  {
    public virtual long Id   { get; set; }
    public virtual string MessageText { get; set; }
}

[TestMethod]
    public void ExampleLogTest() {
        var e1 = new ExampleLog();
        e1.MessageText = "example1";
        var e2 = new ExampleLog();
        e2.MessageText = "example2";
        _context.Set<ExampleLog>().Add(e1);
        _context.Set<ExampleLog>().Add(e2);
     var res =   _context.SaveChanges();
      Debug.WriteLine("result expected 2->" + res.ToString());
    }

изменить.. При запросе добавляется сохранение шаблона Respository, выбор BASic, удаление ошибок.

public class RepositoryBase<TPoco> : where TPoco :    BaseObject {
    public RepositoryBase(BosBaseDbContext context) { Context = context; }

....

   /// <summary>
    /// Add new POCO 
    /// </summary>
    public virtual OperationResult Add(TPoco poco) {
        var opResult = new OperationResult();
        try {
          Context.Set<TPoco>().Add(poco);
        }
        catch (Exception ex) {
         .... custom error tool
            return opResult;
        }
        return opResult;
    }
     /// <summary>
    /// Poco must already be attached,, detect chnages is triggered
    /// </summary>
    public virtual OperationResult Change(TPoco poco) {
        var opResult = new OperationResult();
        try {    // ONLY required if NOT using chnage tracking enabled
            Context.ChangeTracker.DetectChanges();
        }
        catch (Exception ex) {
         .... custom error tool
            return opResult;
        }
        return opResult;
    }

Ответ 5

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

using (var db = new EFService())
{
    var question= db.Question.Single(p => p.QuestionID == QuestionID);
    question.Answers.Add(a);
    question.Answers.Add(b);
    db.SaveChanges();
}