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

但是,我正在尝试向名为“LastMessage”的“Conversation”类添加导航属性,该类将跟踪创建的最后一条消息记录:

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

当我尝试应用上述内容时,出现错误

system.invalidOperationException:子/依赖方无法 确定之间的一对一关系 “Conversation.LastMessage”和“Message.Conversation”。

如何维护“对话”和“消息”之间的一对多关系,但还要在“对话”类中添加导航属性以导航到单个“消息”记录?

解决方法

如果对话可以有多个消息,则称为一对多关系。 您必须修复表格:


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

在尝试了各种数据注释和 Fluent API 废话之后,我能想到的最简洁的解决方案非常简单,两者都不需要。它只需要向 Conversation 类添加一个“私有”构造函数(如果您使用的是延迟加载,则为“受保护”构造函数),您的“DbContext”对象被注入到该类中。只需将您的“对话”和“消息”类设置为正常的一对多关系,并且现在可以从“对话”实体中获得数据库上下文,您可以让“最后一条消息”简单地从数据库返回一个查询使用 Find() 方法。 Find() 方法也使用了缓存,因此如果多次调用 getter,它只会访问一次数据库。

这是关于此能力的文档: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; }
}