Search code examples
linqasp.net-web-apiodataprojection

The query specified in the URI is not valid + Projection + LINQ Select


I apologize if this is a repetitive question. Basically what I am trying to achieve is to have my projection is a separate method/class which I can reuse like this (keep in mind I am beginner with LINQ).

public static Expression<Func<MyObject, object>> getProjection()
    {
        return r => new
        {
            Name = r.Name,
            Address = r.Address,
            City = r.City,
            PostalCode = r.PostalCode,
            Province = r.Province,
            Country = r.Country,
            Phone = r.Phone,
            Website = r.Website
        };
    }

However, when I call Projection like this.

var filteredList = db.MyObject.Select(Projections.getProjection()).AsQueryable();
return Ok(filteredList);

Then I get the error

The query specified in the URI is not valid. Could not find a property named 'Name' on type 'System.Object'.

If I replace the Projection helper method with actual Projection just by copy and pasting then it works. I am just trying to avoid rewriting the same projection again for other Select methods by creating a helper method "getProjection". First if you can verify if this is the right way of calling Projection. Secondly how can I get rid of that OData error.

Thanks


Solution

  • LINQ to Entities needs strong types. They can be generic, and the CAN be anonymous. Your problem is that your function returns a weak type Expression>.

    Use a strong type to solve this, or don't use a projection method.

    A: Strong type. This is actually quite tidy, and could be considered lazy to try and get away with an anonymous type here.

    public class MyRecord { /* fields here */ }
    public static Expression<Func<MyObject, MyRecord>> getProjection()
    {
        return r => new MyRecord
        {
            Name = r.Name,
            Address = r.Address,
            City = r.City,
            PostalCode = r.PostalCode,
            Province = r.Province,
            Country = r.Country,
            Phone = r.Phone,
            Website = r.Website
        };
    }
    
    /* of type IQueryable<MyRecord> */
    var filteredList = db.MyObject.Select(getProjection());
    

    B: Remove the projection method:

    /* of type IQueryable<Anonymous> - this is why 'var' exists */
    var filteredList = db.MyObject.Select(r => new
    {
        Name = r.Name,
        Address = r.Address,
        City = r.City,
        PostalCode = r.PostalCode,
        Province = r.Province,
        Country = r.Country,
        Phone = r.Phone,
        Website = r.Website
    });
    

    Note this if you intend to return this to another method, you're still going to need a strong (non-anonymous) type. The only way you can pass anonymous types through methods is via generics. e.g:

    function T Do<T>(func<T> something) { return something(); }
    var anon = Do(() => { a = 1, b = 2 });
    

    C: If you do this projection often AND you DON'T want to create projection classes then ask yourself 'why not?'. If you want to avoid writing this project code often and are happy to create projection classes then consider using a tool such as AutoMapper. It's use is pretty common nowadays.