Необязательное отношение один к одному с использованием 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);

Сделал бы трюк, принятое решение не может четко объяснить это, и это отбросило меня на несколько часов, чтобы найти причину

Другие вопросы по тегам