Необов’язкові взаємозв’язки за допомогою API вільного використання Entity Framework


79

Ми хочемо використовувати один до одного необов’язкові відносини, використовуючи спочатку Entity Framework Code. У нас є дві сутності.

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повинен мати a 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 сутності сутності?

Відповіді:


101

EF Code First - підтримка 1:1та 1:0..1відносини. Останнє - це те, що ви шукаєте ("один до нуля або один").

Ваші спроби вільно говорити кажуть, що обов’язково на обох кінцях в одному випадку та необов’язково на обох кінцях в іншому.

Те, що вам потрібно, є необов’язковим для одного кінця, а обов’язковим для іншого.

Ось приклад із першої книги Програмування EF Code

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 );
}

Це означає, що PIIUserвластивість LoyaltyUserDetails є обов’язковою, а властивість PIIUser LoyaltyUserDetailнеобов’язковою.

Ви можете почати з іншого кінця:

modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);

що зараз говорить, що LoyaltyUserDetailвласність PIIUser є необов’язковою, а PIIUserвласність LoyaltyUser потрібна.

Завжди потрібно використовувати шаблон HAS / WITH.

Відношення HTH та FWIW, один до одного (або один до нуля / одного) - це одне з найбільш заплутаних відносин, яке потрібно налаштувати в коді спочатку, щоб ви не були самотніми! :)


1
Чи не обмежує це користувача для того, яке поле FK вибрати? (Я думаю, що він цього хоче, але він не повідомив про це, тому я не знаю), оскільки, схоже, він хоче мати поле ФК у класі.
Франс Бума,

1
Домовились. Це обмеження того, як працює EF. 1: 1 та 1: 0..1 залежать від первинних ключів. В іншому випадку, я думаю, ви заблукали в "унікальному ФК", який досі не підтримується в EF. :( (Ви більше експерта дб ... це правильно ... це дійсно про унікальний FK?) І не буде в майбутньому Ef6 згідно: entityframework.codeplex.com/workitem/299
Julie Lerman

1
Так @FransBouma, ми хотіли використовувати поля PIIUserId та LoyaltUserId як зовнішні ключі. Але EF обмежує нас у цій ситуації, як ви та Джулі згадували. Дякую за відповіді.
İlkay İlknur

@JulieLerman, чому ти використовуєш WithOptional? У чому відмінність із WithRequiredDependentі WithRequiredOptional?
Віллі

1
WithO optional вказує на необов’язкове відношення, наприклад 0..1 (нуль або одиниця), оскільки в операційній програмі зазначено, що "PIIUser може мати LoyaltyUserDetail". І ваша плутанина, яка призводить до другого питання, полягає в тому, що ви помилилися з одним із термінів. ;) Це WithRequiredDependent і WithRequiredPrincipal. Це має більше сенсу? Вказуючи на необхідний кінець, який є або утриманцем (він же "дочірній"), або основним, який називається "батьком". Навіть у відносинах один до одного, коли обидва рівні, EF повинен називати одного головним, а одного - залежним. HTH!
Джулі Лерман

27

Просто зробіть так, як якщо у вас є взаємозв'язок "один-до-багатьох", LoyaltyUserDetailі PIIUserтаким чином ви повинні бути

modelBuilder.Entity<LoyaltyUserDetail>()
       .HasRequired(m => m.PIIUser )
       .WithMany()
       .HasForeignKey(c => c.LoyaltyUserDetailId);

EF повинен створити весь зовнішній ключ, який вам потрібен, і просто не дбати про WithMany !


3
Відповідь Джулі Лерман була прийнята (і повинна залишатися прийнятою, ІМХО), оскільки вона відповідає на запитання та детально описує, чому це правильний спосіб, підтримуючи деталі та обговорення. Копіювати та вставляти відповіді, безумовно, можна по всьому SO, і я сам використовував декілька, але як професійний розробник вам слід більше турбуватися про те, щоб стати кращим програмістом, а не просто отримати код для побудови. Тим не менш, молодший все зрозумів, хоч через рік.
Mike Devenney

1
@ClickOk Я знаю, що це старе запитання та коментар, але це не правильна відповідь, оскільки він використовував один для багатьох як обхідний шлях, це не робить відношення один до одного або один до нуля
Махмуд Дарвіш,

3

З вашим кодом є кілька помилок.

Зв'язок 1: 1 є або: PK <-PK , де одна сторона PK також є FK, або PK <-FK + UC , де сторона FK не є PK і має UC. Ваш код показує, що у вас FK <-FK , оскільки ви визначаєте обидві сторони як FK, але це неправильно. Я розумію, що PIIUserце сторона ПК і LoyaltyUserDetailсторона ФК. Це означає, що PIIUserвін не має поля FK, але LoyaltyUserDetailмає.

Якщо співвідношення 1: 1 є необов’язковим, сторона FK повинна мати принаймні 1 поле, що допускає обнулення.

pswg вище відповів на ваше запитання, але помилився, що він також визначив FK у PIIUser, що, звичайно, є неправильним, як я описав вище. Отже, визначте поле FK, що допускає нуль LoyaltyUserDetail, визначте атрибут, LoyaltyUserDetailщоб позначити його поле FK, але не вказуйте поле FK у PIIUser.

Ви отримаєте виняток, який ви описали вище під публікацією pswg, оскільки жодна сторона не є стороною ПК (основний кінець).

EF не дуже хороший у співвідношенні 1: 1, оскільки він не здатний обробляти унікальні обмеження. Я не фахівець з кодексу спочатку, тому я не знаю, чи може він створити UC чи ні.

(редагувати) btw: A 1: 1 B (FK) означає, що створено лише 1 обмеження FK на цілі B, що вказує на P A, а не 2.


3
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);

це вирішить проблему з ДОВІДКАМИ та ІНОЗЕМНИМИ КЛЮЧАМИ

при ОНОВЛЕННІ або ВИДАЛЕННІ запису


Це не спрацює, як очікувалося. Це призводить до коду міграції, який не використовує LoyaltyUserId як зовнішній ключ, тому User.Id та LoyaltyUser.Id в кінцевому підсумку мають однакове значення, а LoyaltyUserId залишатиметься порожнім.
Аарон Квін

2

Спробуйте додати 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; }
    ...
}

1
Ми спробували анотації даних перед усіма цими вільними підходами API. Але анотації даних не працювали. якщо ви додаєте анотації даних, про які ви згадали вище, EF видає цей виняток => Неможливо визначити основний кінець асоціації між типами 'LoyaltyUserDetail' та 'PIIUser'. Основний кінець цієї асоціації повинен бути явно налаштований, використовуючи або API вільного зв'язку, або анотації даних.
İlkay İlknur

@ İlkayİlknur Що станеться, якщо ви додасте ForeignKeyатрибут лише до одного кінця стосунків? тобто лише на PIIUser або LoyaltyUserDetail .
pswg

Ef кидає той самий виняток.
İlkay İlknur

1
@pswg Чому FK у PIIUser? LoyaltyUserDetail має FK, а не PIIUser. Отже, це має бути [Key, ForeignKey ("PIIUser")] у властивості PIIUserId LoyaltyUserDetail. Спробуйте це
user808128 02

@ user808128 Ви можете розмістити анотацію на властивості навігації або на ідентифікаторі, без різниці.
Thomas Boby

1

Одне, що бентежить з наведеними вище рішеннями, полягає в тому, що первинний ключ визначається як " 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);

Зробив би трюк, прийняте рішення не може чітко пояснити це, і це відкинуло мене на кілька годин, щоб знайти причину


1

Це не приносить користі оригінальному плакату, але для тих, хто все ще перебуває на 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атрибута, і жоден з них не працював).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.