Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class property Context appears to be defined using the incorrect type of string vs DataServiceContext #331

Open
wayneyan opened this issue Jan 4, 2023 · 4 comments

Comments

@wayneyan
Copy link

wayneyan commented Jan 4, 2023

When generating client proxies the variable _Context as defined in the C# proxy for a entity can result in compile time errors.
To reproduce the problem, the OData metadata being used is that for Microsoft Dynamics HR. In this case all types and all bound actions are selected for generation. The following is the URL I was using with the some tenant id being that relevant for my scenario. A custom header was used to specify the bearer token for authentication purposes. I have attached the metadata as a text file to save you the trouble of going to the service end point.

eg. https://aos-rts-sf-c8d2e0b7ff3-prod-westeurope.hr.talent.dynamics.com/namespaces/some tenant id/data

In particular the following two compile time errors are created:

CS1061: 'string' does not contain a definition for 'EntityTracker' and no accessible extension method 'EntityTracker' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
CS1503: Argument 1: cannot convert from 'string' to 'Microsoft.OData.Client.DataServiceContext'

and these occur with the entity type <EntityType Name="EssWorkflowWorkItem"> where the bound action "complete" results in generation of the code

        [global::Microsoft.OData.Client.OriginalNameAttribute("complete")]
        public virtual global::Microsoft.OData.Client.DataServiceActionQuery Complete(string outcome, string comment)
        {
            global::Microsoft.OData.Client.EntityDescriptor resource = Context.EntityTracker.TryGetEntityDescriptor(this);
            if (resource == null)
            {
                throw new global::System.Exception("cannot find entity");
            }

            return new global::Microsoft.OData.Client.DataServiceActionQuery(this.Context, resource.EditLink.OriginalString.Trim('/') + "/Microsoft.Dynamics.DataEntities.complete", new global::Microsoft.OData.Client.BodyOperationParameter("outcome", outcome),
                    new global::Microsoft.OData.Client.BodyOperationParameter("comment", comment));
        }

On inspection the property Context appears to be type string but should be type DataServiceContext. The following snippet is the generated fragment.

        [global::Microsoft.OData.Client.OriginalNameAttribute("Context")]
        public virtual string Context
        {
            get
            {
                return this._Context;
            }
            set
            {
                this.OnContextChanging(value);
                this._Context = value;
                this.OnContextChanged();
                this.OnPropertyChanged("Context");
            }
        }

There is also the definition of a property ContextString which I believe to be causing some confusion in the template.

        [global::Microsoft.OData.Client.OriginalNameAttribute("ContextString")]
        public virtual string ContextString
        {
            get
            {
                return this._ContextString;
            }
            set
            {
                this.OnContextStringChanging(value);
                this._ContextString = value;
                this.OnContextStringChanged();
                this.OnPropertyChanged("ContextString");
            }
        }

[metadata.txt](https://github.com/OData/ODataConne
metadata.txt
ctedService/files/10345616/metadata.txt)

At this point the workaround for me has been to alter the generated code such that property _Context is defined as DataServiceContext. viz.

    [global::Microsoft.OData.Client.OriginalNameAttribute("Context")]
    public virtual DataServiceContext Context
    {
        get
        {
            return this._Context;
        }
        set
        {
            this.OnContextChanging(value);
            this._Context = value;
            this.OnContextChanged();
            this.OnPropertyChanged("Context");
        }
    }
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "#VersionNumber#")]
    private DataServiceContext _Context;
    partial void OnContextChanging(**DataServiceContext** value);

This would point to some error in the T4 template caused by the confusion between Context and ContextString

VisualStudio Version v17.4.3
Microsoft.Odata.Client v7.13.x
metadata.txt

@habbes
Copy link
Contributor

habbes commented Jan 10, 2023

Related issue: #297

@habbes
Copy link
Contributor

habbes commented Jan 10, 2023

This is a known issue. The root issue is that the BaseEntityType defined in the OData client library (Microsoft.OData.Client) library defines a property called Context (of type DataServiceContext). All entity classes generated by OData Connected Service derive from this BaseEntityType class.

If your schema has an entity type with a property called Context, then an entity class will be generated with the property defined in the schema. In your case, your EssWorkflowItem entity type most likely has a property called Context of type Edm.String. So, this leads to the string Context property being generated in the C# code. This property conflicts with the inherited Context property from the BaseEntityType leading to the compiler errors that you observe.

Regarding ContextString, maybe you have different property in your entity type with this name, this seems to be unrelated to the problem.

@habbes
Copy link
Contributor

habbes commented Jan 10, 2023

The right workaround is to rename the generated property to something else, e.g. rename the Context property to something else in your generated code.

 [global::Microsoft.OData.Client.OriginalNameAttribute("Context")]
public virtual string ContextFoo
{
  // ...
}

You can rename the property to anything else, the important thing is that value of the OriginalNameAttribute match what's in your schema (i.e., "Context") so that client can be able to properly match the CLR property to the property in the OData service.

The downside with this workaround is that you'll have to repeat this process each time you re-generate the code.

One we way we could address this issue without breaking changes is adding a flag in the UI that lets the user decide whether the client should provide alternative names to properties that may conflict with existing built-in properties.

@wayneyan
Copy link
Author

The approach these days infrequently uses UI given that DEVOPS pipelines are more likely to be the way forward. (We use the DEVOPS pipeline to call CLI tools that generate the client proxies.) Some form of configuration in terms of "alternative names to properties" as provided to CLI tools is more important.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants