实体框架核心零或一到零或一的关系 [英] Entity Framework Core zero-or-one to zero-or-one relation
问题描述
给出以下类别:
public class A
{
public int Id {get; set;}
public int? BId {get; set;}
public B B {get; set;}
}
public class B
{
public int Id {get; set;}
public int? AId {get; set;}
public A A {get; set;}
}
然后使用Fluent API
Then with Fluent API
modelBuilder.Entity<A>()
.HasOne(a => a.B)
.WithOne(b => b.A)
.HasForeignKey<A>(a => a.BId);
在创建对象并将其添加到数据库时,相应表中的内容如下所示:
When creating objects and add them to database, things look like following in the corresponding tables:
- [A] .Bid已设置
- [B] .AId = null
当我使用EF Core检索数据时:
When I retrieve data using EF Core:
- 设置了A.B,设置了A.BId 设置了
- BA.A,但 BA.AId为空.
我还应该如何设置B.AId?
What should I do to have B.AId set as well?
推荐答案
这些 0..1:0..1
关系通常在实体之间定义,而这些实体都不是显而易见的主要实体.我喜欢汽车和驾驶员的例子,这比A和B想象的多.
These 0..1 : 0..1
relations are usually defined between entities of which none is an obvious principal entity. I like the example of cars and drivers, which is a bit more imaginable than A and B.
您要寻找的模型如下:
有两个相互的外键,它们都有一个唯一索引以在数据库级别强制执行1:1.
There are two mutual foreign keys, both of which have a unique index to enforce 1:1 at the database level.
此处无法使用 HasOne-WithOne
组合,因为这始终需要 HasForeignKey
指令来判断哪个实体是主体.这也仅将一个字段配置为外键.在您的示例中, B.AId
只是一个常规字段.如果您不给它一个值,EF也不会.
The HasOne - WithOne
combi can't be used here, because that always requires a HasForeignKey
instruction to tell which entity is principal. This also configures only one field as foreign key. In your example, B.AId
is just a regular field. If you don't give it a value, EF won't either.
与 HasOne-WithOne
相比,上述模型的映射更加麻烦:
The mapping of the above model is a bit more cumbersome than HasOne - WithOne
:
var carEtb = modelBuilder.Entity<Car>();
var driverEtb = modelBuilder.Entity<Driver>();
carEtb.HasOne(c => c.Driver).WithMany();
carEtb.HasIndex(c => c.DriverID).IsUnique();
driverEtb.HasOne(d => d.Car).WithMany();
driverEtb.HasIndex(c => c.CarID).IsUnique();
因此,通过外键上的索引使两个0..1:n关联变得唯一.
So there are two 0..1:n association that are made unique by indexes on the foreign keys.
哪个将创建以下数据库模型:
Which creates the following database model:
CREATE TABLE [Drivers] (
[ID] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
[CarID] int NULL,
CONSTRAINT [PK_Drivers] PRIMARY KEY ([ID])
);
CREATE TABLE [Cars] (
[ID] int NOT NULL IDENTITY,
[Brand] nvarchar(max) NULL,
[Type] nvarchar(max) NULL,
[DriverID] int NULL,
CONSTRAINT [PK_Cars] PRIMARY KEY ([ID]),
CONSTRAINT [FK_Cars_Drivers_DriverID] FOREIGN KEY ([DriverID])
REFERENCES [Drivers] ([ID]) ON DELETE NO ACTION
);
CREATE UNIQUE INDEX [IX_Cars_DriverID] ON [Cars] ([DriverID])
WHERE [DriverID] IS NOT NULL;
CREATE UNIQUE INDEX [IX_Drivers_CarID] ON [Drivers] ([CarID])
WHERE [CarID] IS NOT NULL;
ALTER TABLE [Drivers] ADD CONSTRAINT [FK_Drivers_Cars_CarID] FOREIGN KEY ([CarID])
REFERENCES [Cars] ([ID]) ON DELETE NO ACTION;
它将创建两个可空的外键,它们均由唯一的过滤后的索引建立索引.完美!
It creates two nullable foreign keys both indexed by a unique filtered index. Perfect!
但是...
EF并不认为这是双向的一对一关系.是的,这是正确的.两个FK就是两个独立的FK.但是,鉴于数据完整性,应该在两端之间建立这种关系:如果驾驶员要求汽车(设置 driver.CarID
),则汽车也应附加到驾驶员(设置car.DriverID
),否则可以将另一个驱动程序连接到它.
EF doesn't see this as a bidirectional one-on-one relationship. And rightly so. The two FKs are just that, two independent FKs. However, in view of data integrity the relationship should be established by both ends: if a driver claims a car (sets driver.CarID
), the car should also be attached to the driver (set car.DriverID
), otherwise another driver could be connected to it.
当现有汽车和驾驶员结合在一起时,可以使用一些辅助方法,例如在 Car
中:
When existing car and drivers are coupled a little helper method could be used, for example in Car
:
public void SetDriver(Driver driver)
{
Driver = driver;
driver.Car = this;
}
但是,当在一个过程中同时创建和关联的 Car
和 Driver
时,这很笨拙.EF会抛出 InvalidOperationException
:
However, when both a Car
and Driver
are created and associated in one process, this is clumsy. EF will throw an InvalidOperationException
:
由于在要保存的数据中检测到循环依赖性,因此无法保存更改:'Car [Added]<-Car {'CarID'} Driver [Added]<-Driver {'DriverID'} Car [Added]".
Unable to save changes because a circular dependency was detected in the data to be saved: 'Car [Added] <- Car { 'CarID' } Driver [Added] <- Driver { 'DriverID' } Car [Added]'.
这意味着:可以一次设置一个FK,但是仅在保存数据后才能设置另一个.这需要在事务中包含两个 SaveChanges
调用,这些调用必须包含一段非常必要的代码:
Which means: one of the FKs can be be set at once, but the other one can only be set after saving the data. That requires two SaveChanges
calls enclosed by a transaction in a pretty imperative piece of code:
using (var db = new MyContext())
{
using (var t = db.Database.BeginTransaction())
{
var jag = new Car { Brand = "Jaguar", Type = "E" };
var peter = new Driver { Name = "Peter Sellers", Car = jag };
db.Drivers.Add(peter);
db.SaveChanges();
jag.Driver = peter;
db.SaveChanges();
t.Commit();
}
}
替代:联结表
因此,现在我尽一切努力来解释所有这些的原因:我认为, 0..1:0..1
关联应通过具有唯一外键的联结表来建模:
Alternative: junction table
So now the reason why I go to these lengths explaining all this: in my opinion, 0..1 : 0..1
associations should be modeled by a junction table with unique foreign keys:
通过使用联结表-
- 可以通过原子操作建立关联,而不是设置两个外键的容易出错的操作.
- 实体本身是独立的:它们没有外键,它们实际上并不需要履行其职责.
此模型可以通过此类模型实现:
This model can be implemented by this class model:
public class Car
{
public int ID { get; set; }
public string Brand { get; set; }
public string Type { get; set; }
public CarDriver CarDriver { get; set; }
}
public class Driver
{
public Driver()
{ }
public int ID { get; set; }
public string Name { get; set; }
public CarDriver CarDriver { get; set; }
}
public class CarDriver
{
public int CarID { get; set; }
public Car Car { get; set; }
public int DriverID { get; set; }
public virtual Driver Driver { get; set; }
}
以及映射:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var carDriverEtb = modelBuilder.Entity<CarDriver>();
carDriverEtb.HasKey(cd => new { cd.CarID, cd.DriverID });
carDriverEtb.HasIndex(cd => cd.CarID).IsUnique();
carDriverEtb.HasIndex(cd => cd.DriverID).IsUnique();
}
现在创建驾驶员和汽车和的关联可以轻松地通过一个 SaveChanges
调用完成:
Now creating drivers and cars and their associations can easily be done in one SaveChanges
call:
using (var db = new MyContext(connectionString))
{
var ford = new Car { Brand = "Ford", Type = "Mustang" };
var jag = new Car { Brand = "Jaguar", Type = "E" };
var kelly = new Driver { Name = "Kelly Clarkson" };
var peter = new Driver { Name = "Peter Sellers" };
db.CarDrivers.Add(new CarDriver { Car = ford, Driver = kelly });
db.CarDrivers.Add(new CarDriver { Car = jag, Driver = peter });
db.SaveChanges();
}
唯一的缺点是从 Car
导航到 Driver
vv有点麻烦.好吧,亲自看看哪种模型最适合您.
The only drawback is that navigting from Car
to Driver
vv is a bit les convenient. Well, see for yourself which model suit you best.
这篇关于实体框架核心零或一到零或一的关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!