1

I'm using the latest version of ABP from abp.io and have two entities with a many-many relationship. These are:

public class GroupDto : AuditedEntityDto<Guid>
{
    public GroupDto()
    {
        this.Students = new HashSet<Students.StudentDto>();
    }

    public string Name { get; set; }
    public bool IsActive { get; set; } 
    public virtual ICollection<Students.StudentDto> Students { get; set; }
}

and

public class StudentDto : AuditedEntityDto<Guid>
{
    public StudentDto()
    {
        this.Groups = new HashSet<Groups.GroupDto>();
    }

    public string Name { get; set; }
    public bool IsActive { get; set; }

    public virtual ICollection<Groups.GroupDto> Groups { get; set; }
}

I set up the following test to check that I am retrieving the related entities, and unfortunately the Students property is always empty.

public async Task Should_Get_List_Of_Groups()
{
    //Act
    var result = await _groupAppService.GetListAsync(
        new PagedAndSortedResultRequestDto()
    );

    //Assert
    result.TotalCount.ShouldBeGreaterThan(0);
    result.Items.ShouldContain(g => g.Name == "13Ck" && g.Students.Any(s => s.Name == "Michael Studentman"));
}

The same is true of the equivalent test for a List of Students, the Groups property is always empty.

I found one single related answer for abp.io (which is not the same as ABP, it's a newer/different framework) https://stackoverflow.com/a/62913782/7801941 but unfortunately when I add an equivalent to my StudentAppService I get the error -

CS1061 'IRepository<Student, Guid>' does not contain a definition for 'Include' and no accessible extension method 'Include' accepting a first argument of type 'IRepository<Student, Guid>' could be found (are you missing a using directive or an assembly reference?)

The code for this is below, and the error is being thrown on the line that begins .Include

public class StudentAppService :
    CrudAppService<
        Student, //The Student entity
        StudentDto, //Used to show students
        Guid, //Primary key of the student entity
        PagedAndSortedResultRequestDto, //Used for paging/sorting
        CreateUpdateStudentDto>, //Used to create/update a student
    IStudentAppService //implement the IStudentAppService
{
    private readonly IRepository<Students.Student, Guid> _studentRepository;

    public StudentAppService(IRepository<Student, Guid> repository)
        : base(repository)
    {
        _studentRepository = repository;
    }

    protected override IQueryable<Student> CreateFilteredQuery(PagedAndSortedResultRequestDto input)
    {
        return _studentRepository
             .Include(s => s.Groups);
    }
}

This implements this interface

public interface IStudentAppService :
    ICrudAppService< // Defines CRUD methods
        StudentDto, // Used to show students
        Guid, // Primary key of the student entity
        PagedAndSortedResultRequestDto, // Used for paging/sorting
        CreateUpdateStudentDto> // Used to create/update a student
{
    //
}

Can anyone shed any light on how I should be accessing the related entities using the AppServices?

Edit: Thank you to those who have responded. To clarify, I am looking for a solution/explanation for how to access entities that have a many-many relationship using the AppService, not the repository.

To aid with this, I have uploaded a zip file of my whole source code, along with many of the changes I've tried in order to get this to work, here.

jcreek
  • 43
  • 10
  • ABP is a framework for creating boilerplate code. It's not EF Core 5 nor is it needed to use EF 5. Its `Repositry` classes aren't needed either. The error seems pretty clear, and clearly explained in the error text - `'IRepository' does not contain a definition for 'Include'`. You tried to use a method meant to be used in EF Core and LINQ on an unrelated `Repository` class. By using those "generic repository" classes, you are no longer able to work with entities and their relations. – Panagiotis Kanavos Mar 08 '21 at 08:02
  • `Include` can only be used on an `IQueryable` or `DbSet`, which means you need the DbContext or DbSet that was hidden by `IRepository`. That's just one reason why "generic repositories" are an **anti**pattern. With a specialized repository you could have a `WithGroups` method that returns `_dbContext.Students.Include(s=>s.Groups)`. Or, you could add an .... `Include` method to `IRepository` that calls `Include`. – Panagiotis Kanavos Mar 08 '21 at 08:08

2 Answers2

3

You can lazy load, eagerly load or configure default behaviour for the entity for sub-collections.

Default configuration:

Configure<AbpEntityOptions>(options =>
{
    options.Entity<Student>(studentOptions =>
    {
        studentOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Groups);
    });
});

Eager Load:

//Get a IQueryable<T> by including sub collections
var queryable = await _studentRepository.WithDetailsAsync(x => x.Groups);
        
//Apply additional LINQ extension methods
var query = queryable.Where(x => x.Id == id);
        
//Execute the query and get the result
var student = await AsyncExecuter.FirstOrDefaultAsync(query);

Or Lazy Load:

var student = await _studentRepository.GetAsync(id, includeDetails: false);
//student.Groups is empty on this stage

await _studentRepository.EnsureCollectionLoadedAsync(student, x => x.Groups);
//student.Groups is filled now

You can check docs for more information.

Edit: You may have forgotten to add default repositories like:

services.AddAbpDbContext<MyDbContext>(options =>
{
    options.AddDefaultRepositories();
});

Though I would like to suggest you to use custom repositories like

IStudentRepository:IRepository<Student,Guid>

So that you can scale your repository much better.

gterdem
  • 757
  • 5
  • 14
  • Thank you for your reply! I feel like I'm missing something fundamental here. If I add the default configuration from your answer to the `ConfigureServices` method in my module in the EntityFrameworkCore project I still have the same issue when trying to access it via the app service, which retrieves a StudentDto entity. For example my Should_Get_List_Of_Students method is almost identical to the Should_Get_List_Of_Groups example given above, and that still has the same issue. How can I get the default behaviour for certain entities to eagerly load via the AppService, not repository? – jcreek Mar 08 '21 at 11:30
  • @jcreek I updated my answer with containing more details. – gterdem Mar 08 '21 at 14:31
  • Hi @gterdem, thank you for your response. To clarify, I am looking for a solution/explanation for how to access entities that have a many-many relationship using the AppService, not the repository. To aid with this, I have uploaded a zip file of my whole source code, along with many of the changes I've tried in order to get this to work, [here](https://www.dropbox.com/s/f7hmqmf6l7cbxn5/assess.zip?dl=1). – jcreek Mar 10 '21 at 07:36
0

You can apply your 'include' logic configured via AbpEntityOptions by overriding base method CreateFilteredQueryAsync:

    protected override async Task<IQueryable<TEntity>> CreateFilteredQueryAsync(TGetListInput input)
    {
        return await ReadOnlyRepository.WithDetailsAsync();
    }