How can I use EF to add multiple child entities to an object when the child has an identity key?

后端 未结 5 1333
北恋
北恋 2021-01-02 02:23

We are using EF5 and SQL Server 2012 the following two classes:

public class Question
{
    public Question()
    {
        this.Answers = new List

        
相关标签:
5条回答
  • 2021-01-02 02:28

    Try these things:

    • use the Create() method of DbSet
    • add the new instances to the Answers collection of your Context

    You have set the QuestionId appropriately for EF to realise the relationship. Also, do not explicitly set AnswerId to zero.

    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);
    

    You may need to make a call to _uow.ChangeTracker.DetectChanges() if you plan on querying the Answers collection of Question 14

    0 讨论(0)
  • 2021-01-02 02:32

    If you have correctly declared the Id as Key and as being DBGenerated identity. Then EF will allow you ADD many of these to the context before saving. You can NOT ATTACH items with the same key. Attach is meant for offline data, put in context, set its state and save type scenarios.

    You have either used the Same instance twice and with EF tracking by default caused a mess. Or somehow used ATTACH twice. Make sure you are handling your instances cleanly.*

    eg

    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();
        }
    }
    

    A just tried a simple Test to check it works (in 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());
        }
    

    edit: At request, adding save Respository pattern, BAsic sample, error handling removed

    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;
        }
    
    0 讨论(0)
  • 2021-01-02 02:35

    I have run into the same identity "limitation" as well. It turns out that if you add a parent and any children, EF can handle the fact that the parent and children are all being added together. You run into problems when you Update the parent and insert two children at the same time. If you attach the parent, EF will automatically pick up those two children and attach them whether you want it to or not. Since we want it to auto generate the Id, we wouldn't set the primary key of the children. However, EF cannot handle items with the same Primary key when the parent is an Update and blows up since both have the same PK of 0 for both children.

    The only way I have found around this is to manually set the ids of the children to different numbers. I usually set the first child's Id to -1, then -2 for the second child, and so on. This will cause EF to save the children and the key will automatically be updated due to the Identity running on the database because -1 and -2 are not valid identity values.

    However, this will cause great pain if you have a 3rd level or beyond. Not only do you have to update this PK on each child, but then you'd have to update the FK on any of its children to this new -1 or -2 value. Otherwise, the save will fail again!

    The only other option I see is really just to insert one child at a time and call save so the context isn't dealing with more than one object with the same PK, but that kind of defeats the purpose of an ORM...

    0 讨论(0)
  • 2021-01-02 02:36

    Did you mentioned that you are adding a two times...?!

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

    Usually, to add items which their id is identity, you must skip setting the id. You also should add the [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] attribute to these IDs:

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

    And add data like this:

    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();
    
    // ...
    
    0 讨论(0)
  • 2021-01-02 02:39

    as i was going through the same issue. So thought to submit my solution. It will be helpful for the other persons who will be 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();
    }
    
    0 讨论(0)
提交回复
热议问题