简体   繁体   中英

Proper Implementation of IQueryable as a Member Variable on a Model?

I'm new to the world of .Net, ASP, Entity Framework, and Linq, so bear with me...

I originally had a model set up like the following;

public class Pad
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid PadId { get; set; }
    public string StreetAddress { get; set; }
    public int ZipCode { get; set; }
    public virtual ICollection<Mate> Mates { get; set; }
    public virtual ICollection<Message> Messages { get; set; }
}

(A pad is a chat room - it contains many Mates and many thousands of Messages) In a Web API controller, I have a function designed to get the 25 most recent messages from the specified Pad.

public IHttpActionResult GetMessages(string id)
{
    var padGuid = new Guid(id);

    // Try to find the pad referenced by the passed ID
    var pads = (from p in db.Pads
                where p.PadId == padGuid
                select p);
    if (pads.Count() <= 0)
    {
        return NotFound();
    }
    var pad = pads.First();

    // Grab the last 25 messages in this pad. 
    // PERFORMANCE PROBLEM
    var messages = pad.Messages.OrderBy(c => c.SendTime).Skip(Math.Max(0, pad.Messages.Count() - 25));
    var messagesmodel = from m in messages
            select m.toViewModel();
    return Ok(messagesmodel);
}

The problem with this implementation is that it seems as though EF is loading the entire set of messages (multiple thousands) into memory before getting the count, ordering, etc. Resulting in a massive performance penalty in Pads with a ton of messages.

My first thought was to convert the Pad.Messages member type to an IQueryable instead of an ICollection - this should defer the Linq queries to SQL, or so I thought. Upon doing this, however, functions like pad.Messages.Count() above complain - turns out that pad.Messages is a null value! And it breaks in other places, such as adding new Messages to the Pad.Messages value.

What is the proper implementation of something like this? In other places, I've seen the recommended solution is constructing a second query against the context such as select Messages where PadId = n , but this hardly seems intuitive when I can have a Messages member value to work with.

Thank you!

var messages = db.Pads.Where(p => p.PadId == padGuid)
    .SelectMany(pad => p.Messages.OrderBy(c => c.SendTime)
        .Skip(Math.Max(0, pad.Messages.Count() - 25)));

How do you plan to count the number of results in a DB query without actually executing the DB query?

How do you plan to get the first item in the query without actually executing the query?

You cannot do either; both must execute the query.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM