我该如何 .Incluce() 只是核心中 ICollection 的最后一个元素 asp.net?

How do I .Incluce() only the last element of an ICollection in asp.net core?

提问人:noob-cryptor 提问时间:6/11/2023 更新时间:6/12/2023 访问量:27

问:

我有一个这样的数据库模型

    public partial class ChatThread : BaseEntity
    {
        [ForeignKey("ChatThreadID")]
        public ICollection<ChatMessage> ChatMessages { get; set; }
    }

并且我只想返回添加到ChatMessages关系的最新元素

我已经在我的get请求中尝试了以下方法,但没有用

        public async Task<ActionResult<IEnumerable<ChatThread>>> GetThreads()
        {
            var threads = _context.ChatThreads.Where(thread => thread.User1 == UserId || thread.User2 == UserId).Include(t => t.ChatMessages.Last());
            return await threads.ToListAsync();
        }

有没有办法使用诸如仅返回一个项目而不是该线程中的每条聊天消息之类的东西?.Include(t => t.ChatMessages.Last());

asp.net 实体框架 asp.net-core asp.net-web-api

评论


答:

0赞 Steve Py 6/12/2023 #1

投影适用于查看关注点。您可以为聊天线程和聊天消息创建视图模型,既可以拼合详细信息,也可以作为单独的模型。

例如,使用展平视图模型:

[Serializable]
public class ChatThreadViewModel
{
    public int ChatThreadId { get; set; }
    // Other chat thread fields needed for view...

    public int LatestChatMessageId { get; set; }
    public string LatestChatMessage { get; set; }
    // any other chat message fields needed for view...
}

然后在阅读时...

public async Task<ActionResult<IEnumerable<ChatThreadViewModel>>> GetThreads()
{
    var threadDetails = await _context.ChatThreads
        .Where(thread => thread.User1 == UserId 
            || thread.User2 == UserId)
        .Select(x => new 
        {
            x.ChatThreadId,
            LastChatMessage = x.ChatMessages
               .OrderByDescending(cm => cm.ChatMessageDateTime)
               .Select(cm => new 
               {
                   cm.ChatMessageId,
                   cm.Message
               }).First()
        }).ToListAsync();

    return await threadDetails
        .Select(x => new ChatThreadViewModel
        {
            ChatThreadId = x.ChatThreadId,
            LastChatMessageId = x.LastChatMessage.ChatMessageId,
            LastChatMessage = x.LastChatMessage.Message
        }).ToListAsync();
}

为了展平细节,我们做了一个双投影,其中第一个查询只拉取我们想要的关系细节,然后第二个查询进行展平。这简化了生成的 SQL,而不是执行以下操作:

        .Select(x => new ChatThreadViewModel
        {
            ChatThreadId = x.ChatThreadId,
            LastChatMessageId = x.ChatMessages
               .OrderByDescending(cm => cm.ChatMessageDateTime)
               .Select(cm => cm.ChatMessageId)
               .First(),
            LastChatMessage = x.ChatMessages
               .OrderByDescending(cm => cm.ChatMessageDateTime)
               .Select(cm => cm.Message)
               .First()
        }).ToListAsync();

这将为我们想要拉回的每个字段创建一个 on ChatMessages。JOIN

或者,如果需要包含“LatestMessage”的 ChatMessageViewModel 的 ChatThreadViewModel,则可以在单个投影中填充这些模型。此选项最适合 Automapper。

另一种选择是在查询中填充一个分离的 ChatThread 实体,在该实体中,我们只为最新消息加载单个 ChatMessage。像这样:

public async Task<ActionResult<IEnumerable<ChatThread>>> GetThreads()
{
    var threads = await _context.ChatThreads
        .Where(thread => thread.User1 == UserId 
            || thread.User2 == UserId)
        .Select(x => new ChatThread
        {
            ChatThreadId = x.ChatThreadId,
            // Populate all other fields in the chat thread...

            ChatMessages = x.ChatMessages
               .OrderByDescending(cm => cm.ChatMessageDateTime)
               .Select(cm => new ChatMessage
               {
                   ChatMessageId = cm.ChatMessageId,
                   Message = cm.Message,
                   // Populate all other fields from the chat message...
               }).Take(1)
               .ToList()
        }).ToListAsync();

    return threads;
}

使用此类方法的注意事项是,实体不再表示数据状态的完整表示形式。在创建接受 ChatThread 实体等内容的方法时,这很容易混淆,因为这样创建的东西既不是原始 ChatThread 的完整表示形式,也不是“可完全”表示的。(即一个可延迟加载的实例,如果需要,可以加载 ChatMessages 或其他相关实体)它不能安全地附加并用作整个 ChatThread 的表示形式,因此存在一种持续的风险,即代码可能会被这些不完整的数据表示形式之一伪装成实体。