实体框架的间歇性问题创建不需要的行

时间:2017-10-29 21:55:35

标签: c# sql-server asp.net-mvc entity-framework

我有一个使用Entity Framework v6的MVC应用程序。我们有一个班级

public class ChildObject
{
    public string Name { get; set; }
    ....
}

映射到数据库中的表。此表有6行永不更改。也不会有任何增加。我们按照以下方式定义了第二个类:

public class ParentClass
{
    public int ChildObjectId { get; set; }
    public ChildObject ChildObject { get; set; }
    ....
}

每当创建或更新ParentClass对象时,逻辑仅引用ChildObjectId属性。只有在撤回数据以供查看时才会引用ChildObject属性。但是,每月大约一次在ChildObject表中出现一个额外的行,该行是现有行的副本。这显然会引发问题。但是我看不出这是怎么发生的,因为我们只使用Id值进行保存。任何关于如何发生这种情况的想法都将非常感激。

1 个答案:

答案 0 :(得分:1)

您描述的行为的典型罪魁祸首是基于现有数据组合新的子实体并将其附加到父级而不是与上下文关联的引用。例如,您可以将子对象加载为要从中选择的集,并将数据发送到视图。用户想要将现有子引用更改为6个选项之一。回调服务器会传递一个子对象模型,其代码如下:

parent.ChildObject = new ChildObject{ Name = model.Name, ... } 

而不是:

var child = context.Children.Single(x => x.Id = model.ChildObjectId);
parent.ChildObject = child;

根据域的设置方式,您可能遇到在设置导航属性时EF上下文创建新子实体的场景。检查ChildObject属性上的FindUsages并查找setter的任何使用。

通常,您应该避免将FK属性(ChildObjectId)与导航属性(ChildObject)结合使用,因为您可能会在导航参考中设置的内容与FK之间产生混淆行为。实体应该用一个或另一个来定义。 (尽管此时EF Core需要使用导航属性。)

你的例子中有几个值得注意的人: 将导航属性标记为虚拟 - 这可确保EF分配代理并识别它。

选项A - 删除FK子ID属性。对于父级要么使用EntityTypeConfiguration,要么初始化DbContext以映射FK列:

EntityTypeConfiguration:

public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
  public ParentClassConfiguration()
  {
    ToTable("ParentTable");
    HasKey(x => x.ParentObjectId)
      .Property(x => x.ParentObjectId)
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

    HasRequired(x => x.ChildObject)
      .WithMany()
      .Map(x => x.MapKey("ChildObjectId"));
      .WillCascadeOnDelete(false);
  }
}

或关于上下文模型生成:(在DbContext中)

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().Map(x => x.MapKey("ChildObjectId")).WillCascadeOnDelete(false);
}

或选项B - 确保FK与参考相关联,并采取措施确保两者始终保持同步:

public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
  public ParentClassConfiguration()
  {
    ToTable("ParentTable");
    HasKey(x => x.ParentObjectId)
      .Property(x => x.ParentObjectId)
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

    HasRequired(x => x.ChildObject)
      .WithMany()
      .HasForeignKey(x => x.ChildObjectId));
      .WillCascadeOnDelete(false);
  }
}

或关于上下文模型生成:(在DbContext中)

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().HasForeignKey(x => x.ChildObjectId)).WillCascadeOnDelete(false);
}

根据我的知识,选项B是目前唯一可用于EF Core的选项,它可能有助于缓解您的问题,但您仍需要注意避免导航属性与FK之间的差异。我绝对推荐选项A,但如果您的代码通常访问FK列,则可能需要进行一些更改。