在SaveChanges之前添加相关实体时与外键冲突

时间:2018-08-13 13:18:31

标签: c# entity-framework entity-framework-6

如果我尝试添加根本不存在的相关实体,我希望EF 6.2.0会失败。但是在这种情况下,有问题的相关实体(Person)在SaveChanges之前添加到上下文中(尚不在Db中,但我在ChangeManager中看到)。 但是由于某种原因,EF告诉我:“ INSERT语句与FOREIGN KEY约束\“ FK_AuditPerson_Person \”发生冲突。冲突发生在数据库“ Person \”,表\“ person.Person \”的“ Id”列中“

我用几节课复述了我的问题:

public class Person
{
    public Guid Id { get; set; }
}

还有:

public class AuditPerson
{
    public Guid AuditId { get; set; }
    public Guid PersonId { get; set; }
    public DateTime Timestamp { get; set; }
}

上下文是:

public class PersonTestContext : DbContext
{
    public DbSet<Person> Packages { get; set; }
    public DbSet<AuditPerson> PersonAudits { get; set; }

    public PersonTestContext() : base("PersonTestDb")
    {
        Database.SetInitializer<PersonTestContext>(null);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
            .ToTable("Person", "person")
            .HasKey(t => t.Id);

        modelBuilder.Entity<AuditPerson>()
            .ToTable("AuditPerson", "dbo")
            .HasKey(ap => new { ap.AuditId, ap.PersonId });

        base.OnModelCreating(modelBuilder);
    }
}

下面的示例引发了问题:

    static void Main(string[] args)
    {
        using (var context = new PersonTestContext())
        {
            var person = new Person { Id = Guid.NewGuid() };
            context.Persons.Add(person);

            var audit = new AuditPerson { AuditId = Guid.NewGuid(), Timestamp = DateTime.UtcNow, PersonId = package.Id };

            context.PersonAudits.Add(audit);

            context.SaveChangesAsync().Wait();
        }
        Console.ReadKey();
    }

有什么可用的替代方法来告诉EF PersonId所引用的对象将保存在同一SaveChanges调用中?

现在,当我添加时问题就消失了

public virtual Person Person { get; set; }

到AuditPerson类。但是,如果没有导航属性,EF能否解决这个问题?

该Db已经存在,并且具有“ FK_AuditPerson_Person”为:

ALTER TABLE [dbo].[AuditPerson]  WITH CHECK 
  ADD CONSTRAINT [FK_AuditPerson_Person] 
  FOREIGN KEY([PersonId])
  REFERENCES [person].[Person] ([Id])

我觉得以前没有导航属性就看过了。

最终思想

正如史蒂夫(Steve)解释的那样,EF不保证插入顺序。 如果我们使用相同的代码,但是类名是Appointment,而不是Person,则不会出现任何问题:添加记录时不会出现任何FK冲突。 实际的生产代码(现已修复)也发生了同样的情况。显然,EF是按类名对插入进行排序的(可以澄清一下代码),因此Person插入是在Audits之后完成的,而约会是在Audit之前完成的,因此行为上的差异

1 个答案:

答案 0 :(得分:0)

出现此问题的原因是,如果没有实体之间的关系,EF不能保证插入顺序将与您的代码匹配。由于FK约束,EF试图首先插入AuditPerson记录并跳出FK。通过指定导航属性,EF可以计算出插入记录的顺序。

如果您通常不从“人员”访问“审核”,那么建议您将“人员”引用放在“审核”中,并映射为:

HasRequired(x => x.Person)
  .WithMany()
  .HasForeignKey(x => x.PersonId);

然后在设置实体时

context.Persons.Add(person);
var audit = new AuditPerson { AuditId = Guid.NewGuid(), Timestamp = DateTime.UtcNow, Person = person };

context.PersonAudits.Add(audit);
context.SaveChangesAsync().Wait();

此外,如果您的数据库是SQL Server,我建议利用该数据库通过newsequentialId()管理PK,并将EF设置为将其识别为Identity列。或者,您可以使用相同的高/低字节顺序生成GUID,以使UUID与SQL的顺序ID匹配。这些ID更便于索引。