1

I have Implemented ODATA and Automapper and facing 2 problems.

1 problem:

If I am using the Data Layer Models, It is giving me correct result:

[EnableQuery]
 public IQueryable<Student> GetStudentTest()
 {
   return _studentService.RetrieveStudent();
 }

and the expected result is correct where enrollments and Studentaddress is null:

studentId : 1
firstName : "Captain"
firMiddleName : "Cool"
lastName : "Marvel"
enrollmentDate : "2021-12-28T06:52:52.743"
enrollments : null
studentAddresses : null

But When I use the Automapper and DTO:

[EnableQuery]
public IQueryable<StudentsDTO> GetStudent()
{
 return _mapper.ProjectTo<StudentsDTO>(_studentService.RetrieveStudent());
}

The result is wrong that is it gives me result with enrollment and results:

[
    {
        "studentId": 1,
        "firstName": "Captain",
        "firMiddleName": "Cool",
        "lastName": "Marvel",
        "enrollmentDate": "2021-12-28T06:52:52.743",
        "enrollments": [
            {
                "enrollmentId": 1,
                "courseId": 1,
                "studentId": 1,
                "grade": 1,
                "course": {
                    "courseId": 1,
                    "title": "Course1",
                    "credits": 5,
                    "enrollments": null
                },
                "student": null
            }
        ],
        "studentAddresses": [
            {
                "addressId": 1,
                "houseNumber": "5/133",
                "city": "Lucknow",
                "state": "UttarPradesh",
                "studentId": 1,
                "students": null
            }
        ]
    }
]

2 Problem:

Using OData and Automapper with the query like $expand within $expand:

/api/v1/GetEnrollments?$expand=Student($expand=StudentAddresses),Course

The ProjectTo of automapper doesnt able to bind the data to subtypes as mentioned studentAddresses.

The answers and suggestions will be much appreciated.

1 Answers1

0

I recently wrote about this, though the question is unrelated, the reasoning is the same: OData accent-insensitive filter

Queryable Extensions
ProjectTo must be the last call in the chain. ORMs work with entities, not DTOs. So apply any filtering and sorting on entities and, as the last step, project to DTOs.

The reason that the 2nd example doesn't work is that the data has already been enumerated into memory, before the $expand clause was applied, calling $expand or Include on the in-memory collection will not result in any additional call to the database to retrieve the missing data.

Instead we need to use an additional package: AutoMapper.Extensions.ExpressionMapping that will give us access to the UseAsDataSource extension method.

UseAsDataSource
Mapping expressions to one another is a tedious and produces long ugly code.

UseAsDataSource().For() makes this translation clean by not having to explicitly map expressions. It also calls ProjectTo() for you as well, where applicable.

This changes your implementation to the following which should allow the EnableQueryAttribute to correctly apply the QueryOptions on the request to the underlying IQueryable<T> expression.

[EnableQuery] 
public IQueryable<StudentsDTO> GetStudent()
{
    return _studentService.RetrieveStudent().UseAsDataSource().For<StudentsDTO>());
}

NOTE:

The implementation of _studentService.RetrieveStudent() can still affect this execution, especially the how $expand might be applied. If it returns an IEnumerable<T> that is simply wrapped in an IQueryable<T> using the [AsQueryable()][3] extension then the results might not be as you expect, but it would not help to speculate in this post how to resolve this.

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81