Прочитать на английском

Поделиться через


Отношения «один-к-одному»

Связи "один к одному" используются, когда одна сущность связана с одной другой сущностью. Например, имеет Blog один BlogHeaderобъект, который BlogHeader принадлежит одному Blog.

Этот документ структурирован вокруг множества примеров. Примеры начинаются с распространенных случаев, которые также содержат основные понятия. В последующих примерах рассматриваются менее распространенные виды конфигурации. Хороший подход заключается в том, чтобы понять первые несколько примеров и концепций, а затем перейти к более поздним примерам на основе конкретных потребностей. На основе этого подхода мы начнем с простых "обязательных" и "необязательных" связей "один к одному".

Совет

Код для всех приведенных ниже примеров можно найти в OneToOne.cs.

Обязательный один к одному

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Связь "один к одному" состоит из:

Совет

Не всегда очевидно, какая сторона отношения "один к одному" должна быть основной, и какая сторона должна быть зависимой. Ниже приведены некоторые рекомендации.

  • Если таблицы базы данных для двух типов уже существуют, то таблица со столбцами внешнего ключа должна сопоставляться с зависимым типом.
  • Тип обычно является зависимым типом, если он не может логически существовать без другого типа. Например, нет смысла иметь заголовок для блога, который не существует, поэтому, BlogHeader естественно, зависимый тип.
  • Если существует естественная связь родительского или дочернего, то дочерний тип обычно является зависимым.

Таким образом, для связи в этом примере:

  • Свойство BlogHeader.BlogId внешнего ключа не допускает значение NULL. Это делает связь "обязательной", так как каждое зависимое (BlogHeader) должно быть связано с некоторым субъектом (Blog), так как его свойство внешнего ключа должно иметь некоторое значение.
  • Обе сущности имеют навигации, указывающие на связанную сущность с другой стороны связи.

Примечание

Необходимая связь гарантирует, что каждая зависимые сущности должна быть связана с какой-либо основной сущностью. Однако основная сущность всегда может существовать без какой-либо зависимой сущности. То есть требуемая связь не указывает на то, что всегда будет зависимый объект. В модели EF нет никакого способа, а также нет стандартного способа в реляционной базе данных, чтобы убедиться, что субъект связан с зависимым. Если это необходимо, она должна быть реализована в логике приложения (бизнес). Дополнительные сведения см. в разделе "Обязательные навигации ".

Совет

Связь с двумя навигациями —один из зависимых от субъекта и обратного от субъекта к зависимому — называется двунаправленной связью.

Эта связь обнаруживается по соглашению. Это означает следующее.

  • Blog обнаруживается как субъект в связи и BlogHeader обнаруживается как зависимый.
  • BlogHeader.BlogId обнаруживается как внешний ключ зависимого, ссылающегося Blog.Id на первичный ключ субъекта. Связь обнаруживается по мере необходимости, так как BlogHeader.BlogId не допускает значение NULL.
  • Blog.BlogHeader обнаруживается как эталонная навигация.
  • BlogHeader.Blog обнаруживается как эталонная навигация.

Важно!

При использовании ссылочных типов, допускающих значение NULL C#, навигация от зависимого от субъекта должна иметь значение NULL, если свойство внешнего ключа допускает значение NULL. Если свойство внешнего ключа не допускает значение NULL, то навигация может иметь значение NULL или нет. В этом случае BlogHeader.BlogId не допускается значение NULL, а BlogHeader.Blog также не допускает значение NULL. Конструкция = null!; используется для того, чтобы пометить это как преднамеренное для компилятора C#, так как EF обычно задает Blog экземпляр и не может иметь значение NULL для полностью загруженной связи. Дополнительные сведения см. в статье о работе с типами ссылок, допускающих значение NULL.

В случаях, когда навигации, внешний ключ или обязательный или необязательный характер связи не обнаруживаются по соглашению, эти вещи можно настроить явно. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

В приведенном выше примере конфигурация связей запускает тип основной сущности (Blog). Как и во всех отношениях, вместо этого он точно эквивалентен началу с типа зависимой сущности (BlogHeader). Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne(e => e.Header)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

Ни один из этих вариантов лучше, чем другой; они оба приводят к точно той же конфигурации.

Совет

Никогда не нужно настраивать связь дважды, начиная с субъекта, а затем снова начинаться с зависимого. Кроме того, попытка настроить основной и зависимый половины связи отдельно не работает. Выберите настроить каждую связь из одного конца или другого, а затем написать код конфигурации только один раз.

Необязательный один к одному

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

Это то же, что и предыдущий пример, за исключением того, что свойство внешнего ключа и навигация к субъекту теперь допускает значение NULL. Это делает связь "необязательным", так как зависимость (BlogHeader) не может быть связана с любым субъектом (Blog) путем задания свойства внешнего ключа и навигации.null

Важно!

При использовании ссылочных типов, допускающих значение NULL C#, свойство навигации от зависимого от субъекта должно иметь значение NULL, если свойство внешнего ключа допускает значение NULL. В этом случае BlogHeader.BlogId допускается значение NULL, поэтому BlogHeader.Blog также необходимо иметь значение NULL. Дополнительные сведения см. в статье о работе с типами ссылок, допускающих значение NULL.

Как и раньше, эта связь обнаруживается по соглашению. В случаях, когда навигации, внешний ключ или обязательный или необязательный характер связи не обнаруживаются по соглашению, эти вещи можно настроить явно. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired(false);
}

Обязательный один к одному с первичным ключом для связи первичного ключа

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

В отличие от связей "один ко многим", зависимый конец отношения "один к одному" может использовать его основное свойство или свойства в качестве свойства внешнего ключа или свойств. Это часто называется связью PK-to-PK. Это возможно только в том случае, если основной и зависимый типы имеют одинаковые типы первичных ключей, и результирующая связь всегда требуется, так как первичный ключ зависимого не может иметь значение NULL.

Любая связь "один к одному", в которой внешний ключ не обнаруживается по соглашению, должна быть настроена, чтобы указать субъект и зависимые концы отношения. Обычно это делается с вызовом HasForeignKey. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>();
}

Совет

HasPrincipalKey также может использоваться для этой цели, но это менее распространено.

Если свойство не указано в вызове HasForeignKey, а первичный ключ подходит, он используется в качестве внешнего ключа. В случаях, когда навигации, внешний ключ или обязательный или необязательный характер связи не обнаруживаются по соглашению, эти вещи можно настроить явно. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.Id)
        .IsRequired();
}

Обязательный один к одному с теневым внешним ключом

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

В некоторых случаях в модели может не потребоваться свойство внешнего ключа, так как внешние ключи представляют собой подробные сведения о том, как связь представлена в базе данных, которая не требуется при использовании связи исключительно объектно-ориентированной. Однако если сущности будут сериализованы, например для отправки по проводу, то значения внешнего ключа могут быть полезным способом сохранения сведений о связи, если сущности не находятся в форме объекта. Поэтому часто прагматично сохранять свойства внешнего ключа в типе .NET для этой цели. Свойства внешнего ключа могут быть закрытыми, что часто является хорошим компромиссом, чтобы избежать предоставления внешнего ключа, позволяя ему перемещаться с сущностью.

После предыдущего примера этот пример удаляет свойство внешнего ключа из типа зависимой сущности. Однако вместо использования первичного ключа EF вместо этого предписывается создать теневое свойство внешнего ключа, называемое BlogId типом int.

Важно отметить, что используются ссылочные типы , допускающие значение NULL, C#, поэтому для определения того, является ли свойство внешнего ключа пустым, используется значение NULL, поэтому связь является необязательной или обязательной. Если ссылочные типы, допускающие значение NULL, не используются, свойство теневого внешнего ключа будет иметь значение NULL по умолчанию, что делает связь необязательной по умолчанию. В этом случае используйте для IsRequired принудительного принудительного применения теневое свойство внешнего ключа к ненулевому значению и обязательной связи.

Эта связь снова требует некоторой конфигурации, чтобы указать субъект и зависимые концы:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId");
}

В случаях, когда навигации, внешний ключ или обязательный или необязательный характер связи не обнаруживаются по соглашению, эти вещи можно настроить явно. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

Необязательный один к одному с теневым внешним ключом

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

Как и в предыдущем примере, свойство внешнего ключа было удалено из типа зависимой сущности. Однако, в отличие от предыдущего примера, на этот раз свойство внешнего ключа создается как значение NULL, так как используются ссылочные типы C#, допускающие значение NULL, и навигация по типу зависимой сущности допускает значение NULL. Это делает связь необязательной.

Если ссылочные типы ссылок на C# не используются, свойство внешнего ключа по умолчанию будет создано как допустимое значение NULL. Это означает, что отношения с автоматически созданными свойствами тени являются необязательными по умолчанию.

Как и раньше, эта связь должна иметь определенную конфигурацию, чтобы указать основные и зависимые концы:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId");
}

В случаях, когда навигации, внешний ключ или обязательный или необязательный характер связи не обнаруживаются по соглашению, эти вещи можно настроить явно. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired(false);
}

Один к одному без перехода к субъекту

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

В этом примере свойство внешнего ключа было повторно введено, но навигация по зависимому элементу была удалена.

Совет

Связь только с одной навигацией—один из зависимых от субъекта или одного из зависимых, но не обоих— называется однонаправленной связью.

Эта связь обнаруживается по соглашению, так как внешний ключ обнаруживается таким образом, что указывает на зависимой стороне. В случаях, когда навигации, внешний ключ или обязательный или необязательный характер связи не обнаруживаются по соглашению, эти вещи можно настроить явно. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

Обратите внимание, что вызов WithOne не имеет аргументов. Это способ сказать EF, что навигация не выполняется.BlogHeader Blog

Если конфигурация начинается с сущности без навигации, то тип сущности в другом конце связи должен быть явно указан с помощью универсального HasOne<>() вызова. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne<Blog>()
        .WithOne(e => e.Header)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

Один к одному без перехода к субъекту и с теневым внешним ключом

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
}

Этот пример объединяет два из предыдущих примеров, удаляя как свойство внешнего ключа, так и навигацию по зависимым.

Как и раньше, эта связь должна иметь определенную конфигурацию, чтобы указать основные и зависимые концы:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

Более полную конфигурацию можно использовать для явной настройки имени навигации и внешнего ключа с соответствующим вызовом IsRequired() или IsRequired(false) по мере необходимости. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

Один к одному без навигации для зависимой

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

В предыдущих двух примерах были переходы от субъекта к зависимым, но переход от зависимого к субъекту отсутствует. Для следующих нескольких примеров навигация по зависимому объекту повторно представлена, а навигация по субъекту удаляется.

По соглашению EF будет рассматривать это как связь "один ко многим". Для его создания требуется некоторая минимальная конфигурация:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne();
}

Обратите внимание еще раз, что вызывается без аргументов, WithOne() чтобы указать, что навигация в этом направлении отсутствует.

В случаях, когда навигации, внешний ключ или обязательный или необязательный характер связи не обнаруживаются по соглашению, эти вещи можно настроить явно. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

Если конфигурация начинается с сущности без навигации, то тип сущности в другом конце связи должен быть явно указан с помощью универсального HasOne<>() вызова. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

Один к одному без навигаций

Иногда это может быть полезно для настройки связи без навигации. Такая связь может управляться только путем непосредственного изменения значения внешнего ключа.

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

Эта связь не обнаруживается по соглашению, так как навигации отсутствуют, указывающие на то, что два типа связаны. Его можно настроить явно в OnModelCreating. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne();
}

В этой конфигурации BlogHeader.BlogId свойство по-прежнему обнаруживается как внешний ключ по соглашению, и отношение является обязательным, так как свойство внешнего ключа не допускает значение NULL. Отношение можно сделать необязательным, сделав свойство внешнего ключа пустым.

Более полная явная конфигурация этой связи:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

Один к одному с альтернативным ключом

В всех примерах до сих пор свойство внешнего ключа зависимостей ограничивается свойством первичного ключа субъекта. Внешний ключ вместо этого может быть ограничен другим свойством, которое затем становится альтернативным ключом для типа основной сущности. Например:

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the BlogHeader.BlogId foreign key
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Эта связь не обнаруживается по соглашению, так как EF всегда будет создавать связь с первичным ключом. Его можно настроить явно с OnModelCreating помощью вызова HasPrincipalKey. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasPrincipalKey<Blog>(e => e.AlternateId);
}

HasPrincipalKey можно объединить с другими вызовами, чтобы явно настроить навигации, свойства внешнего ключа и обязательный или необязательный характер. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasPrincipalKey<Blog>(e => e.AlternateId)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

Один к одному с составным внешним ключом

В всех примерах до сих пор основное или альтернативное свойство ключа субъекта состоит из одного свойства. Первичные или альтернативные ключи также могут быть сформированы в форме нескольких свойств. Они называются составными ключами. Если субъект связи имеет составной ключ, внешний ключ зависимого также должен быть составным ключом с таким же количеством свойств. Например:

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Эта связь обнаруживается по соглашению. Однако оно будет обнаружено только в том случае, если составной ключ настроен явным образом, так как составные ключи не обнаруживаются автоматически. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(e => new { e.Id1, e.Id2 });
}

Важно!

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

HasPrincipalKey Оба HasForeignKey и могут использоваться для явного указания ключей с несколькими свойствами. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasOne(e => e.Header)
                .WithOne(e => e.Blog)
                .HasPrincipalKey<Blog>(e => new { e.Id1, e.Id2 })
                .HasForeignKey<BlogHeader>(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

Совет

В приведенном выше коде вызовы HasKey и HasOne были сгруппированы в вложенный построитель. Вложенные построители удаляют необходимость Entity<>() вызывать несколько раз для одного типа сущности, но функционально эквивалентны вызову Entity<>() несколько раз.

Обязательный один к одному без каскадного удаления

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

По соглашению необходимые связи настраиваются для каскадного удаления. Это связано с тем, что зависимость не может существовать в базе данных после удаления субъекта. База данных может быть настроена для создания ошибки, обычно сбой приложения, а не автоматического удаления зависимых строк, которые больше не могут существовать. Для этого требуется некоторая конфигурация:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .OnDelete(DeleteBehavior.Restrict);
}

Самосознание ссылки на один к одному

Во всех предыдущих примерах тип основной сущности отличается от типа зависимой сущности. Это не обязательно должно быть так. Например, в приведенных ниже типах каждый из них Person при необходимости связан с другим Person.

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

    public int? HusbandId { get; set; } // Optional foreign key property
    public Person? Husband { get; set; } // Optional reference navigation to principal
    public Person? Wife { get; set; } // Reference navigation to dependent
}

Эта связь обнаруживается по соглашению. В случаях, когда навигации, внешний ключ или обязательный или необязательный характер связи не обнаруживаются по соглашению, эти вещи можно настроить явно. Рассмотрим пример.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasOne(e => e.Husband)
        .WithOne(e => e.Wife)
        .HasForeignKey<Person>(e => e.HusbandId)
        .IsRequired(false);
}

Примечание

Для связей "один к одному", так как основные и зависимые типы сущностей совпадают, указывая, какой тип содержит внешний ключ, не уточняет зависимый конец. В этом случае навигация, указанная в HasOne точках от зависимого к субъекту, и навигация, указанная в WithOne точках от субъекта к зависимой.