ASP.Net Core Cannot Handle Navigation Properties and Replacement Causes Runtime Error

PostAlmostAnything 1 Reputation point
2022-03-15T22:27:30.543+00:00

For some reason ASP.Net Core cannot properly process queries when there is more than one relationship between multiple user id numbers in one table and others. I found this out after adding a messaging system to my site which has two user id fields in the messages table. One is for sender and the other for recipient. Both use the Id from ASP.Net Core Identity. This works fine unless I need to get information about both senders and recipients at which point it seems to get confused as to what is going on and take forever.

To get around this I thought I'd try writing functions that simply get user data based on Id and call those instead of navigation properties. The end result is a runtime error that says:

"InvalidOperationException: The client projection contains a reference to a constant expression of 'MySite.SiteServices.MessageService' through the instance method 'GetMsgUser'. This could potentially cause a memory leak; consider making the method static so that it does not capture constant in the instance. See https://go.microsoft.com/fwlink/?linkid=2103067 for more information."

I was surprised not to get this error from the navigation properties back when I was using them because "memory leak" sounds like a possible result of them also. The navigation properties rather than simply getting the data for a specific user seems to be getting much more, so I thought maybe something simpler using multiple tasks might work, but it seems to be worse.

My task is as follow"

public async Task<List<Messages>> GetMessagesbyUseridPaginated(string userid, int currentPage, int pageSize)
 {
 return await (from message in _context.Messages
  where message.Site == (int?)GlobalStatic.SITENUMBER() && message.Recipient == userid && (message.Category == 1 || message.Category == 2)
  select message into msg
  select new Messages
  {
  Messageid = msg.Messageid,
  Subject = msg.Subject,
  Message = msg.Message,
  Recipient = msg.Recipient,
  Sender = msg.Sender,
  Date = msg.Date,
  Newmessage = msg.Newmessage,
  Site = msg.Site,
  Category = msg.Category,
  Parentid = msg.Parentid,
  Msgcol = MsgCol(msg.Newmessage),
  Msgopened = MsgOpened(msg.Newmessage),
  Msghref = MsgHref(msg.Parentid, msg.Messageid),
  //SenderNavigation = new ApplicationUser
  //{
 // UserName = msg.SenderNavigation.UserName,
 // Images = msg.SenderNavigation.Images,
 // Id = msg.SenderNavigation.Id,
 // Email = msg.SenderNavigation.Email,
 // MsgNotifications = msg.SenderNavigation.MsgNotifications,
 // Notifications = msg.SenderNavigation.Notifications
  //},
  //RecipientNavigation = new ApplicationUser
  //{
 // UserName = msg.RecipientNavigation.UserName,
 // Images = msg.RecipientNavigation.Images,
 // Id = msg.RecipientNavigation.Id,
 // Email = msg.RecipientNavigation.Email,
 // MsgNotifications = msg.RecipientNavigation.MsgNotifications,
 // Notifications = msg.RecipientNavigation.Notifications
  //},
  Getsender = (ApplicationUser)(ICollection<ApplicationUser>)GetMsgUser($"{msg.Sender}"),
  Getrecipient = (ApplicationUser)(ICollection<ApplicationUser>)GetMsgUser($"{msg.Recipient}"),
  CategoryNavigation = new Messagecategories
  {
  Categoryid = msg.CategoryNavigation.Categoryid,
  Categoryname = msg.CategoryNavigation.Categoryname
  },
  }).Skip((currentPage - 1) * pageSize).Take(pageSize).ToListAsync();
 }

Then I added this to get user data:

public async Task<ApplicationUser> GetMsgUser(string userid)
 {
 ApplicationUser oappUser = await _userManager.FindByIdAsync(userid);
 ApplicationUser appUser = new ApplicationUser();
 appUser.Id = oappUser.Id;
 appUser.UserName = oappUser.UserName;
 appUser.Email = oappUser.Email;
 appUser.MsgNotifications = oappUser.MsgNotifications;
 appUser.Notifications = oappUser.Notifications;
 appUser.Images = (ICollection<Images>)GetMsgUserAvatar($"{oappUser.Id}");
 return appUser;
 }
 public async Task<Images> GetMsgUserAvatar(string userid)
 {
 return await _context.Images.Where((Images ig) => ig.Userid == userid && ig.Imagetype == 5).OrderByDescending(i => i.Imageid).LastOrDefaultAsync();
 }

A couple questions I have are:

1) Could I tell .Net to simply ignore the potential memory leak and run the page anyway?
2) Does Microsoft ever explain anywhere why you can't have more than one user Id in a table without it breaking how navigation works?
3) Notice how all the navigation properties are commented out above? That is due to it taking 3-5 minutes for users to check messages.

UPDATE:

The original setup using navigation resulted in this error in the input. The error is:

Microsoft.EntityFrameworkCore.Query: Warning: Compiling a query which loads related collections for more than one collection navigation either via 'Include' or through projection but no 'QuerySplittingBehavior' has been configured. By default Entity Framework will use 'QuerySplittingBehavior.SingleQuery' which can potentially result in slow query performance. See https://go.microsoft.com/fwlink/?linkid=2134277

UPDATE:

I got rid of the warning by adding .AsSplitQuery() to the end of messages query right before .ToListAsync(); unfortunately the performance is still slow.

The current query looks like this:

public async Task<List<Messages>> GetMessagesbyUseridPaginated(string userid, int currentPage, int pageSize)
        {
            return await (from message in _context.Messages
             where message.Site == (int?)GlobalStatic.SITENUMBER() && message.Recipient == userid && (message.Category == 1 || message.Category == 2)
             select message into msg
             select new Messages
             {
                 Messageid = msg.Messageid,
                 Subject = msg.Subject,
                 Message = msg.Message,
                 Recipient = msg.Recipient,
                 Sender = msg.Sender,
                 Date = msg.Date,
                 Newmessage = msg.Newmessage,
                 Site = msg.Site,
                 Category = msg.Category,
                 Parentid = msg.Parentid,
                 Msgcol = MsgCol(msg.Newmessage),
                 Msgopened = MsgOpened(msg.Newmessage),
                 Msghref = MsgHref(msg.Parentid, msg.Messageid),
                 SenderNavigation = new ApplicationUser
                 {
                     UserName = msg.SenderNavigation.UserName,
                     Images = msg.SenderNavigation.Images,
                     Id = msg.SenderNavigation.Id,
                     Email = msg.SenderNavigation.Email,
                     MsgNotifications = msg.SenderNavigation.MsgNotifications,
                     Notifications = msg.SenderNavigation.Notifications
                 },
                 RecipientNavigation = new ApplicationUser
                 {
                     UserName = msg.RecipientNavigation.UserName,
                     Images = msg.RecipientNavigation.Images,
                     Id = msg.RecipientNavigation.Id,
                     Email = msg.RecipientNavigation.Email,
                     MsgNotifications = msg.RecipientNavigation.MsgNotifications,
                     Notifications = msg.RecipientNavigation.Notifications
                 },
                 //Getsender = (ApplicationUser)(ICollection<ApplicationUser>)GetMsgUser($"{msg.Sender}"),
                 //Getrecipient = (ApplicationUser)(ICollection<ApplicationUser>)GetMsgUser($"{msg.Recipient}"),
                 CategoryNavigation = new Messagecategories
                 {
                     Categoryid = msg.CategoryNavigation.Categoryid,
                     Categoryname = msg.CategoryNavigation.Categoryname
                 },
             }).Skip((currentPage - 1) * pageSize).Take(pageSize).AsSplitQuery().ToListAsync();
        }
Entity Framework Core
Entity Framework Core
A lightweight, extensible, open-source, and cross-platform version of the Entity Framework data access technology.
697 questions
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,154 questions
{count} votes