Как определить дополнительные отношения внешнего ключа в аннотациях FluentAPI / Data с помощью Entity Framework? - PullRequest
20 голосов
/ 05 ноября 2011

У меня есть (пример) приложения со следующим кодом:

public class Posts
{

    [Key]
    [Required]
    public int ID { get; set; }

    [Required]
    public string TypeOfPost { get; set; }

    public int PollID { get; set; }
    public virtual Poll Poll { get; set; }

    public int PostID { get; set; }
    public virtual Post Post { get; set; }

}

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

. Я не думаю, что в любом случае есть настройка «Только один обязательный» или аналогичный, поэтому я выполняю все проверки и прочее в приложении.(Если кто-нибудь знает лучший способ, скажите, пожалуйста!).

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

Я пробовал оба из следующих действий:

modelBuilder.Entity<Post>()
    .HasOptional(x => x.Poll).WithOptionalDependent();

modelBuilder.Entity<Post>()
    .HasOptional(x => x.Poll).WithOptionalPrincipal();

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

Я считаю, что первый - тот, который мне нужен, но мне нужно использовать его в сочетании с [ForeignKey]в классе Post.Если я здесь прав, должен ли [ForeignKey] указывать виртуальное свойство или идентификатор свойства?

Кроме того, какова фактическая разница между WithOptionalDependent и WithOptionalPrincipal?- Я читал на MSDN, но я действительно не понимаю разницу.

Ответы [ 4 ]

18 голосов
/ 07 ноября 2011

Причина, по которой он не допускает нулевые значения, потому что следующее:

public int PollID { get; set; }
public virtual Poll Poll { get; set; }

public int PostID { get; set; }
public virtual Post Post { get; set; }

должно было быть

public int? PollID { get; set; }
public virtual Poll Poll { get; set; }

public int? PostID { get; set; }
public virtual Post Post { get; set; }
12 голосов
/ 05 ноября 2011

Я бы, вероятно, попытался бы создать два отношения один-к-одному как необязательно: требуется , поскольку Poll должно иметь ссылку на Posts и Post также должен иметь ссылку на Posts:

modelBuilder.Entity<Posts>()
    .HasOptional(x => x.Post)
    .WithRequired();

modelBuilder.Entity<Posts>()
    .HasOptional(x => x.Poll)
    .WithRequired();

Это делает Posts автоматически основным в отношении и Post или Poll зависимым.Принципал имеет первичный ключ в отношении, зависимый внешний ключ, который также является первичным ключом в то же время в таблице Post / Poll, потому что это отношение один к одному.Только в отношении «один ко многим» у вас будет отдельный столбец для внешнего ключа.Для отношения «один к одному» вы также должны удалить столбцы внешнего ключа PostId и PollId, поскольку Posts через свой первичный ключ ссылается на Post и Poll.

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

public abstract class BasePost  // your former Posts class
{
    public int ID { get; set; }
    public string UserName { get; set; }
}

public class Post : BasePost
{
    public string Text { get; set; }
    // other properties of the Post class
}

public class Poll : BasePost
{
    // properties of the Poll class
}

Вам больше не понадобится TypeOfPost, потому что вы можете фильтровать два конкретных типа, используя оператор OfType LINQ, например:

var x = context.BasePosts.OfType<Post>()
    .Where(p => p.UserName == "Jim")
    .ToList();

При этом будут выбраны все сообщения определенного пользователя, но не опросы.

Затем вам нужно решить, какой тип наследования вы хотите использовать - TPH, TPT или TPC .

Редактировать

Чтобы получить отношение «один ко многим», вы можете указать следующее отображение в Fluent API:

modelBuilder.Entity<Posts>()
    .HasOptional(x => x.Post)
    .WithMany()
    .HasForeignKey(x => x.PostID);

modelBuilder.Entity<Posts>()
    .HasOptional(x => x.Poll)
    .WithMany()
    .HasForeignKey(x => x.PollID);

Свойства внешнего ключа должны быть обнуляемыми (int?) для этого, как вы уже нашли.Поскольку именование ваших свойств внешнего ключа соответствует соглашению об именах, которое EF использует для отображения, вы можете полностью исключить отображение Fluent.Это потребуется только в том случае, если у вас нетрадиционные имена (например, PostFK или что-то в этом роде).Затем вы можете также использовать аннотации данных (атрибут [ForeignKey(...)]) вместо Fluent API.

8 голосов
/ 26 августа 2014

ForeignKey просто должен иметь значение Nullable, чтобы сделать его необязательным - виртуальный является отдельным, и требуется только для отложенной загрузки.

Обязательные отношения в декларативном коде EF First:

public User User { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }

Необязательные отношения в декларативном коде EF First:

public User User { get; set; }
[ForeignKey("User")]
public int? UserId { get; set; }

Вы увидите это, когда запустите update-database -verbose -f:

ALTER TABLE [dbo].[MyTable] ALTER COLUMN [UserId] [int] NULL
0 голосов
/ 16 ноября 2016

Что-то еще, что может помочь. Установка атрибутов внешнего ключа (аннотаций) с атрибутом [Обязательный] также обеспечит обязательные для EF свойства навигации ДАЖЕ, КОГДА свойство FK имеет значение NULL. У меня есть особый случай с унаследованными данными, когда свойство FK требуется, но может ссылаться или не ссылаться на запись в отношениях. Имеет смысл, но я не думал, что EF был таким «умным».

    [Required] <-- even if the FK is nullable, OrgUnit will be Required
    [StringLength(10)]
    [ForeignKey("OrgUnit"), Column(Order = 1)]
    public string OrgCode
    {
        get;
        set;
    }

    ... other FK, Column order 0   

    public virtual OrgUnit OrgUnit
    {
        get;
        set;
    }
...