我有以下型号:
public class ItemRental
{
[Key]
public Int32 ItemRentalId { get; set; }
public Int32 ItemId { get; set; }
public Int32 ChargeItemId { get; set; }
[ForeignKey("ItemId")]
public Item Item { get; set; }
[ForeignKey("ChargeItemId")]
public Item ChargeItem { get; set; }
}
public class Item
{
[Key]
public Int32 ItemId { get; set; }
public Int32? ItemRentalId { get; set; }
[ForeignKey("ItemRentalId")]
public ItemRental ItemRental { get; set; }
}
ItemRental
与Item
的关系为1..N,与ChargeItem
的关系为1..N。
问题是我需要从Item
返回到ChargeItem
的关系,因此在ItemRentalId
上添加了属性Item
。这是可以为空的,因为不是每个Item
都必须有ItemRental
。
是否可以仅使用注释创建此关系?
我尝试了流利的api:
modelBuilder.Entity<Item>()
.HasOptional(m => m.ItemRental)
.WithRequired(c => c.ChargeItem)
.Map(p => p.MapKey("ItemRentalId"));
但是在进行迁移之后,它没有使用ChargeItemId作为关系。
问题是当我运行此迁移时,它不会将ItemRentalId
视为FK导航属性。
答案 0 :(得分:1)
所以,如果我理解正确,你的问题是将一个映射到零或一关系。 您正在体验的是实体框架的按设计特征。由于很多原因,处理一对一关系(及其可选对应关系)很棘手。当你这样做时,你不能在模型中指定外键 - 而是实体的主键也将是主端的外键,不能指定额外的FK。
有关详细信息,请参阅下文。
映射一到零或一个
所以,让我们说你有以下型号:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
}
public class MyDemoContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Car> Cars { get; set; }
}
现在你要设置它以便你可以表达以下规格:一个人可以拥有一辆或零车,并且每辆车都完全属于一个人(关系是双向的,所以如果CarA属于PersonA,那么PersonA&#39;拥有&#39; CarA)。
所以让我们稍微修改一下模型:添加导航属性和外键属性:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int CarId { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
配置:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithOptional(p => p.Car);
}
}
此时这应该是不言自明的。汽车有一个必需的人(HasRequired()),该人有一辆可选的汽车(WithOptional())。同样,配置此关系的哪一方并不重要,只需在使用Has / With和Required / Optional的正确组合时要小心。从Person
方面看,它看起来像这样:
public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
public PersonEntityTypeConfiguration()
{
this.HasOptional(p => p.Car).WithOptional(c => c.Person);
}
}
现在让我们看一下db模式:
仔细观察:您可以看到People
中没有FK可以引用Car
。此外,Car
中的FK不是PersonId
,而是CarId
。这是FK的实际脚本:
ALTER TABLE [dbo].[Cars] WITH CHECK ADD CONSTRAINT [FK_dbo.Cars_dbo.People_CarId] FOREIGN KEY([CarId])
REFERENCES [dbo].[People] ([PersonId])
这意味着我们在模型中拥有的CarId
和PersonId
foregn键属性基本上被忽略了。它们位于数据库中,但它们不是外键,因为它可能是预期的。这是因为一对一映射不支持将FK添加到EF模型中。这是因为一对一映射在关系数据库中存在很大问题。
这个想法是每个人都可以拥有一辆汽车,而这辆汽车只能属于那个人。或者可能存在人员记录,其中没有与之相关的汽车。
那怎么用外键来表示呢?显然,PersonId
中可能有Car
,CarId
中可能有People
。为了强制每个人只能拥有一辆汽车,PersonId
中Car
必须是唯一的。但如果PersonId
中的People
是唯一的,那么如何在PersonId
为NULL
的情况下添加两条或更多条记录(多条车辆不拥有所有者) )?答:你不能(实际上,你可以在SQL Server 2008及更新版本中创建一个过滤的唯一索引,但让我们暂时忘掉这个技术性;更不用说其他RDBMS了)。更不用说你指定关系的两端......
如果People
和Car
表格具有相同的&#39;,则执行此规则的唯一真正方法主键(连接记录中的值相同)。要做到这一点,CarId
中的Car
必须是人民PK的PK和FK。这使整个架构变得混乱。当我使用它时,我宁愿在Car
PersonId
中命名PK / FK,并相应地对其进行配置:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public string LicensePlate { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithOptional(p => p.Car);
this.HasKey(c => c.PersonId);
}
}
不理想,但可能会好一些。但是,在使用此解决方案时,您必须保持警惕,因为它违反了通常的命名约定,这可能会让您误入歧途。这是从这个模型生成的模式:
因此,这种关系不是由数据库架构强制执行的,而是由Entity Framework本身强制执行的。这就是为什么你在使用它时必须非常小心,不要让任何人直接对数据库发脾气。