load navigation properties with filter for Entity

2019-03-03 21:58发布

问题:

Few days back I put a question regarding mapping two classes Message and MessageStatusHistory using EF. The mapping is going fine but I am facing some problems with the navigation property StatusHistory in class Message that relates it to MessageStatusHistory objects. I am loading the messages for one user only and want to the statuses pertaining to that user only. Like I would want to show if the user has marked message as read/not-read and when. If I use default loading mechanism like following it loads all the history related to the message irrespective of the user:

IDbSet<Message> dbs = _repo.DbSet;
dbs.Include("StatusHistory").Where(x=>x.MessageIdentifier == msgIdentifier);

To filter history for one user only I tried following trick:

IDbSet<Message> dbs = _repo.DbSet;
var q = from m in dbs.Include("StatusHistory")
        where m.MessageIdentifier == msgIdentifier
        select new Message
        {
            MessageIdentifier = m.MessageIdentifier,
            /*OTHER PROPERTIES*/
            StatusHistory = m.StatusHistory
                             .Where(x => x.UserId == userId).ToList()
        };

return q.ToList();//THROWING ERROR ON THIS LINE

I am getting the error:

The entity or complex type 'MyLib.Biz.Message' cannot be constructed in a LINQ 
to Entities query.

I have tried by commenting StatusHistory = m.StatusHistory.Where(x => x.UserId == userId).ToList() also but it has not helped.

Please help me in getting Messages with filtered StatusHistory.

EDIT:- above is resolved with this code:

var q = from m in _repository.DBSet.Include("Histories")
        where m.MessageIdentifier == id
        select new {
                     m.Id,/*OTHER PROPERTIES*/
                     Histories = m.Histories.Where(x => 
                                   x.SenderId == userId).ToList()
                   };

var lst = q.ToList();
return lst.Select(m => new Message{
           Id = m.Id, MessageIdentifier = m.MessageIdentifier, 
           MessageText = m.MessageText, Replies = m.Replies, 
           ReplyTo = m.ReplyTo, Histories = m.Histories, SenderId = 
           m.SenderId, SenderName = m.SenderName, CreatedOn = m.CreatedOn
       }).ToList();

But if I try to include replies to the message with:

from m in _repository.DBSet.Include("Replies").Include("Histories")

I am getting error on converting query to List with q.ToList() for Histories = m.Histories.Where(x=> x.SenderId == userId).ToList().

回答1:

About your EDIT part: You cannot use ToList() in a projection, just leave it an IEnumerable<T> and convert to a List<T> when you construct the Message. You also don't need to create two list objects, you can switch from the LINQ to Entities query to LINQ to Objects (the second Select) by using AsEnumerable():

var list = (from m in _repository.DBSet
            where m.MessageIdentifier == id
            select new {
                // ...
                Histories = m.Histories.Where(x => x.SenderId == userId)
            })
            .AsEnumerable() // database query is executed here
            .Select(m => new Message {
                // ...
                Histories = m.Histories.ToList(),
                // ...
            }).ToList();

return list;

Be aware that Include has no effect when you use a projection with select. You need to make the properties that you want to include part of the projection - as you already did with select new { Histories.....