Ответ 1
"Это не сложно" - это то, что я, когда читаю ваш вопрос. Но снова я обнаружил, что ассоциации "один к одному" - предательские ублюдки. Здесь мы идем.
Я предполагаю, что под 0..1 – 0..1
вы подразумеваете, что два объекта могут существовать независимо друг от друга, но также могут быть исключительно связаны друг с другом.
Позволяет сделать его конкретным. Car
и Driver
. Представьте себе множество автомобилей и водителей, среди которых CarA и DriverA. Теперь предположим, что вы хотите, чтобы CarA связался с DriverA, и ваша реализация заключается в том, что DriverA связывается с CarA. Но как только DriverA сделает это, вы хотите, чтобы CarA был только для DriverA, Ассоциация CarA больше не является необязательной, поэтому ее также следует установить сразу.
Как это реализовать?
Вариант 1:
Если это рабочая модель:
public class Car
{
public int CarId { get; set; }
public string Name { get; set; }
public int? DriverId { get; set; }
public virtual Driver Driver { get; set; }
}
public class Driver
{
public int DriverId { get; set; }
public string Name { get; set; }
public int? CarId { get; set; }
public virtual Car Car { get; set; }
}
технически DriverA может иметь внешний ключ для CarA и CarA внешнего ключа DriverB.
Поэтому, когда установлен внешний ключ DriverA-CarA
, вы должны "одновременно" установить обратный внешний ключ CarA-DriverA
. Это то, что вы должны делать в коде, что означает, что это бизнес-правило. И на самом деле это не атомная операция, поэтому вы должны убедиться, что это было сделано в одной транзакции с базой данных.
Модель класса по крайней мере поддерживает прецедент, но это тоже разрешительно. Он должен быть ограничен. Что еще более важно, он не будет работать с EF. EF жалуется на то, что нужно установить основной конец. И если вы это сделаете, EF не создаст двунаправленную связь.
Альтернативное отображение было предложено здесь. Я пробовал это, но с двумя необязательными ассоциациями:
В конфигурации отображения Driver
:
this.HasOptional(t => t.Car).WithMany().HasForeignKey(d => d.CarId);
В конфигурации отображения Car
:
this.HasOptional(t => t.Driver).WithMany().HasForeignKey(c => c.DriverId);
(альтернативы аннотации данных нет)
Я обнаружил, что EF устанавливает только одно значение внешнего ключа в базе данных при создании нового драйвера и автомобиля. Вы должны устанавливать и сохранять обе ассоциации отдельно, управляя своей собственной транзакцией. С существующими объектами вы все равно должны установить оба внешних ключа, хотя это можно сохранить одним вызовом SaveChanges
.
Лучшие варианты? Посмотрим...
Вариант 2:
Это ассоциация "один ко многим", как указано в ссылке, на которую вы ссылаетесь. Эта модель требует внешних ограничений, но создание ассоциации является атомарным. И у вас все еще есть ссылка на одном конце и коллекция на другом конце. И он легко сопоставляется с EF.
Вариант 3:
Вы можете создать таблицу соединений CarDriver
, которая имеет два внешних ключа, до Car
и Driver
, оба из которых содержат свой уникальный первичный ключ:
Это регулярная ассоциация "многие-ко-многим". По умолчанию EF будет отображать это как модель класса, в которой Car
и Driver
имеют свойства сопоставления, указывающие друг на друга, а таблица соединений не отображается напрямую:
public class Car
{
public int CarId { get; set; }
public string Name { get; set; }
public virtual ICollection<Driver> Drivers { get; set; }
}
public class Driver
{
public int DriverId { get; set; }
public string Name { get; set; }
public virtual ICollection<Car> Cars { get; set; }
}
Теперь создание ассоциации - это атомная операция. Совершенно возможно сопоставить эту модель с EF. Общие ссылки исчезли, но вы по-прежнему можете получить FirstOrDefault()
свойств коллекции как суррогатную ссылку.
Но там важная добыча. Теперь каждый объект может иметь любое количество ассоциированных копий. Если вы создаете ассоциацию, вам нужно закодированное бизнес-правило, которое проверяет, нет ли у задействованных объектов каких-либо ассоциаций. Возможно, этот вариант еще хуже, чем вариант 2. Но я упомянул об этом из-за следующей опции:
Вариант 4
Вариант 3 является атомарным, но он также требует внешних ограничений. Чтобы сделать ассоциацию исключительной, оба столбца в CarDriver
должны иметь уникальные ключи, поэтому каждый автомобиль или драйвер может появляться только один раз в таблице. По этим показателям модель реализует двунаправленную необязательную ассоциацию 1:1 сама по себе. Любой код, работающий над ним, должен подчиняться правилам. Безопасный и надежный...
Но вы хотите этого?
Вы никогда не собираетесь настраивать параметр 4 в коде EF-first. Не с плавным отображением и аннотациями данных. Вы должны использовать "Миграции" для этого или сначала работать с базой данных.
Если база данных используется несколькими приложениями, лучшим вариантом может быть привязка ограничений в модели данных. Если правило вряд ли изменится, это также может быть жизнеспособным вариантом. Однако, если в базе данных работают только одно (или два) приложения, и в будущем может измениться бизнес-правило, я предпочел бы более либеральную модель данных, сопровождаемую закодированными правилами. Кодовая бизнес-логика намного легче изменить, чем модель данных.
Кроме того, даже с опцией 4 вам нужна бизнес-логика, чтобы проверить существование ассоциации, прежде чем пытаться ее создать, иначе для вас это сделает уродливая ошибка базы данных.
Выводы
Вариант 1 близок к тому, что вы хотите. Но мне не нравится обязательство устанавливать оба внешних ключа, это легко забывается или игнорируется будущими разработчиками.
Но Вариант 2 и 3 имеют еще более серьезные требования в отношении закодированных бизнес-правил, которые можно забыть. И коллекции неестественны, поскольку суррогатное "1" заканчивается. Вариант 3 имеет некоторое обращение ко мне, потому что Car
и Driver
полностью независимы в базе данных, а ассоциация - это запись с непустыми внешними ключами (DBA тоже любят это).
Вариант 4 имеет такую же привлекательность, и это лучший вариант, когда нескольким приложениям придется реализовывать внешние ограничения, которые должны быть наложены на опции 2 и 3. Кроме того, даже если закодированные правила забыты, ограничения базы данных окончательный улов. Но он не может быть легко реализован с помощью EF-кода.