6

I made a partial class file to add new properties to my Entity-Framework generated model.

I am using WebAPI + OData, and the $metadata doesn't list my new/custom properties, and so the JSON it returns doesn't include my new/custom properties.

For example, let's say my Entity is "Person"

"Person" has one Database property; NumSpouses; an int which is returned in $metadata like this: <Property Name="NumSpouses" Type="Edm.Int32"/>

That's great, but I added a property like this to a separate file, with a partial class:

public partial class Person {
  ...
  public string MarriedStatus { 
     get { return this.NumSpouses==0 ? "Single" : "Married"; }
  }
  ... 
}

How can I get this Property available in my OData responses?

<Property Name="MarriedStatus" Type="Edm.String"/>

Currently, if I asked for MarriedStatus in $expand (as if it were a NavigationProperty.... which it's not [I thought I'd try $expand anyway as if it magically provided custom properties]), I'd get a message like this:

{
  "odata.error":{
    "code":"","message":{
      "lang":"en-US","value":"The query specified in the URI is not valid. Could not find a property named 'MarriedStatus' on type 'fakeDataModels.Person'."
},"innererror":{
  "message":"Could not find a property named 'MarriedStatus' on type 'fakeDataModels.Person'.","type":"Microsoft.Data.OData.ODataException","stacktrace":"   at ..."
    }
  }
}
Community
  • 1
  • 1
Nate Anderson
  • 18,334
  • 18
  • 100
  • 135
  • 1
    Is your partial class in the same namespace as the EF generated model? – lencharest Apr 01 '16 at 03:06
  • Yes it is. The namespace is consistent, let's call it "PersonDataModels", in both files, and both files are in the same project. I know `Controller.Json` JSON serialization is not the same as OData serialization, but I will point out that JSON will serialize the `MarriedStatus` property unless I explicitly say not to (`ScriptIgnoreAttribute`). Maybe this calls for the equivalent of a "ViewModel", I should add to my client-side (Javascript) code, which will just provide me the convenience function `getMarriedStatus()`. – Nate Anderson Apr 01 '16 at 16:05
  • 1
    Partial classes work as expected in a toy project that I created. Maybe the problem lies in your OData configuration. Are you using `ODataConventionModelBuilder` and simply registering entity sets? Or something more sophisticated? – lencharest Apr 01 '16 at 16:43
  • I am showing you fake code, not my real code (to hide my business goal/logic). But I think I am simply registering entity sets; I have the equivalent of this: `ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet("Person"); config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());` – Nate Anderson Apr 01 '16 at 17:07
  • Is it possible to share the toy project code somehow? I know lots of files are involved... Maybe there is no jsFiddle equivalent. If I can identify some difference I will post back generalizing the difference in terms of the fake circumstances I described. Thanks for your help. – Nate Anderson Apr 01 '16 at 17:09
  • 1
    I wrote the toy as a [single file](https://gist.github.com/lencharest/54d65895a1636035a44b59616d5e9b02). The configuration is designed for running under OWIN, but you should still be able to grok what I've done. Note that I'm calling `config.MapODataServiceRoute` rather than `config.Routes.MapODataRoute`. Is your project OData V3 or V4? – lencharest Apr 01 '16 at 17:25
  • Tried to start a chat. My work site blocker won't allow it. I'm using NuGet "Microsoft ASP.NET Web API 2.2 for OData v1-3". When I click on "Projec t Information" it [it links me here] (http://odata.github.io/v1-3-libraries.html). I will try `config.MapODataServiceRoute` instead of `config.Routes.MapODataRoute`. The only other substantial difference I see is that my Controllers return DbSets: `[Queryable] public SingleResult GetPerson([FromODataUri] int key) { return SingleResult.Create(db.Persons.Where(person => person.Id == key)); }` – Nate Anderson Apr 01 '16 at 18:14
  • I added an empty `set {}` on all of these properties, and it worked! I remember reading this before about JSON serialization (or [serialization in general](http://stackoverflow.com/q/13401192/1175496)), but was not sure if it would apply here. Your code has this set { }. I wonder you omitted the `set;`, would your OData results omit the `MarriageStatus` property? Since your code made this difference clear, if you could post this as an answer I will accept to give you credit. – Nate Anderson Apr 01 '16 at 18:29

2 Answers2

6

MarriedStatus is a calculated/readonly property. The ASP.NET implementation of OData does not currently support such properties. As a workaround, add a setter that throws NotImplementedException.

    public string MarriedStatus {
        get { return this.NumSpouses > 0 ? "Married" : "Single"; }
        set { throw new NotImplementedException(); }
    }

Optionally, if you are using OData V4, you can annotate MarriedStatus to specify that it is calculated. See Yi Ding's answer to OData read-only property. But the annotation is advisory only; it does not prevent clients from attempting to set a calculated property (e.g., in a POST request).

Community
  • 1
  • 1
lencharest
  • 2,825
  • 2
  • 15
  • 22
  • 1
    There is, however, still an issue when trying to serialize the property as LINQ to Entities chokes if there is a setter and the property doesn't exist in the database. See [this question](https://stackoverflow.com/questions/6919709/only-initializers-entity-members-and-entity-navigation-properties-are-supporte) – MylesRip May 30 '17 at 01:03
3

In addition to the answer of lencharest. You should use the Ignore() function of the Entity Framework fluent API instead of the [NotMapped] attribute. Because OData looks for this attribute to ignore properties for serialization. If you use the fluent API you will not have this problem.

dbModelBuilder.Entity<TEntity>()
    .Ignore(i => i.ComputedProperty);
Vqf5mG96cSTT
  • 2,561
  • 3
  • 22
  • 41
  • 1
    But I want to include *more* columns... I don't want to Ignore columns. I don't see how Ignore/NotMapped is relevant – Nate Anderson Feb 10 '18 at 01:22
  • 1
    Do note that .Ignore() simply tells Entity Framework to ignore this property as if it were not in the database. So it will ignore this for inserts, selects, updates, while still allowing you to have it on your model. This way you can add whatever you want. I was also providing an answer to @MylesRip. – Vqf5mG96cSTT Feb 12 '18 at 09:37