Ядро Entity Framework — один ко многим, но родительский элемент также имеет свойство навигации для одного дочернего элемента?

#asp.net-core #entity-framework-core #foreign-keys

#asp.net-core #entity-framework-core #внешние ключи

Вопрос:

В настоящее время у меня есть рабочее отношение «один ко многим» между сущностями «Диалог» и «Сообщение», где в разговоре может быть несколько сообщений.

Это работает нормально:

 public class Conversation
{
    public long ID { get; set; }
}

public class Message : IEntity
{
    public virtual Conversation Conversation { get; set; }
    public long ConversationID { get; set; }
    public long ID { get; set; }
}
 

Тем не менее, я пытаюсь добавить свойство навигации к классу ‘Conversation’ с именем ‘LastMessage’, которое будет отслеживать последнюю созданную запись сообщения:

 public class Conversation
{
    public long ID { get; set; }
    public virtual Message LastMessage { get; set; }
    public long LastMessageID { get; set; }
}
 

Когда я пытаюсь применить вышеуказанное, я получаю сообщение об ошибке

Система.Исключение InvalidOperationException: дочерняя / зависимая сторона не может быть определена для отношения «один к одному» между диалогом.Последнее сообщение ‘ и ‘Сообщение.Беседа ‘.

Как мне поддерживать связь «один ко многим» между «Разговором» и «Сообщением», а ТАКЖЕ добавлять свойство навигации в классе «Conversation», которое переходит к одной записи «Message»?

Ответ №1:

Если в диалоге может быть несколько сообщений, это называется отношениями «один ко многим». Вы должны исправить таблицы:

 
public class Conversation
{
    [Key]
    public long ID { get; set; }
    [InverseProperty(nameof(Message.Conversation))]
    public virtual ICollection<Message> Messages { get; set; }
    
}

public class Message 
{
    [Key]
    public long ID { get; set; }

    public long ConversationID { get; set; }
 
    [ForeignKey(nameof(ConversionId))]
    [InverseProperty("Messages")]
    public virtual Conversation Conversation { get; set; }
      
 }
 

Ответ №2:

После того, как я попробовал всевозможные аннотации к данным и беглый API, самое чистое решение, которое я мог придумать, оказалось очень простым, которое не требует ни того, ни другого. Для этого требуется только добавить «частный» конструктор в класс Conversation (или «защищенный», если вы используете отложенную загрузку), в который вводится ваш объект DbContext. Просто настройте свои классы ‘Conversation’ и ‘Message’ как обычное отношение «один ко многим», и теперь, когда контекст вашей базы данных доступен из объекта ‘Conversation’, вы можете заставить ‘LastMessage’ просто возвращать запрос из базы данных с помощью метода Find (). Метод Find() также использует кэширование, поэтому, если вы вызываете средство получения более одного раза, оно совершит только один переход к базе данных.

Вот документация по этой возможности: https://docs.microsoft.com/en-us/ef/core/modeling/constructors#injecting-services

Примечание: свойство ‘LastMessage’ доступно только для чтения. Чтобы изменить его, установите свойство ‘LastMessageID’.

 class Conversation
{
    public Conversation() { }
    private MyDbContext Context { get; set; }
    // make the following constructor 'protected' if you're using Lazy Loading
    // if not, make it 'private'
    protected Conversation(MyDbContext Context) { this.Context = Context; }

    public int ID { get; set; }
    public int LastMessageID { get; set; }
    public Message LastMessage { get { return Context.Messages.Find(LastMessageID); } }
}

class Message
{
    public int ID { get; set; }
    public int ConversationID { get; set; }
    public virtual Conversation Conversation { get; set; }
}