0

Here is a simplified version of my model:

public class User  {
    public int UserID { get; set; }
    public string FirstName { get; set; }
    public virtual ICollection<Recipe> Recipes { get; set; }
}

public class Recipe {
    public int RecipeID { get; set; }
    public string RecipeName { get; set; }
    public int UserID { get; set; }
    public virtual User User { get; set; }
}

I have a controller that I'd like to return a User as well as some summary information about their recipes. The scaffolded controller code looks like this:

var user = await _context.Users.SingleOrDefaultAsync(m => m.UserID == id);

It works fine. Now I try to add the Recipes, and it breaks:

var user = await _context.Users.Include(u => u.Recipes).SingleOrDefaultAsync(m => m.UserID == id);

My web browser starts to render the JSON, and it flickers and I get a message in the browser saying the connection has been reset.

My Theory - I believe that the parent (User) renders, which exposes the child (Recipe) which contains a reference to the parent (User), which contains a collection of the child (Recipe) and so on which is causing an infinite loop. Here's why I think this is happening:

  1. The Visual Studio debugger allows me to navigate the properties in that way infinitely.
  2. If I comment out the Recipe.User property, it works fine.

What I've tried I tried to just include the data from Recipe that I need using Entity Framework projection (I'm attempting to not include Recipe.User). I tried to only include Recipe.RecipeName... but when I try to use projection to create an anonymous type like this:

var user = await _context.Users.Include(u => u.Recipes.Select(r => new { r.RecipeName })).SingleOrDefaultAsync(m => m.UserID == id);

I receive this error:

InvalidOperationException: The property expression 'u => {from Recipe r in u.Recipes select new <>f__AnonymousType1`1(RecipeName = [r].RecipeName)}' is not valid. The expression should represent a property access: 't => t.MyProperty'.

What is the solution? Can I project with different syntax? Am I going about this all wrong?

ChadSC
  • 340
  • 5
  • 13

2 Answers2

0

Consider using POCOs for serialization rather than doubly-linked entity classes:

public class UserPOCO  {
    public int UserID { get; set; }
    public string FirstName { get; set; }
    public ICollection<RecipePOCO> Recipes { get; set; }
}

public class RecipePOCO {
    public int RecipeID { get; set; }
    public string RecipeName { get; set; }
    public int UserID { get; set; }
}

Copy the entity contents to the corresponding POCO and then return those POCO objects as the JSON result. The removal of the User property via usage of the RecipePOCO class will remove the circular reference.

Matthew D
  • 196
  • 6
  • Matthew - I think you're right, and I think this is the option I'm going to choose, but... If I have an instance of the Recipe class, and only the UserID, how would I navigate to the User via code? I haven't figured that out yet... – ChadSC Mar 28 '18 at 17:20
  • Ideally you would use the entity for navigation and any processing. The copying of data to a POCO would be the absolute last step before sending data to the client. You will end up with what resembles a duplication of classes but the POCO models serve a different purpose than the entity classes. As far as the client goes, I'm not aware of any trivial way to restore the link once the data has been serialized to JSON and sent. – Matthew D Mar 28 '18 at 17:25
0

I can propose you 3 options.

  1. U sing [JsonIgnore] on property, but it will work on every use of Recipe class, so when you would like to just return Recipe class you won't have User in it.

    public class Recipe {
        public int RecipeID { get; set; }
        public string RecipeName { get; set; }
        public int UserID { get; set; }
        [JsonIgnore]
        public virtual User User { get; set; }
    }
    
  2. You can this solution to stop reference loop in all jsons https://stackoverflow.com/a/42522643/3355459
  3. Last option is to create class (ViewModel) that will only have properties that you want send to the browser, and map your result to it. It is propably best from security reason.
JJaniszewski
  • 25
  • 1
  • 5