OData / odata.net

ODataLib: Open Data Protocol - .NET Libraries and Frameworks
https://docs.microsoft.com/odata
Other
686 stars 349 forks source link

The complex type 'System.Collections.Generic.IEnumerable`1[...]' has no settable properties. #2854

Open MrtnSys opened 7 months ago

MrtnSys commented 7 months ago

Issue

We are currently migrating Client and Server from OData V2 to OData V4 and get the following exception with one query in the .NET Maui App:

The complex type 'System.Collections.Generic.IEnumerable`1[...]' has no settable properties. (I have hidden the class name in brackets because of private interface.)

As the server people do not know what to do and the query runs successfully in Postman and returns the desired Json result, we address the .NET OData library.

Assemblies affected

Visual Studio 2022 Client: Microsoft Maui (.NET 8.0) App Server: Apache Olingo OData Library, Tomcat Server, SAP Database Protocol: OData V4

OData Client in Maui-Project is generated with Unchase OData Visual Studio Extension with Metadata.xml from OData Server.

Package References in Maui Project:

<PackageReference Include="Microsoft.OData.Client" Version="7.20.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.20.0" />
<PackageReference Include="Microsoft.OData.Edm" Version="7.20.0" />
<PackageReference Include="Microsoft.Spatial" Version="7.20.0" />

Exception

 at Microsoft.OData.Client.ClientEdmModel.ValidateComplexType(Type type, EdmTypeCacheValue cachedEdmType)
 at Microsoft.OData.Client.ClientEdmModel.GetOrCreateEdmType(Type type)
 at Microsoft.OData.Client.ClientEdmModel.CreateEdmProperty(IEdmStructuredType declaringType, PropertyInfo propertyInfo)
 at Microsoft.OData.Client.ClientEdmModel.<>c__DisplayClass37_0.<GetOrCreateEdmTypeInternal>b__0(EdmEntityTypeWithDelayLoadedProperties entityType)

This has broken my current development and I am stuck on this.

Any help is appreciated.

xuzhg commented 7 months ago

The error message 'The complex type System.Collections.Generic.IEnumerable``1[...] has no settable properties. shows you have 'IEnumerable`1' as complex type. This is not a POCO (Plain Old CLR/Class Objects). You should use POCO class model. See https://learn.microsoft.com/en-us/odata/client/using-poco-classes

MrtnSys commented 7 months ago

Oh man, I have found it, thanks to your suggestion. In my Project I have added some partial classes according to some classes in ODataService.cs generated from Metadata.xml, to attach some additional information.

And in the problematic class there is one Property "MyClassList" which defines the IEnumerable and has no setter.

public partial class Product
{
        public IEnumerable<MyClass> MyClassList
        {
            get
            {
                .....
            }
        }
}

But this is implemented since we use OData V2 and Package "Microsoft.Data.OData v5.8.5" which had no problem with the missing setter.

So I tried to add the [JsonIgnore] and [NotMapped] Attribute to the Property "MyClassList" to stop the OData Client from accessing it, but neither of them worked. Setting the Property to private and removing it's usage solves the problem, but I need it public. So I will have a look what I can do.

habbes commented 7 months ago

@MrtnSys I'm curious what is the use case of this property. Is it a property that's not part of the CSDL model? Something you don't want OData to track?

gathogojr commented 7 months ago

@MrtnSys Confirmed that an exception is thrown if a property of type IEnumerable<T> is declared in the partial class - even when no declared or dynamic property with a similar name is returned by the service.

Exception message:

'The complex type 'System.Collections.Generic.IEnumerable`1[T]' has no settable properties.'.

The workaround is to declare the property as IList<T>, List<T>, Collection<T>, ObservableCollection<T>, etc - just not IEnumerable<T>!

cc. @xuzhg @habbes

MrtnSys commented 7 months ago

I'm curious what is the use case of this property. Is it a property that's not part of the CSDL model? Something you don't want OData to track?

@habbes Since the generated ODataService.cs implements INotifyPropertyChanged, I have bound the results of the queries directly to the UI. And with additional properties in a partial class I can provide additional or formatted information to the UI based on the objects data. It's although a good way not having to use an UI-converter for formatting the data. So these properties do not need to be tracked. And from my experience with serializers their behaviour is that they ignore properties without setters, which has worked that way in "Microsoft.Data.OData v5.8.5" or known Json-Serializers.

And thanks for providing the workaround @gathogojr.

joecarl commented 7 months ago

I feel like there should be an attribute to prevent the materializer from setting unwanted properties. Something similar to [IgnoreClientProperty] but in the opposite direction.