我继承了一个遗留的SQL数据库,我正试图将其逆向工程为代码优先。由于遗留软件在当前配置上运行,因此不能更改模式。对我来说奇怪的是,开发人员将子表配置为具有由ChildId + ParentId组成的复合主键,而不仅仅是ParentId。
我有:
public class Parent
{
public decimal PkParentId { get; set; }
public string ParentName { get; set; }
...
public Child Child { get; set; }
}
public class Child
{
public decimal PkChildId { get; set; }
public decimal PkParentId { get; set; }
public string ChildName { get; set; }
...
public Parent Parent { get; set; }
}
这是我的映射。当我对它进行反向工程时,EF想要在父节点中创建一个具有子节点集合的1对多,但我真正想要的是1对0或1(单个子节点可能存在,也可能不存在,但父节点必须存在)。
public class ParentConfiguration : EntityTypeConfiguration<Parent>
{
public ParentConfiguration()
{
HasKey(p => p.PkParentId);
Property(p => p.PkParentId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasOptional(p => p.Child).WithRequired(p => p.Parent);
// Also tried this
// HasOptional(p => p.Child).WithRequired(p => p.Parent).Map(p => p.MapKey("PkParentId"));
}
}
public class ChildConfiguration : EntityTypeConfiguration<Child>
{
public ChildConfiguration()
{
// Pk is composite on child
HasKey(e => new {e.PkChildId, e.PkParentId});
Property(p => p.PkChildId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
我可以同时获取父/子元素没有问题,但如果我试图插入父/子元素:
var newParent = new Parent
{
ParentName = "XXX",
Child = new Child { ChildName = "YYY" }
};
_dbContext.Parents.Add(newParent);
_dbContext.SaveChanges();
这会导致以下错误:在SET子句中多次指定列名'PkParentId'。在同一个SET子句中,一个列不能被赋多个值。修改SET子句以确保列只更新一次。如果SET子句更新了视图的列,那么列名'PkParentId'可能会在视图定义中出现两次。
可以肯定的是,如果检查生成的SQL,它试图插入PkParentId两次与子插入,一次作为第一个参数,一次作为最后一个参数。这就像EF没有把PkParentId放在一起是父导航属性的外键所以我试着把它添加到子配置中:
HasRequired(x => x.Parent).WithOptional(y => y.Child).Map(z => z.MapKey("PkParentId"));
会产生相同的错误。奇怪的是,我找不到一个可选的关系,让我使用"HasForeignKey"除了WithMany()强制我回到1:m
EF中的一对一总是包含一个主键,同时也是一个外键。子键的主键复制父键的值,并通过FK约束引用它们。
但是子节点的主键与父节点的完全匹配!它没有。
但这是只有你知道的事。EF不需要知道Child
有一个组合键。把PkChildId
标记为computed:
public ChildConfiguration()
{
// Pk is composite on child
HasKey(e => e.PkParentId);
Property(p => p.PkChildId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
}
现在EF只能通过PkParentId
来识别Parent
s和Child
ren,当插入Child
时,它只会从数据库中读取PkChildId
,而不会将其用作实体键