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

问题描述 投票:2回答:1

考虑到这些类:

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;} 
}

然后用流利的API

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

当创建对象并将其添加到数据库中,事情看起来像对应表中的以下内容:

  • [A] .BId被设定
  • [B] = .AId空

当我检索使用EF核心数据:

  • A·B设置,A.BId设置
  • B.A设置,但B.AId为空。

我应该怎么做才能有B.AId设置呢?

c# entity-framework-core ef-fluent-api
1个回答
4
投票

这些0..1 : 0..1关系通常其中零是一个明显的主要实体的实体之间。我喜欢赛车和车手的例子,这是比A和B更想象了一下

你的模型看起来像这样以后是:

OneToOneWithKeys

有两个相互外键,两者都具有强制执行1唯一索引:1在数据库级别。

HasOne - WithOne COMBI不能在这里使用,因为总是需要一个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不认为这是一个双向的一对一的关系。这是正确的。这两个FKS是正义的,两个独立的FKS。然而,考虑数据完整性的关系应由两端建立:如果驾驶员要求汽车(套driver.CarID)时,汽车也应附接到驱动器(组car.DriverID),否则另一个驱动程序可以连接到它。

当现有的汽车和驱动器被耦合,可以使用一个小的辅助方法中,例如在Car

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

但是,这两个一CarDriver被创建并在一个进程相关联时,这是笨拙。 EF将抛出一个InvalidOperationException

无法保存更改,因为在数据中检测到的循环依赖关系被保存:“车[补充] < - 汽车{‘CarID’}驱动程序[补充] < - 驱动程序{‘DriverID’}车[补充]”。

这意味着:在FKS中的一个可以进行一次性设置,但其他人可以只保存数据后进行设置。这需要通过一个事务在一个漂亮的命令式的代码段封闭双SaveChanges电话:

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();
    }
}

Alternative: junction table

所以,现在的原因,我去这些长度解释这一切:在我看来,0..1 : 0..1协会应通过独特的外键结合表进行建模:

OntToOneJunction

通过使用结合表 -

  1. 该协会在一个原子操作,而不是设置两个外键的容易出错的操作来建立。
  2. 实体本身是独立的:他们没有他们并不真正需要履行自己的角色外键。

这种模式可以通过这个类模型来实现:

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调用来完成:

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 navigting到Driver VV是有点莱方便。好了,自己看看哪种模式适合你最好的。

© www.soinside.com 2019 - 2024. All rights reserved.