4

I am trying to expose a read-only computed property through my OData v4 service using Web API. My searches only turn up posts from 2014 or older, with the only solutions being either obsolete code or the promise that computed properties would be supported in the next version of OData (I'm sure there have been a few "next versions" since then).

For my example, I'll use a Person class with a combined FullName:

public class Person
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // My calculated property.
    public string FullName
    {
        get
        {
            return FirstName + " " + LastName;
        }
    }
}

This is my WebAPIConfig:

using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using TestService.Models;

namespace TestService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

            builder.EntitySet<Person>("People");

            config.MapODataServiceRoute("ODataRoute", null, builder.GetEdmModel());
        }
    }
}

At present, the JSON does not display the FullName. What would it take to do that in the current version of everything?

Edit: Adding my controller for my Person object:

using System.Web.Http;
using System.Web.OData;
using TestService.Models;

namespace TestService.Controllers
{
    public class PeopleController : ODataController
    {
        DataContext db = new DataContext();

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }

        [EnableQuery]
        public IQueryable<Person> Get()
        {
            return db.People;
        }

        [EnableQuery]
        public SingleResult<Person> Get([FromODataUri] int key)
        {
            IQueryable<Person> result = db.People.Where(p => p.ID == key);
            return SingleResult.Create(result);
        }
    }
}
Pizzor2000
  • 379
  • 3
  • 19

2 Answers2

5

I found an answer via another answer. For your case I think the code would look something like this:

namespace TestService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

            builder.EntitySet<Person>("People");
            // Added this:
            builder.StructuralTypes.First(t => t.ClrType == typeof(Person))
                .AddProperty(typeof(Person).GetProperty(nameof(Person.FullName)));

            config.MapODataServiceRoute("ODataRoute", null, builder.GetEdmModel());
        }
    }
}

This worked for me on .NET Core 3.1 and the latest WebApi and OData libraries (I think).

Note that if your model is EF based you'll also need [NotMapped] on the property to keep it from blowing up.

S'pht'Kr
  • 2,809
  • 1
  • 24
  • 43
1

I Had the exact same problem with :

[NotMapped]
public int QtyRemaining
{ 
    get
    { 
        return QtyToReceive - QtyReceived;
    }
}

and I added to the startup.cs, in the ODataConventionModelBuilder like you said. Here :

private static IEdmModel GetEdmModel()
{
    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<MyEntity>("ApiEndPointName..ByTheWay"));
    var cu = builder.StructuralTypes.First(t => t.ClrType == typeof(MyEntity));
    cu.AddProperty(typeof(MyEntity).GetProperty("QtyRemaining"));
    
    return builder.GetEdmModel();
}

HOWEVER, the property added this way seems to lack the capacity of being filtered in odata /api/blabla/entity$filter=QtyRemaining eq 0

You will have a great and meaningfull error like :

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.

---> System.InvalidOperationException: The LINQ expression 'DbSet() .Where(i => i.QtyRemaining >= __TypedProperty_0)' could not be translated. Additional information: Translation of member 'QtyRemaining' on entity type 'myEntity' failed. This commonly occurs when the specified member is unmapped (Duuhh yess?!). Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

I have hit my head on the wall a couple to coming to a sad conclusion : https://powerusers.microsoft.com/t5/Building-Flows/Get-Items-and-OData-Filter/td-p/483179

It seems that you will NOT be able to FILTER on the particular property.

In My case, and as suggested in the link, I will only do : /api/myentity$select=field1,QtyTotal,QtySold,QtyRemaining&$filter=QtySold lt QtyTotal.

hope this 'addendum' to main answer will help someone like me

Simon
  • 2,266
  • 1
  • 22
  • 24