Entity Framework Core От нуля или одного до нуля или одного отношения - PullRequest
0 голосов
/ 03 февраля 2019

С учетом этих классов:

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:

  • AB установлен, A.BId установлен
  • BA установлен, но B.AId равен нулю .

Что мне делать, чтобы установить B.AId?

1 Ответ

0 голосов
/ 05 февраля 2019

Эти 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;

Создает два внешних ключа, которые могут иметь значение NULL и оба индексируютсяуникальный отфильтрованный индекс.Отлично!

Но ...

EF не видит в этом двусторонних отношений один на один.И это правильно.Два ФК - это просто два независимых ФК.Тем не менее, с точки зрения целостности данных, отношения должны быть установлены с обеих сторон: если водитель претендует на автомобиль (устанавливает driver.CarID), автомобиль также должен быть присоединен к водителю (установлен car.DriverID), в противном случае другой водитель может быть

Когда существующие автомобили и водители связаны, можно использовать небольшой вспомогательный метод, например, в Car:

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

Однако, когда оба Car иDriver создаются и , связанные в одном процессе, это неуклюже.EF выбросит InvalidOperationException:

Невозможно сохранить изменения, поскольку в данных, которые будут сохранены, обнаружена циклическая зависимость: 'Car [Added] <- Car {' CarID '} Driver [Добавлено] <- Driver {'DriverID'} Car [Добавлено] '.</p>

Это означает, что один из FK может быть установлен сразу, а другой - только после сохранения данных.Это требует двух 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();
    }
}

Альтернатива: таблица соединений

Итак, теперь причина, по которой я перехожу к этим длинам, объясняя всеэто: по моему мнению, 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 до Driver vvэто немного менее удобно.Ну, посмотрите сами, какая модель вам больше подходит.

...