Group and Select First From Two Tables Linq

前端 未结 2 1616
庸人自扰
庸人自扰 2021-01-23 16:20

I\'m trying to create a simple query using EFCore, returning the list of people i\'m conversing with, and the last message that was sent between the two of us (pretty much like

2条回答
  •  南方客
    南方客 (楼主)
    2021-01-23 16:50

    You should be able to get your visitor information via association without explicitly trying to join/group on it.

    Apologies for switching to Fluent syntax, I really dislike the Linq QL, it always seems so forced to do anything through it... :)

    Your original query:

    from c in ChatMessages
    orderby c.CreatedAt descending 
    group c by c.VisitorId  into x
    select x.First()
    

    or

    var groupedMessages = context.ChatMessages
        .OrderByDescending(c => c.CreatedAt)
        .GroupBy(c => c.VisitorId)
        .First();
    

    To group by Visitor:

    var groupedMessages = context.ChatMessages
        .OrderByDescending(c => c.CreatedAt)
        .GroupBy(c => c.Visitor)
        .First();
    

    This will give you a Key as a Visitor entity, with the messages for that visitor. However, this somewhat begs the question, why?

    var visitorsWithMessages = context.Visitors.Include(v => v.VisitorChatMessages);
    

    This loads the visitors and eager-loads their associated chat messages. Entities satisfy queries against the data relationships. For consumption though we care about details like ensuring the chat messages are ordered, or possibly filtered.

    To project that into a suitable structure I'd use view models for the visitor and chat message to optimize the query to cover just the details I care about, and present them in the way I care about:

    var visitorsWithMessages = context.Visitors
       // insert .Where() clause here to filter which Visitors we care to retrieve...
       .Select(v => new VisitorViewModel
       {
          Id = v.Id,
          Name = v.Name,
          RecentChatMessages = v.VisitorChatMessages
             .OrderByDescending(c => c.CreatedAt)
             .Select(c => new ChatMessageViewModel
             {
                Id = c.Id,
                Message = c.Message,
                CreatedAt = c.CreatedAt,
                CreatedBy = c.User.UserName ?? "Anonymous"
             }).Take(10).ToList()
        }).ToList();
    

    This uses projection and EF's mapped relationships to get a list of Visitors and up to 10 of their most recent chat messages. I populate simple POCO view model classes which contain the fields I care about. These view models can be safely returned from methods or serialized to a view/API consumer without risking tripping up lazy loading. If I just need the data and don't need to send it anywhere, I can use anonymous types to get the fields I care about. We don't need to explicitly join entities together, you only need to do that for entities that deliberately do not have FK relationships mapped. We also do not need to deliberately eager-load entities either. The Select will compose an optimized SQL statement for the columns we need.

    You can consume those results in the view and render based on Visitor + Messages or even dive deeper if you want to display a list of each visitors 10 most recent messages /w visitor details as a flattened list of messages:

    Edit: The below query may have had an issue by accessing "v." after the .SelectMany. Corrected to "c.Visitor."

    var recentMessages = context.Visitors
       .SelectMany(v => v.VisitorChatMessages
          .OrderByDescending(c => c.CreatedAt)
          .Select(c => new VisitorChatMessageViewModel
          {
              Id = c.Id,
              VisitorId = c.Visitor.Id,
              Message = c.Message,
              CreatedAt = c.CreatedAt,
              CreatedBy = c.User.UserName ?? "Anonymous",
              Visitor = c.Visitor.Name
          }).Take(10)
        }).ToList();
    

    No idea how to do that in Linq QL though. :)

    Edit: That last example will give you the last 10 messages with their applicable Visitor detail. (Name)

    To get the last 100 messages for example and group them by their visitor:

    var recentMessages = context.Visitors
       .SelectMany(v => v.VisitorChatMessages
          .OrderByDescending(c => c.CreatedAt)
          .Select(c => new VisitorChatMessageViewModel
          {
              Id = c.Id,
              VisitorId = c.Visitor.Id,
              Message = c.Message,
              CreatedAt = c.CreatedAt,
              CreatedBy = c.User.UserName ?? "Anonymous",
              Visitor = c.Visitor.Name // Visitor Name, or could be a ViewModel for more info about Visitor...
          }).Take(100)
        }).GroupBy(x => x.Visitor).ToList();
    

    If you select a VisitorViewModel for Visitor instead of ".Visitor.Name" then your grouping key will have access to Name, Id, etc. whatever you select from the associated Visitor.

提交回复
热议问题