Необязательное отношение один к одному с использованием Entity Framework Fluent API
Мы хотим использовать опциональные отношения один-к-одному, используя Entity Framework Code First. У нас есть две сущности.
public class PIIUser
{
public int Id { get; set; }
public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
PIIUser
может иметь LoyaltyUserDetail
но LoyaltyUserDetail
должен иметь PIIUser
, Мы попробовали эти техники свободного подхода.
modelBuilder.Entity<PIIUser>()
.HasOptional(t => t.LoyaltyUserDetail)
.WithOptionalPrincipal(t => t.PIIUser)
.WillCascadeOnDelete(true);
Этот подход не создал LoyaltyUserDetailId
внешний ключ в PIIUsers
Таблица.
После этого мы попробовали следующий код.
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(t => t.PIIUser)
.WithRequiredDependent(t => t.LoyaltyUserDetail);
Но на этот раз EF не создавал никаких внешних ключей в этих 2 таблицах.
У вас есть идеи по этому вопросу? Как мы можем создать однозначное необязательное отношение, используя свободную структуру API
6 ответов
EF Code First поддерживает 1:1
а также 1:0..1
отношения. Последнее - это то, что вы ищете ("один к нулю или один").
Ваши попытки свободно говорят, что требуется с обоих концов в одном случае и необязательно с обоих концов в другом.
То, что вам нужно, необязательно на одном конце и обязательно на другом.
Вот пример из книги "Программирование EF Code First"
modelBuilder.Entity<PersonPhoto>()
.HasRequired(p => p.PhotoOf)
.WithOptional(p => p.Photo);
PersonPhoto
Сущность имеет свойство навигации, называемое PhotoOf
это указывает на Person
тип. Person
Тип имеет свойство навигации под названием Photo
это указывает на PersonPhoto
тип.
В двух связанных классах вы используете первичный ключ каждого типа, а не внешние ключи. т.е. вы не будете использовать LoyaltyUserDetailId
или же PIIUserId
свойства. Вместо этого отношения зависят от Id
поля обоих типов.
Если вы используете свободный API, как указано выше, вам не нужно указывать LoyaltyUser.Id
как внешний ключ, EF поймет это.
Таким образом, не имея вашего кода для тестирования себя (я ненавижу делать это из головы)... я бы перевел это в ваш код как
public class PIIUser
{
public int Id { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public PIIUser PIIUser { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(lu => lu.PIIUser )
.WithOptional(pi => pi.LoyaltyUserDetail );
}
Это говорит LoyaltyUserDetails PIIUser
свойство обязательно и PIIUser's LoyaltyUserDetail
свойство не является обязательным.
Вы можете начать с другого конца:
modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);
который сейчас говорит PIIUser's LoyaltyUserDetail
свойство является необязательным и LoyaltyUser's PIIUser
собственность обязательна.
Вы всегда должны использовать шаблон HAS / WITH.
HTH и FWIW, отношения один к одному (или один к нулю / один) являются одними из самых запутанных отношений, которые нужно сначала настроить в коде, чтобы вы не были одиноки!:)
Просто нравится, если у вас есть отношения один ко многим между LoyaltyUserDetail
а также PIIUser
так что вы должны быть
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(m => m.PIIUser )
.WithMany()
.HasForeignKey(c => c.LoyaltyUserDetailId);
EF должен создать все необходимые вам внешние ключи и просто не заботиться о WithMany!
Есть несколько вещей не так с вашим кодом.
Соотношение 1:1: PK <-PK, где одна сторона PK также является FK, или PK <-FK + UC, где сторона FK не является PK и имеет UC. Ваш код показывает, что у вас есть FK <-FK, так как вы определяете обе стороны, чтобы иметь FK, но это неправильно. Я рекон PIIUser
сторона ПК и LoyaltyUserDetail
это сторона FK. Это означает PIIUser
не имеет поля FK, но LoyaltyUserDetail
делает.
Если соотношение 1:1 является необязательным, сторона FK должна иметь по крайней мере 1 обнуляемое поле.
pswg выше ответил на ваш вопрос, но сделал ошибку, что он / она также определил FK в PIIUser, что, конечно, неправильно, как я описал выше. Так что определяйте обнуляемое поле FK в LoyaltyUserDetail
определите атрибут в LoyaltyUserDetail
чтобы отметить это поле FK, но не указывайте поле FK в PIIUser
,
Вы получаете исключение, которое вы описали выше под постом pswg, потому что ни одна сторона не является стороной PK (основной конец).
EF не очень хорош в 1:1, поскольку он не способен обрабатывать уникальные ограничения. Сначала я не эксперт по Кодексу, поэтому я не знаю, способен ли он создать UC или нет.
(edit) btw: A 1:1 B (FK) означает, что создано только 1 ограничение FK, причем цель B указывает на PK A, а не 2.
public class User
{
public int Id { get; set; }
public int? LoyaltyUserId { get; set; }
public virtual LoyaltyUser LoyaltyUser { get; set; }
}
public class LoyaltyUser
{
public int Id { get; set; }
public virtual User MainUser { get; set; }
}
modelBuilder.Entity<User>()
.HasOptional(x => x.LoyaltyUser)
.WithOptionalDependent(c => c.MainUser)
.WillCascadeOnDelete(false);
это решит проблему со ссылками и иностранными ключами
при обновлении или удалении записи
Это бесполезно для оригинального плаката, но для тех, кто все еще использует EF6, кому нужно, чтобы внешний ключ отличался от первичного, вот как это сделать:
public class PIIUser
{
public int Id { get; set; }
//public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
modelBuilder.Entity<PIIUser>()
.HasRequired(t => t.LoyaltyUserDetail)
.WithOptional(t => t.PIIUser)
.Map(m => m.MapKey("LoyaltyUserDetailId"));
Обратите внимание, что вы не можете использовать LoyaltyUserDetailId
поле, потому что, насколько я могу судить, его можно указать только с помощью свободного API. (Я пробовал три способа сделать это, используяForeignKey
атрибут и ни один из них не работал).
Попробуйте добавить ForeignKey
приписать LoyaltyUserDetail
имущество:
public class PIIUser
{
...
public int? LoyaltyUserDetailId { get; set; }
[ForeignKey("LoyaltyUserDetailId")]
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
...
}
И PIIUser
имущество:
public class LoyaltyUserDetail
{
...
public int PIIUserId { get; set; }
[ForeignKey("PIIUserId")]
public PIIUser PIIUser { get; set; }
...
}
Одна вещь, которая путает с вышеупомянутыми решениями, - то, что Первичный ключ определен как "Id" в обеих таблицах, и если у вас есть первичный ключ, основанный на имени таблицы, это не будет работать, я изменил классы, чтобы проиллюстрировать то же самое, т. е. необязательная таблица не должна определять свой собственный первичный ключ, а должна использовать то же имя ключа из основной таблицы.
public class PIIUser
{
// For illustration purpose I have named the PK as PIIUserId instead of Id
// public int Id { get; set; }
public int PIIUserId { get; set; }
public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
// Note: You cannot define a new Primary key separately as it would create one to many relationship
// public int LoyaltyUserDetailId { get; set; }
// Instead you would reuse the PIIUserId from the primary table, and you can mark this as Primary Key as well as foreign key to PIIUser table
public int PIIUserId { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
И затем следуют
modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);
Сделал бы трюк, принятое решение не может четко объяснить это, и это отбросило меня на несколько часов, чтобы найти причину