Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OData read-only property

I have a WebAPI 2.2 application with OData V4. Also I'm using EF 6.1.

In one of my entities I have a calculated property:


public class Person 
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   
   // Calculated Property - No setter
   public string FullName 
   { 
        get { return FirstName + " " + LastName; }
   }
} 

In order to provide the calculated property to my clients, I need to register in the OData Model

public static IEdmModel GetModel() 
{
    ODataModelBuilder builder = new ODataConventionModelBuilder();
    
    builder.Namespace = "NavigationServices";
    builder.EntityType<Person>;
    builder.EntityType<Person>()
        .Property(a => a.FullName); // Calculated Property
        
    return builder.GetEdmModel();
}

So, when I obtain my data in the client side, every object has the Calculated property.

However, when I try to Create (POST) a new element or Update (PUT) a existing one, my action don't recognize the element and generates an error saying that it doesn't find the "set method" for the property.

I read a couple of posts about read only properties in OData (apparently not supported) but I don't find a way to use OData with calculated properties.

Some advice of how to overcome this situation?

like image 323
user3120088 Avatar asked Dec 03 '14 16:12

user3120088


3 Answers

Now there is a soft way for doing this which is to build a contract between the client and server using annotations.

In the Core vocabulary of the V4 standard, there is a such term:

<Term Name="Computed" Type="Core.Tag" DefaultValue="true" AppliesTo="Property">
    <Annotation Term="Core.Description" String="A value for this property is generated on both insert and update"/>
</Term>

In Web API OData, in WebConfig.cs, you write such code to add such annotation to your property:

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
var model = builder.GetEdmModel() as EdmModel;
model.SetVocabularyAnnotation(
    new EdmAnnotation(model.EntityContainer.FindEntitySet("People").EntityType().FindProperty("FullName"),
    new EdmTerm("Org.OData.Core.V1", "Computed", EdmPrimitiveTypeKind.Boolean),
    new EdmBooleanConstant(true)));

Then in your data, it'll look something like this:

<Annotations Target="V4Service.Models.Person/FullName">
    <Annotation Term="Org.OData.Core.V1.Computed" Bool="true"/>
</Annotations>

Through the steps above the service advertises that the FullName property on Person entity is computed by the service. Then in the controller methods for POST and PATCH requests you can have you own logic of ignoring any value sent by the client for the FullName property and compute your own.

I'm not sure which client you are using. If you're using OData Client for .NET, our support for getting annotation values will be in our next release. If you don't mind using EdmLib directly, the annotation value retrieval support has already been added.

like image 199
Yi Ding - MSFT Avatar answered Sep 25 '22 05:09

Yi Ding - MSFT


You are right, OData doesn't support read-only properties at this time.

However, it supports read-only entities.

Or, you can trick OData by adding a setter which does nothing to your property.

public string FullName
{ 
    get
    {
        return FirstName + " " + LastName;
    }
    set
    {
        // do nothing
    }
}

This is how you set an entity as read-only:

public class Northwind : DataService<NorthwindEntities>
{
    // This method is called only once to initialize service-wide policies.
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("Customers", EntitySetRights.AllRead);
    }
}
like image 30
Mihai Dinculescu Avatar answered Sep 26 '22 05:09

Mihai Dinculescu


In your entity you need a [NotMapped] data annotation:

[NotMapped]
public string FullName => $"{FirstName} - {LastName}";

in your OData configuration:

builder.EntityType<Person>;
builder.StructuralTypes.First(t => t.ClrType == typeof(Person))
                .AddProperty(typeof(Person).GetProperty(nameof(Person.FullName)));

this way you don't need an empty setter.

like image 37
Masoud Avatar answered Sep 25 '22 05:09

Masoud