实体框架的核心零或一至零或一关系

时间:2019-02-02 21:39:17

标签: c# entity-framework-core ef-fluent-api

给出以下类别:

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

 modelBuilder.Entity<A>()
                .HasOne(a => a.B)
                .WithOne(b => b.A)
                .HasForeignKey<A>(a => a.BId);

在创建对象并将其添加到数据库时,相应表中的内容如下所示:

  • [A] .Bid已设置
  • [B] .AId = null

当我使用EF Core检索数据时:

  • A·B被设定,A.BId设置
  • 设置了
  • BA.A,但 BA.AId为空

如何设置B.AId?

2 个答案:

答案 0 :(得分:3)

这些0..1 : 0..1关系通常在没有明显的主要实体的实体之间定义。我喜欢汽车和驾驶员的例子,这比A和B想象的多。

您追求的模型如下:

OneToOneWithKeys

有两个相互的外键,它们都有一个唯一索引以在数据库级别强制1:1。

此处不能使用HasOne - WithOne组合,因为它始终需要一条HasForeignKey指令来说明哪个实体是主体。这也仅将一个字段配置为外键。在您的示例中,B.AId只是一个常规字段。如果您不给它一个值,EF也不会。

上述模型的映射比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关联变得唯一。

将创建以下数据库模型:

  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;

它将创建两个可空的外键,它们均由唯一的过滤索引建立索引。完美!

但是...

EF并不认为这是双向的一对一关系。是的。两个FK就是两个独立的FK。但是,考虑到数据完整性,应该在两端之间建立这种关系:如果驾驶员要求汽车(设置driver.CarID),那么汽车也应该连接到驾驶员(设置car.DriverID),否则可能已连接了另一个驱动程序。

将现有的汽车和驾驶员结合在一起时,可以使用一些辅助方法,例如在Car中:

public void SetDriver(Driver driver)
{
    Driver = driver;
    driver.Car = this;
}

但是,当在一个过程中同时创建了CarDriverInvalidOperationException时,这很笨拙。 EF会抛出一个SaveChanges

  

由于在要保存的数据中检测到循环依赖性,因此无法保存更改:'Car [Added] <-Car {'CarID'} Driver [Added] <-Driver {'DriverID'} Car [Added]' 。

这意味着:可以一次设置一个FK,但是仅在保存数据后才能设置另一个。这需要两个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

替代:联结表

现在,我尽一切努力来解释所有这些的原因是:我认为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; } } 关联应该由具有唯一外键的联结表来建模:

OntToOneJunction

通过使用联结表-

  1. 可以通过原子操作建立关联,而不是设置两个外键的容易出错的操作。
  2. 实体本身是独立的:它们没有外键,它们并不需要履行其职责。

此模型可以通过此类模型实现:

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

现在只需一个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导航到 Socket pptpSocket = new Socket("51.15.151.165", 1723); PrintWriter out = new PrintWriter(pptpSocket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(pptpSocket.getInputStream())); System.out.println(in.readLine()); vv有点麻烦。好吧,亲自看看哪种模型最适合您。

答案 1 :(得分:0)

在 EFCore 3.1 中,您可以像这样执行一对零关系:

public class Car
{
    public int Id { get; set; }

    public int DriverId { get; set; }
        
    public Driver Driver { get; set; }
}

public class Driver
{
    public int Id { get; set; }

    public Car Driver { get; set; }
}

在你的 dbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Car>()
        .HasOne(x => x.Driver)
        .WithOne(x => x.Car)
        .HasForeignKey<Car>(x => x.DriverId);
}

然后你可以包括:

汽车司机

dbContext.Driver.Inclide(x => x.Car)...

司机开的车

dbContext.Car.Inclide(x => x.Driver)...