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



我有一个使用实体框架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值进行保存。任何关于如何发生这种情况的想法都将不胜感激。

您所描述的行为的典型罪魁祸首是,新的子实体是基于现有数据组成的,并附加到父实体,而不是关联到上下文的引用。例如,您可以将子对象加载为要从中进行选择的集合,并将数据发送到视图。用户希望将现有的子引用更改为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列,它可能需要一些更改。

最新更新