12

我们正在使用 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,但我希望数据库创建新的 ID。

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

在我的应用程序中,我在客户端生成了一个或多个新的 Answer 对象,然后将它们发送到服务器。上面我正在模拟在不将客户添加到问题中的情况下会发生什么。请注意,向 Question 对象添加所有 Answers 是在客户端完成的,然后以 JSON 字符串的形式传递给服务器。然后将其反序列化为一个问题对象,如下所示:

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

我希望使用 a 创建每个 Answer 对象new identity ID,以便将这些对象添加到 Question 对象中,并以正常方式返回 Question 对象。

我不知道如何做到这一点。到目前为止,我所有的简单测试都不起作用。请注意,这是我们小组成员之前提出的问题的一个变体,这个问题不太清楚,我正试图结束。这个问题我希望更清楚。

笔记:

以下是更新的编码方式:

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

5 回答 5

7

我也遇到了同样的身份“限制”。事实证明,如果您添加父级和任何子级,EF 可以处理父级和子级都被添加在一起的事实。当您更新父级并同时插入两个子级时,您会遇到问题。如果您附加父级,EF 将自动拾取这两个子级并附加它们,无论您是否愿意。由于我们希望它自动生成 Id,我们不会设置孩子的主键。但是,当父项是 Update 并爆炸时,EF 无法处理具有相同主键的项目,因为两个子项的 PK 相同,均为 0。

我发现的唯一方法是手动将孩子的 ID 设置为不同的数字。我通常将第一个孩子的 ID 设置为 -1,然后将第二个孩子的 ID 设置为 -2,依此类推。这将导致 EF 保存子项,并且由于在数据库上运行的标识,密钥将自动更新,因为 -1 和 -2 不是有效的标识值。

但是,如果您有 3 级或更高级别,这将导致巨大的痛苦。您不仅必须更新每个孩子的这个 PK,而且还必须将其任何孩子的 FK 更新为这个新的 -1 或 -2 值。否则,保存将再次失败!

我看到的唯一其他选择实际上只是一次插入一个孩子并调用 save ,因此上下文不会处理具有相同 PK 的多个对象,但这违背了 ORM 的目的......

于 2014-02-20T22:08:18.223 回答
1

你有没有提到你要添加a两次......?!

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

通常,要添加其 id 为标识的项目,您必须跳过设置 id。您还应该将[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]属性添加到这些 ID:

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

// ...
于 2013-07-30T12:46:47.320 回答
0

试试这些东西:

  • 使用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()如果您打算查询14的Answers集合,您可能需要致电Question

于 2013-07-30T12:23:01.953 回答
0

如果您已正确地将 Id 声明为 Key 并作为 DBGenerated 标识。然后 EF 将允许您 在保存之前将其中的许多 添加到上下文中。您不能使用相同的键附加项目。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();
    }
}

A 刚刚尝试了一个简单的测试来检查它是否有效(在 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());
    }

编辑: 应要求,添加保存存储库模式,基本示例,删除错误处理

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;
    }
于 2013-07-30T12:49:06.573 回答
0

因为我正在经历同样的问题。所以想提交我的解决方案。这将对其他将要搜索的人有所帮助-

using (var db = new EFService())
{
    var question= db.Question.Single(p => p.QuestionID == QuestionID);
    question.Answers.Add(a);
    question.Answers.Add(b);
    db.SaveChanges();
}
于 2019-06-18T11:50:36.273 回答