odata.net
odata.net copied to clipboard
Allow using unmapped properties getters in queries
Imagine the following scenario:
[EntitySet("TestEntities")]
public class TestModel
{
public decimal PlasticKg { get; set; }
public decimal OthersKg { get; set; }
[NotMapped] // This attribute doesn't exist in Microsoft.OData.Client but I think it would be necessary for this case and useful for other cases
public decimal TotalKg { get => PlasticKg + OthersKg; }
}
public class MyContext : DataServiceContext
{
public DataServiceQuery<TestModel> TestEntities;
public MyContext(Uri serviceRoot) : base(serviceRoot)
{
TestEntities = CreateQuery<TestModel>("TestEntities");
}
}
// Main program:
var ctx = new MyContext(uri);
// The following lines should behave the same (but they don't):
ctx.TestEntities.Where(c => c.PlasticKg + c.OthersKg > 10).ToList(); // Works fine
ctx.TestEntities.Where(c => c.TotalKg > 10).ToList(); // Throws because the server responds with "Bad Request: Could not find a property named 'TotalKg' in model ..."
So my proposal is:
- Implement the
NotMappedAttribute
class. - On query building, if using a property that has a custom
getter
and theNotMapped
attribute, then the getter definition should be used as part of the query instead of using the property name. - Additionaly the
NotMappedAttribute
should prevent the materializer from setting that property in all cases, it also should throw exception if the property is used in a query and it doesn't have a customgetter
.
I could implement this myself and submit a pull request, but I would like to receive some feedback before proceding. Thanks
@joecarl Thanks for sharing your thoughts. Any contributions are welcome. Feel free to share the PR.
By the way, it looks like to design/implement the $compute feature. For example:
[Computed]
public decimal TotalKg { get => PlasticKg + OthersKg; }
Here's our proposal: If an unmapped property (Not Found from Edm Model at the client side) is defined and used in the LINQ Expression, we can do:
- If this property has '[Computed]' decorated, and it only contains the getter, we can generate this property into $compute clause. So
ctx.TestEntities.Where(c => c.TotalKg > 10).ToList();
can be translated to:
~/testEntities?$filter=TotalKey gt 10&$compute=PlasticKg add OthersKg as TotalKey
- If this property has nothing decorated, we can translate this property as nested expression as:
~/testEntities?$filter=(PlasticKg add OthersKg) gt 10
Any thoughts?
@xuzhg Thankyou very much for your answer.
Your proposal seems good to me, I will think about it and will likely submit a pull request. I have a question though: does the $compute
feature bring any advantage over the non computed query?
Also, about the NotMapped
attribute. It might not be necessary for this case.
But imagine the scenario where my model has a property public decimal Cost { get; set; }
and the OData endpoint actually has a field named Cost
but for whatever reason I don't want to use it (e.g.: the api has obsolete prices and i want to calculate it myself later or maybe the matching name are just an unfortunate coincidence). How could I prevent the materializer from setting that prop? Also how could I make sure that an exception will be thrown if I use that prop in a query? (An exception should be thrown since otherwise I would be doing an unintended filtering and the api would silently return wrong results)
I've been attempting to implement this, but I've encountered a significant obstacle. The getter method's body cannot be translated at runtime because it is a compiled method, unlike Expression
objects whose tree is stored at compile time, allowing access at runtime.
Does anybody know how to face this problem? Any help is appreciated