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

Problem with open type #211

Open
Robelind opened this issue Apr 11, 2024 · 6 comments
Open

Problem with open type #211

Robelind opened this issue Apr 11, 2024 · 6 comments
Labels
enhancement New feature or request

Comments

@Robelind
Copy link

I'm trying to use an open type in my EDM, but I run into a problem when applying queries.

    public class TestData
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Value { get; set; }
    }

    public class Test
    {
        public int Id { get; set; }
        public ICollection<TestData> Data { get; set; }
    }

    public class TestDTO
    {
        [Key]
        public int Id { get; set; }
        public Dictionary<string, object> Properties { get; set; } = new();
    }

    [ApiController]
    [Route("api/[controller]")]
    public class TestsController : ControllerBase
    {
        public async Task<IActionResult> Get(ODataQueryOptions<TestDTO> options)
        {
            IMapper mapper = new Mapper(new MapperConfiguration(x =>
            {
                x.CreateMap<Test, TestDTO>().ForMember(dest => dest.Properties,
                    cfg => cfg.MapFrom(src => src.Data.Select(d => new KeyValuePair<string, object>(d.Name, d.Value))));
            }));
            IEnumerable<Test> entities =
            [
                new Test { Id = 1, Data = [new TestData { Name = "Value", Value = 10 }] },
                new Test { Id = 2, Data = [new TestData { Name = "Value", Value = 100 }] }
            ];

            return(Ok(await entities.AsQueryable().GetQueryAsync(mapper, options)));
        }
    }

If I do e.g. http://localhost:52769/api/Tests?$orderby=Value
it results in the following exception:

System.InvalidCastException: Unable to cast object of type 'Microsoft.OData.UriParser.SingleValueOpenPropertyAccessNode' to type 'Microsoft.OData.UriParser.SingleValuePropertyAccessNode'.
   at AutoMapper.AspNet.OData.LinqExtensions.<GetOrderByCall>g__GetMethodCall|10_0(<>c__DisplayClass10_0&)
   at AutoMapper.AspNet.OData.LinqExtensions.GetOrderByCall(Expression expression, OrderByClause orderByClause, ODataQueryContext context)
   at AutoMapper.AspNet.OData.LinqExtensions.GetQueryableMethod(Expression expression, ODataQueryContext context, OrderByClause orderByClause, Type type, Nullable`1 skip, Nullable`1 top)
   at AutoMapper.AspNet.OData.LinqExtensions.GetOrderByMethod[T](Expression expression, ODataQueryOptions`1 options, ODataSettings oDataSettings)
   at AutoMapper.AspNet.OData.LinqExtensions.GetQueryableExpression[T](ODataQueryOptions`1 options, ODataSettings oDataSettings)
   at AutoMapper.AspNet.OData.QueryableExtensions.GetQueryable[TModel,TData](IQueryable`1 query, IMapper mapper, ODataQueryOptions`1 options, QuerySettings querySettings, Expression`1 filter)
   at AutoMapper.AspNet.OData.QueryableExtensions.GetQueryAsync[TModel,TData](IQueryable`1 query, IMapper mapper, ODataQueryOptions`1 options, QuerySettings querySettings)
   at CompactStore.API.Controllers.TestsController.Get(ODataQueryOptions`1 options) in C:\Users\wlsrlm\source\repos\Compact Store NextGen\CompactStore\API\Controllers\ItemsController - Copy.cs:line 120
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
@BlaiseD
Copy link
Member

BlaiseD commented Apr 12, 2024

"$orderby=Value" - Value is not a member of TestDTO is it?

@BlaiseD BlaiseD closed this as completed Apr 12, 2024
@Robelind
Copy link
Author

Yes it is. That's the point of open OData types, that you can create dynamic properties.
Open types

@BlaiseD
Copy link
Member

BlaiseD commented Apr 12, 2024

Maybe I'm misunderstanding.

Try running the query without the orderyby then post the result showing the 'Value field. Do the same using OData without this library.

If OData shows a Value a field and GetQueryAsync does not then you're welcome to submit a PR.

@Robelind
Copy link
Author

http://localhost:52769/api/Tests:

{
    "@odata.context": "http://localhost:52769/api/$metadata#Tests",
    "value": [
        {
            "Id": 1,
            "Value": 10
        },
        {
            "Id": 2,
            "Value": 100
        }
    ]
}

The result is the same with or without Automapper.
The problem arises when applying sorting.

@BlaiseD
Copy link
Member

BlaiseD commented Apr 12, 2024

Ok - that node type is not being handled. I think the code you're looking for is here if you're interested in a PR.

@BlaiseD BlaiseD reopened this Apr 12, 2024
@BlaiseD BlaiseD added the enhancement New feature or request label Apr 12, 2024
@antonGritsenko
Copy link

Actually, open types are not supported at all by AutoMapper OData, this is not the only place. You will have also problem during filtering, grouping etc.
The worst case if EFCore is behind of the LINQ query with https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=data-annotations, then it will not work properly at all.

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

No branches or pull requests

3 participants