-
Notifications
You must be signed in to change notification settings - Fork 711
OData - Same Named Function in Two Configurations Causes System.InvalidOperationException on Startup #697
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
Comments
It should be allowable. Resolving an operation by its qualified name was previously an issue. I could have sworn I had a test for that. I'll have to spelunk some more. This seems to be the issue. I distinctly recall using I did notice you copied the MostExpensive method verbatim from OrdersController. The OrdersController intentionally uses attribute-routing and PeopleController intentionally uses convention-based routing to demonstrate the different styles. I doubt that's the cause this issue, but you'll want to be careful with mixing and matching as it's bound to cause issues. Since you already have a repro setup with the source, you might break inside |
@commonsensesoftware My sloppy cut and paste job on the examples was the fastest way to show the error. I have a much larger application where I do not mix and match different methods/schemas and I tripped over this issue. In the attached screenshot: In |
Thanks. I kind of gathered that from since you indicated Line 252. ;) I'm wondering why it wasn't resolved by FindBoundOperations. It would seem, there is a resolution case for collections missing against the entity set itself. That might explain how things fall through to this case and work with a single operation, but not with multiple. I was suspecting this might be the issue, but then I couldn't figure out how it worked at all. This behavior would explain how a uniquely named operation would yield a false positive. I don't have IntelliSense handy. Is there a way to find or resolve an operation by |
This is replicable on action as well. The following trigger the error message "Sequence contains more than one element" on "CreateWebHostBuilder(args).Build().Run();"
This is working with Microsoft.AspNetCore.OData.Versioning v4.1.1 |
I have a failing unit test in the example repo...I replicated my 'sloppy' cut and paste of Walking through the code, @commonsensesoftware, you are right, ln 244 While fishing around the I'm not able to step into Any suggestions on how to further track this down? |
We have updated OData versioning support to 5.0.0 and we seem to have an issue related with this. We have functions of the same name in a few of our entities and none of the paths were recognized (except one) for each function. All the other implementations using the same function name are ignored in Swagger and they return 404 when called. For instance, we have the following model and we can only call accounts/categories after upgrading. The others seem to be ignored
They are however listed in metadata as bound functions. For instance:
Do you have any insight as to why this might be the case? |
we are running into the same issue. Also when I create a method like this:
-> Swagger does not replace the parameters, it will get appended as querystring. |
Subscribed to this issue as I am currently migrating an API from .net framework to .net5 and have hit this problem. |
I've run into this issue as well. This appears to be caused by a bug way down in the depths of Microsoft.OData.Edm, right here. For some context, I created a quick sample to isolate this issue with two edm entity types The line of code linked above is ultimately called by When we hit line 2528 of I do not know, but it looks like the special collection handling on line 2533 might need to be run first such as if (parameterType.TypeKind == EdmTypeKind.Collection)
{
// covariance applies here, so IEnumerable<A> is applicable to IEnumerable<B> where B:A
IEdmCollectionType parameterCollectionType = (IEdmCollectionType)parameterType;
IEdmCollectionType bindingCollectionType = (IEdmCollectionType)bindingType;
return bindingCollectionType.ElementType.Definition.IsOrInheritsFrom(parameterCollectionType.ElementType.Definition);
}
else if (parameterType.TypeKind != bindingType.TypeKind)
{
return false;
}
else
{
return bindingType.IsOrInheritsFrom(parameterType);
} or something along those lines. I should point out that the above code doesn't actually work because @commonsensesoftware I don't know if it'd be more productive for you to communicate this issue with whoever maintains Microsoft.OData.Edm or if I should write up an issue against that repo. Sample project can be found here |
did some further testing. the following resolves the issue. I will write up a PR against Microsoft.OData.Edm. if (parameterType.TypeKind == EdmTypeKind.Collection && bindingType.TypeKind == EdmTypeKind.Entity)
{
var parameterCollectionType = (IEdmCollectionType)parameterType;
return bindingType.IsOrInheritsFrom(parameterCollectionType.ElementType.Definition);
}
if (parameterType.TypeKind != bindingType.TypeKind)
{
return false;
}
if (parameterType.TypeKind == EdmTypeKind.Collection)
{
// covariance applies here, so IEnumerable<A> is applicable to IEnumerable<B> where B:A
IEdmCollectionType parameterCollectionType = (IEdmCollectionType)parameterType;
IEdmCollectionType bindingCollectionType = (IEdmCollectionType)bindingType;
return bindingCollectionType.ElementType.Definition.IsOrInheritsFrom(parameterCollectionType.ElementType.Definition);
}
else
{
return bindingType.IsOrInheritsFrom(parameterType);
} @commonsensesoftware if you've got any pull with to grease the wheels to get this resolved, that'd be excellent. Hoping to not have to design around this bug. |
even further testing! (sorry to spam) The bug may actually be in Functions may be bound to either a type or a collection as such: foo.Collection.Function("Bulk")
.ReturnsFromEntitySet<Foo>("Foos");
foo.Function("Add")
.ReturnsFromEntitySet<Foo>("Foos");
if ( EntitySet != null )
{
var entity = EntitySet.Type;
if ( entities.Count == 0 || !entities[0].Equals( entity ) )
{
entities.Add( entity );
}
} the above change resolves the issue, but then creates a new issue for same-named functions bound directly to the entity type. One workaround could have this add both // I would expect this be bound to /Foos/Audit
foo.Collection.Function("Audit")
.ReturnsFromEntitySet<Foo>("Foos");
// not sure about routing conventions for functions on entity types - /Foos({id})/Audit?
foo.Function("Audit")
.ReturnsFromEntitySet<Foo>("Foos"); In this scenario, the |
engenb very interessting 👍 . This issue also prevent us from upgrading. What solution you use, if any ? :-) |
I got the same exception in "Microsoft.AspNet.OData.Routing.ODataRouteBuilderContext.ResolveOperation" when trying to add a function overload:
Edit:
|
I also have the issue with version 5.0.0. The following configuration code raises the exception :
I didn't find a way to bypass it, even by adding an optional parameter. The only way is to use two different method names BatchCreateA and BatchCreateB instead of BatchCreate for both. Swagger doesn't like it either but it can be fixed with a custom configuration code :
Do you know when the fix is going to be realeased ? Thanks. |
@engenb were you able to open a pull request for this issue? |
@engenb It feels (to me at least) that the newly introduced issue of same name method bound to both the collection and Foo is far more of an edge case for odata than the name named action across many entity collection. Sorry, I'm slightly worried I've misunderstood now. Do you mean functions bound with the same name to multiple entities or functions with the same name on the same entity? |
…ction parameter. Related #697
The issue 🔥 down has begun! 😉. Apologies for the long overdue follow-up. I believe I've been able to solve the problem. Thank you all for a lot of input, configurations, and variations for a repro. I've tried several combinations. Surprisingly, I couldn't reproduce the original problem. This could be due to any of the following:
It did, however, find that although the exceptions went away, new, subtle issues crept in. Here's the steps I used in an attempt to repro the problem:
The Configurationif ( apiVersion == ApiVersions.V1 )
{
order.Function( "MostExpensive" ).ReturnsFromEntitySet<Order>( "Orders" );
}
if ( apiVersion >= ApiVersions.V1 )
{
order.Collection.Function( "MostExpensive" ).ReturnsFromEntitySet<Order>( "Orders" );
}
if ( apiVersion == ApiVersions.V1 )
{
person.Function( "MostExpensive" ).ReturnsFromEntitySet<Person>( "People" );
person.Collection.Function( "MostExpensive" ).ReturnsFromEntitySet<Person>( "People" );
}
The Code/// <summary>
/// Gets the most expensive order.
/// </summary>
/// <returns>The most expensive order.</returns>
/// <response code="200">The order was successfully retrieved.</response>
/// <response code="404">The no orders exist.</response>
[ODataRoute( "MostExpensive" )]
[MapToApiVersion( "1.0" )]
[Produces( "application/json" )]
[ProducesResponseType( typeof( Order ), Status200OK )]
[ProducesResponseType( Status404NotFound )]
[EnableQuery( AllowedQueryOptions = Select )]
public SingleResult<Order> MostExpensive() =>
SingleResult.Create( new[] { new Order() { Id = 42, Customer = "Bill Mei" } }
.AsQueryable() );
/// <summary>
/// Gets the most expensive order.
/// </summary>
/// <param name="key">The order identifier.</param>
/// <returns>The most expensive order.</returns>
/// <response code="200">The order was successfully retrieved.</response>
/// <response code="404">The no orders exist.</response>
[ODataRoute( "{key}/MostExpensive" )]
[MapToApiVersion( "1.0" )]
[Produces( "application/json" )]
[ProducesResponseType( typeof( Order ), Status200OK )]
[ProducesResponseType( Status404NotFound )]
[EnableQuery( AllowedQueryOptions = Select )]
public SingleResult<Order> MostExpensive( int key ) =>
SingleResult.Create( new[] { new Order() { Id = key, Customer = "Bill Mei" } }
.AsQueryable() );
/// <summary>
/// Gets the most expensive person.
/// </summary>
/// <returns>The most expensive person.</returns>
/// <response code="200">The person was successfully retrieved.</response>
/// <response code="404">No people exist.</response>
[MapToApiVersion( "1.0" )]
[Produces( "application/json" )]
[ProducesResponseType( typeof( Order ), Status200OK )]
[ProducesResponseType( Status404NotFound )]
[EnableQuery( AllowedQueryOptions = Select )]
public SingleResult<Person> MostExpensive() =>
SingleResult.Create( new[] { new Person() { Id = 42, FirstName = "Elon", LastName = "Musk" } }
.AsQueryable() );
/// <summary>
/// Gets the most expensive person.
/// </summary>
/// <returns>The most expensive person.</returns>
/// <response code="200">The person was successfully retrieved.</response>
/// <response code="404">The person does not exist.</response>
[MapToApiVersion( "1.0" )]
[Produces( "application/json" )]
[ProducesResponseType( typeof( Order ), Status200OK )]
[ProducesResponseType( Status404NotFound )]
[EnableQuery( AllowedQueryOptions = Select )]
public SingleResult<Person> MostExpensive( int key ) =>
SingleResult.Create( new[] { new Person() { Id = key, FirstName = "John", LastName = "Doe" } }
.AsQueryable() );
Open API / Swagger DefinitionsThis abridged list is for Orders
People
RequestsThis worked in the UI and the HTTP REPL. Here is the REPL console output for simplicity.
HTTP/1.1 200 OK
api-deprecated-versions: 0.9
api-supported-versions: 1.0, 2.0, 3.0
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Date: Thu, 24 Jun 2021 20:35:40 GMT
OData-Version: 4.0
Server: Kestrel
Transfer-Encoding: chunked
{
"@odata.context": "http://localhost:5000/api/$metadata#Orders/$entity",
"id": 42,
"createdDate": 6/24/2021 1:35:41 PM,
"customer": "Bill Mei"
}
HTTP/1.1 200 OK
api-deprecated-versions: 0.9
api-supported-versions: 1.0, 2.0, 3.0
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Date: Thu, 24 Jun 2021 20:37:54 GMT
OData-Version: 4.0
Server: Kestrel
Transfer-Encoding: chunked
{
"@odata.context": "http://localhost:5000/api/$metadata#Orders/$entity",
"id": 1,
"createdDate": 6/24/2021 1:37:55 PM,
"customer": "Bill Mei"
}
HTTP/1.1 200 OK
api-deprecated-versions: 0.9
api-supported-versions: 1.0, 2.0, 3.0
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Date: Thu, 24 Jun 2021 20:39:42 GMT
OData-Version: 4.0
Server: Kestrel
Transfer-Encoding: chunked
{
"@odata.context": "http://localhost:5000/api/$metadata#People/$entity",
"id": 42,
"firstName": "Elon",
"lastName": "Musk"
}
HTTP/1.1 200 OK
api-deprecated-versions: 0.9
api-supported-versions: 1.0, 2.0, 3.0
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Date: Thu, 24 Jun 2021 20:39:57 GMT
OData-Version: 4.0
Server: Kestrel
Transfer-Encoding: chunked
{
"@odata.context": "http://localhost:5000/api/$metadata#People/$entity",
"id": 1,
"firstName": "John",
"lastName": "Doe"
} ConclusionUnderstanding the OData vernacular is a bit challenging. As I recall, The only other possibilities are that an
As I understand it, you can can't have an operation that only appears on a singleton because it's just a special mapping to a specific entity. This list is, therefore, the order in which Things then get tricky. It's possible that a collection and entity both have the same operation name and the same number of parameters. In fact, I noticed that if these things line up, it will appear to have matched correctly (by happenstance). The only way to make sure the correct operation is selected is by matching up the parameters, which isn't so easy. A bound entity function must have a key parameter for the entity. If all other parameters match, but there are still some action parameters left, it is assumed that the operation is bound to the entity not the collection. As far as I can tell, this does result in the correct action-to-operation match every time. This fix will be released in the next patch. Can't wait? The fixes are in this branch if you want to play around with them. |
@commonsensesoftware thanks for investigating and fixing the issue. I'm hitting this too, and was wondering what version of he nuget package will have the fix, and when will it likely be available? |
…ction parameter. Related #697
…ction parameter. Related #697
…ction parameter. Related #697
tldr - In the OData example project I added a function with the same name to two different configurations. The examples can no longer run. I used to be able to add functions with the same name to two different OData endpoints without issue.
Example Repo - https://github.com/benhysell/aspnet-api-versioning
Example Project - https://github.com/benhysell/aspnet-api-versioning/tree/master/samples/aspnetcore/SwaggerODataSample
Using the examples I've duplicated the
MostExpensive
function fromOrder
toPerson
via the steps below to trigger the exception.Steps to Reproduce
SwaggerODataSample
person.Collection.Function( "MostExpensive" ).ReturnsFromEntitySet<Person>( "People" );
https://github.com/benhysell/aspnet-api-versioning/blob/master/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs#L34
Is it allowable to have the same named function in two different models? And if not why not?
The text was updated successfully, but these errors were encountered: