4

I am trying to implement querying to Sql Server via EF Core and OData(7.1.0).

Action method looks like follows:

[HttpGet]
public IEnumerable<UserInfoDto> Get(ODataQueryOptions ops)
{
     return this.service.GetUserInfos(ops);
}

Service code:

public List<UserInfoDto> GetUserInfos(ODataQueryOptions ops)
{
    using (var context = new EFContext())
    {
        var query = context.Users.Join(context.Customers, x => x.CustomerId, y => y.Id, (x, y) => new UserInfoDto
        {
            Id = x.Id,
            Name = x.Name,
            Age = x.Age,
            CustomerId = x.CustomerId,
            CustomerTitle = y.Title,
            CustomerDescription = y.Description
        });

        var result = ops.ApplyTo(query).Cast<UserInfoDto>().ToList();
        return result;
    }
}

Startup Configute method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
     app.UseMvc(b => 
     {
         b.Count().Filter().OrderBy().Select().MaxTop(null);
         b.EnableDependencyInjection();
     });
}

However, when I am having $select in query (e.g https://localhost:5001/api/userinfos?$select=id), instead of projected result I am getting an error:

InvalidOperationException: No coercion operator is defined between types 'Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder+SelectSome`1[Oda taApp.UserInfoDto]' and 'OdataApp.UserInfoDto'.



What am I missing? Any help is appreciated.

1 Answers1

3

When you using $select query option for OData, you need an IQueryable of dynamic .

For example, imagine the following class:

public class Person {
    public int Id { get; set }
    public string Name { get; set; }
}

So, if you have a query without $select, the IQueryable will generate a collection of Person, with all properties (id and name) in each item.

But, if you use a $select=id query, each item will have only the ID property and you cant Cast a dynamic type to a Person type.

In other words, you cant use $select and return a List<UserInfoDto>, you need to return a List<dynamic> without the Cast method, just like this:

    var result = ops.ApplyTo(query) as IQueryable<dynamic>;
    return result.ToList();

EDIT:

the full implementation of method will be:

// changing the return type
public List<dynamic> GetUserInfos(ODataQueryOptions<UserInfoDto> ops)
{
    using (var context = new EFContext())
    {
        var query = context.Users.Include(Customers, x => x.CustomerId, y => y.Id, (x, y) => new UserInfoDto
        {
            Id = x.Id,
            Name = x.Name,
            Age = x.Age,
            CustomerId = x.CustomerId,
            CustomerTitle = y.Title,
            CustomerDescription = y.Description
        });
        // casting the applyto result
        var result = ops.ApplyTo(query) as IQueryable<dynamic>;
        return result.ToList();
    }
}
Rodrigo G Rodrigues
  • 331
  • 1
  • 3
  • 16
  • 1
    Thanks, I will try it out and let you know whether it works for me c: – Andrew Kibalnikov Nov 13 '19 at 21:55
  • Yeah, after your explanation things become quite logical to me! Since OData `$select` does not cover a couple of complex queries I need, I guess I will stay with static DTO and forbid `$select` operations at all. Anyway, many thanks for your help! – Andrew Kibalnikov Jan 03 '20 at 10:03
  • 1
    Object of type 'System.Linq.EnumerableQuery`1[System.Object]' cannot be converted to type 'System.Linq.IQueryable`1[DtoExample] – Jackie Jan 14 '21 at 16:19
  • @Jackie did you got way around this – harishr Jan 07 '22 at 19:19
  • @harishr I think @Jackie is trying to cast IQueryable<> back to IQueryable, which is not working because the types of both are not able to cast each other. You dont need to cast the ApplyTo() back to your Dto or Model, because $select will change the type, like I said in my answer `you cant cast dynamic type to a Person type`. Please try to correct your code to not enforces a casting to a class of your solution. – Rodrigo G Rodrigues Jan 09 '22 at 18:49
  • 1
    @rodrigo the problem/error posted by Jackie is after removing the casting, $select still does not work – harishr Jan 10 '22 at 04:01
  • @harishr I didnt find solution for this exactly. But this is what I have decided to use: https://stackoverflow.com/a/16517768/5055668 This way you can apply $select on generic types – Jackie Jan 11 '22 at 11:18
  • @Jackie I update the answer so you can see the full implementation of the method, I think your return type is not the same as the applyto casting one. – Rodrigo G Rodrigues Jan 12 '22 at 20:59
  • I dont have the model class definition, so I cant run myself an example. Using the .Select(string) will not work for $expand – Rodrigo G Rodrigues Jan 12 '22 at 21:04