Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ef7 unable to retrieve child collections' child object's properties

EF7 doesn't support lazy loading of child objects, but does support the .Include() function. That being said, I'm struggling with something and am not sure if it just isn't possible in EF7, or I've just been staring at this too long.

Suppose something like the following (checking reg.Activities.Task.Ordinal (an int), Task is always null, even when I check the DB myself and am certain there is in fact a related record)...

public void SomeOtherMethod()
    var r = getRegistration(User.UserName);
    var act = r.Activities
      .Where(a => a.IsDone == false)  // unfinished
      .OrderByDescending(o => o.Task.Ordinal)  // Task indicates activity type, is always null
      .FirstOrDefault();  // to get a user's most recent unfinished activity

    //DO SOMETHING WITH ACT
}

public Registration getRegistration(string userName) {
    var reg = _context.Registrations
      .Where(r => r.User.UserName == userName)  // this works however?
      .Include(r => r.Acvitities)  // List<Activity>
      .FirstOrDefault();

      return reg;
}

...I have the navigation properties in place in the model classes, but .Task above is null and not loaded.

Further, since the query has been projected, I can't .Include additional properties any more in the creation of act. I cant .ThenInclude in the creation of reg because class Registration does not include a definition for a Task property (but Registration does have a collection of Activities that are List<Activity>, and Activity does have a Task that is related to another table/class that defines the tasks and the order they should be presented to users per Activity.

I've tried various incantations of .Join, .Include and .ThenInclude hoping to be able to join the Task to each of the Activities while returning the Registration object, but this fails because Registration itself does not contain a Task property.

I considered creating a new issue on GitHub, but am not yet certain that it isn't very doable and I'm just not looking at this correctly.


UPDATE1: Mihail suggested using...
.Include(r => r.Activities.Select(resp => resp.Responses))
...but this yields an exception. This SO (https://stackoverflow.com/a/30151601/3246805) indicates thats for EF5 and that .ThenInclude should be used.

However, trying that suggestion...
.ThenInclude(r => r.Select(t => t.Task))
...yields the following Exception...

The properties expression 'r => {from Activity t in r select [t].Task}' is not valid. The expression should represent a property access: 't => t.MyProperty'. When specifying multiple properties use an anonymous type: 't => new { t.MyProperty1, t.MyProperty2 }'.
Parameter name: propertyAccessExpression



UPDATE2: Stafford asked for schema. Best effort at a sharable repo...

public class RegistrationData {
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    public MyUser User { get; set; }    // MyUser : IdentityUser

    //blah blah, more fields

    public List<UserTask> Activitys { get; set; }
}

public class UserTask {
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    [Required]
    public bool IsDone { get; set; } = false;

    [Required]
    public int RegistrationId { get; set; }
    [Required]
    public RegistrationData Registration { get; set; }

    [Required]
    public int TaskId { get; set; }
    [Required]
    public Task Task { get; set; }

    public List<UserResponse> Responses { get; set; }
}

public class Task {
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.None)] // ID comes from loaded config
    public int Id { get; set; }

    [StringLength(20, MinimumLength = 1)]
    public string Name { get; set; }

    [Required]
    public int Ordinal { get; set; }

    [Required]
    public int GroupId { get; set; }
}

public class UserResponse {
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    [Required]
    public int UserTaskId { get; set; }
    [Required]
    public int QuestionNumber { get; set; }
}
like image 972
t.j. Avatar asked Dec 17 '15 06:12

t.j.


1 Answers

Use Include followed by ThenInclude for the child's child properties. The child properties may not show up in intellisense for ThenInclude, but just enter it anyway - it will compile and operate as expected.

var reg = _context.Registrations
  .Where(r => r.User.UserName == userName)
  .Include(r => r.Acvitities).ThenInclude(a => a.Task)
  .Include(r => r.Activities).ThenInclude(a => a.SomethingElse)
  .FirstOrDefault();
  return reg;
like image 175
Stafford Williams Avatar answered Oct 12 '22 22:10

Stafford Williams