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?
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.
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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With