OData / WebApi

OData Web API: A server library built upon ODataLib and WebApi
https://docs.microsoft.com/odata
Other
853 stars 476 forks source link

System.InvalidOperationException: The entity set Students is based on type MySchool.Dtos.Students.ViewStudentDto that has no keys defined #2116

Open BenjaminCharlton opened 4 years ago

BenjaminCharlton commented 4 years ago

Hi there!

I am new to OData but I think I have found some behaviour that isn't working as documented...

In order for the ODataConventionModelBuilder to work, your entities' key properties should be named Id or EntityNameId, right? And they should be public, right? And if you break any conventions you have to mark the property with a System.ComponentModel.DataAnnotations.KeyAttribute [Key] right?

Well I have stuck to those rules, but I'm still getting this message when I start up my application in Visual Studio:

System.InvalidOperationException: The entity set Students is based on type MySchool.Dtos.Students.ViewStudentDto that has no keys defined

Is this an issue or is it something I'm doing wrong?

One thing that might complicate matters is that I'm using strongly typed value objects for my Ids. So I have a StudentId class, which is the type of the Id property on the ViewStudentDto object. It has a JsonConverter and a TypeConverter to assist with serializing the StudentId to a plain old Guid, and those converters are both working fine. Is there some incompatibility between strongly typed IDs and OData EDM?

I am using version Microsoft.AspNetCore.OData 7.4.0 Beta.

If it's me, please can you help me figure out what I'm doing wrong? If it's a limitation in this Beta release, I hope you appreciate me reporting the issue here.

Many thanks

Benjamin

xuzhg commented 4 years ago

@BenjaminCharlton

For your sentence: In order for the ODataConventionModelBuilder to work, your entities' key properties should be named Id or EntityNameId, right? And they should be public, right? And if you break any conventions you have to mark the property with a System.ComponentModel.DataAnnotations.KeyAttribute [Key] right?

The answer is "Yes". You can refer here for the entity key convention:
Besides, you also can call "HasKey()" fluent API to define the key.

By noted, the type of key should be one of the following Edm primitive type from OData spec:

So, In corresponding C#, you can use "System.Boolean" to "Edm.Boolean", "System.Byte" to "Edm.Byte", and so on.

So, It might be root cause. If it's not, please share your C# model (classes) or a repro for me to dig more. Hope it can help.

BenjaminCharlton commented 4 years ago

Thanks very much for the fast response, Sam! Based on what you have told me, I would say the most likely reason I'm getting this error is that my key property is of proprietary type StudentId and not one of the primitive EDM types you listed.

Is there any mechanism for us to code a type converter for these scenarios? For example, I already have type converters to turn my StudentId into a System.Guid for Entity Framework and for JsonSerializer. Can I code something similar for EDM that will convert between StudentId and Edm.Guid?

BenjaminCharlton commented 4 years ago

I used Sam's insight to solicit help on StackOverflow.com but so far nobody knows how to do this:

https://stackoverflow.com/questions/61136569/how-to-configure-strongly-typed-entity-ids-value-objects-with-odata-edm/61139343#61139343

Is OData not capable of dealing with strongly typed IDs? Do I have to choose to abandon either OData or strongly typed IDs?

Thanks for any advice

Benjamin

xuzhg commented 4 years ago

I used Sam's insight to solicit help on StackOverflow.com but so far nobody knows how to do this:

https://stackoverflow.com/questions/61136569/how-to-configure-strongly-typed-entity-ids-value-objects-with-odata-edm/61139343#61139343

Is OData not capable of dealing with strongly typed IDs? Do I have to choose to abandon either OData or strongly typed IDs?

Thanks for any advice

Benjamin

can the method in https://devblogs.microsoft.com/odata/how-to-consume-sql-spatial-data-with-web-api-v2-2-for-odata-v4/ help you?

BenjaminCharlton commented 4 years ago

Thank you too, @xuzhg for the link to your article. I learned a lot from reading it!

I can't see how to configure OData to accept a strongly typed Id as its key though.

I feel like I should explain why this scenario would ever happen...

You might want to create immutable strongly typed value objects to represent the Ids of your entities in order to avoid accidentally muddling them up if a function took multiple Guids or multiple ints as an input. Having the strongly typed Ids gives you compile time type-checking to ensure the right arguments are passed in the right places.

For example:

It's better to have these strongly typed Ids at all places in the application, including at the edges where the application interfaces with web APIs and databases. In Blazor, you actually share the assembly containing your DTOs between the client and the server, allowing you to strongly type the Ids in your DTOs. Then the JsonConverterthat is applied on the class can deserialize them to plain old strings representing Guids.

In Entity Framework Core, you simply write:

 public class StudentConfiguration : IEntityTypeConfiguration<Student>
    {
        public void Configure(EntityTypeBuilder<Student> builder)
        {
            builder.Property(s => s.Id).HasConversion(id => id.Value, v => new StudentId(v));
builder.HasKey(s => s.Id);
         }
     }

Is there no equivalent in OData? Is this a feature that could be added in future?

BenjaminCharlton commented 4 years ago

Hi there!

I've been digging deeper into forums, blogs and source code and thought maybe adding this line would help OData with the conversion:

        {

            services.AddControllersWithViews().AddJsonOptions(
                 // Below I'm explicitly referencing the typeconverter that will convert between a plain string/guid and a strongly typed StudentId.
                options => options.JsonSerializerOptions.Converters.Add(new StudentIdJsonConverter()));

            services.AddOData();
      }

It hasn't made any difference, but I feel like I am getting close...

Do you think I could be onto something, setting the JsonOptions set in the Startup.ConfigureServices method or is it likely to be a dead-end so far as OData is concerned?

Thanks!

Benjamin

BenjaminCharlton commented 4 years ago

This idea above didn't get me anywhere. Then I experimented with JsonOptions and JsonSerializerOptions. I experimented a bit with custom OData formatters but that didn't get me anywhere either. Then I disappeared down a rabbit hole trying to derive custom classes from the OData configuration classes, gradually overriding more and more methods to do with adding keys and validating the model, until I finally gave up on that.

So far as I can see, OData currently isn't able to support using value objects as strongly typed IDs in your DTOs, even if you have written type converters and JSON converters for them.

It's a feature that would be good to see in future.

BenjaminCharlton commented 3 years ago

Hello again!

I just wanted to circle back after a few months and see if anyone can tell me whether there is a plan to allow OData to work with value objects such as strongly-typed Ids in .NET 6.0 or any future versions?

Thanks!

Benjamin

SequPL commented 2 years ago

Any news ?

it is very confusing that cannot use strongly typed value objects with DTOs directly.

mgernand commented 6 months ago

Hello there!

It's been almost 4 years since the OP first posted his question. Has anybody found a way do do this in the meantime? OData is the only library we use that does not (to my current knowledge) support strongly-typed IDs, or any other type convertion mechanism.

Cheers!