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

Odata $filter and $orderby not working with Select to new DTO for one to one relationship #23877

Closed
cap7ainjack opened this issue Jan 14, 2021 · 8 comments · Fixed by #24003
Closed
Assignees
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@cap7ainjack
Copy link

cap7ainjack commented Jan 14, 2021

Since I update to .NET Core $orderby and $filter does not work in this specific case, which was perfectly working before.

The DTO models

public class AddressDto
{
        public Guid Id { get; set; }
        public string ZipCode{ get; set; }
        public IdAndName State { get; set; }
}
public class IdAndName 
{
        public Guid Value { get; set; }
        public string Text{ get; set; }
}


// The DB model
public class AdressEntity {
            public Guid Id { get; set; }
        public string ZipCode{ get; set; }
        public Guid StateId {get;set;}
        public State State {get;set;}
}

// The SELECT:

query.Select(x =>
    new AddressDto()
    {
        Id = x.Id,
        ZipCode = x.ZipCode,
        State = x.StateId.HasValue ? new IdAndName() { Text = x.State.Name, Value= x.StateId.Value } : null,
    });

And the request

api/AddressDto?$count=true&$top=20&$skip=0&$orderby=state/text%20desc&$filter=(state/value%20eq%20be78ab1e-2ae3-4754-956e-7e099ea82242)

Which was wokring perfectly in the .NET Framework now thorws and error:

... .Where(t => t.Outer.Active == __TypedProperty_1 && t.Outer.StateId.HasValue ? new IdAndName { \r\n Text = t.Inner.Name, \r\n Value = t.Outer.StateId.Value \r\n }\r\n : null.Value == __TypedProperty_2)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.

The problem is obvious. Somehow Odata sends all the null check expression in the query...

I`m not using AutoMapper, I do all converts like this with select. And with the nested collection properties everything is fine.... but with the single ones which should be easies it is NOT...

When I change the null check to be like this:

 query.Select(x =>
new AddressDto()
{
    Id = x.Id,
    ZipCode = x.ZipCode,
    State = x.State != null ? new IdAndName() { Text = x.State.Name, Value= x.StateId.Value } : null,
}); 

I get another error:

"The operands for operator 'Equal' do not match the parameters of method 'op_Equality'."

I do not think there is other way to perform null check...

@maumar maumar changed the title Odata $filter and $orderby now working with Select to new DTO for one to one relationship Odata $filter and $orderby not working with Select to new DTO for one to one relationship Jan 14, 2021
@maumar maumar self-assigned this Jan 14, 2021
@maumar
Copy link
Contributor

maumar commented Jan 14, 2021

full repro without odata:

        [ConditionalFact]
        public virtual void Repro23877()
        {
            using var ctx = new MyContext();
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();
            var baseQuery = ctx.Addresses.Select(x => new AddressDto
            {
                Id = x.Id,
                ZipCode = x.ZipCode,
                State = x.StateId != null ? new IdAndName { Value = x.StateId.Value, Text = x.State.Name } : null
            });

            var guid = Guid.NewGuid();
            var query = baseQuery.Where(x => x.State.Value != guid).ToList();
        }

        public class AddressDto
        {
            public Guid Id { get; set; }
            public string ZipCode { get; set; }
            public IdAndName State { get; set; }
        }
        public class IdAndName
        {
            public Guid Value { get; set; }
            public string Text { get; set; }
        }

        public class AddressEntity
        {
            public Guid Id { get; set; }
            public string ZipCode { get; set; }
            public Guid? StateId { get; set; }
            public State State { get; set; }
        }

        public class State
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
        }

        public class MyContext : DbContext
        {
            public DbSet<AddressEntity> Addresses { get; set; }
            public DbSet<State> States { get; set; }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
            }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Repro23877;Trusted_Connection=True;MultipleActiveResultSets=true");
            }
        }

@maumar
Copy link
Contributor

maumar commented Jan 14, 2021

in comparison, when we compare x.State.Value (or rather, IIF((a.Outer.StateId != null), new IdAndName() {Value = a.Outer.StateId.Value, Text = a.Inner.Name}, null).Value) to guid they are initially typed as Guid. However, during query optimizer, in TryOptimizeMemberAccessOverConditional we optimize left side it and type as Guid?. However we don't compensate for the type difference so the exception is thrown

@anranruye
Copy link

anranruye commented Jan 15, 2021

In my memory, it seems that ef(core) never support this kind of projection.

@maumar
Copy link
Contributor

maumar commented Jan 15, 2021

@cap7ainjack you can workaround the issue by making Value of the IdAndName a nullable guid and use:

 query.Select(x =>
new AddressDto()
{
    Id = x.Id,
    ZipCode = x.ZipCode,
    State = x.State != null ? new IdAndName() { Text = x.State.Name, Value= x.StateId.Value } : null,
}); 

@cap7ainjack
Copy link
Author

That fixes is it. Thanks. I didnt get the Guid exception until now. I mean the previous error

"The operands for operator 'Equal' do not match the parameters of method 'op_Equality'."

is kind of replaced with Guid and nullable Guid comparison error. However, thanks again. This solves the issue.

@maumar maumar reopened this Jan 15, 2021
@maumar
Copy link
Contributor

maumar commented Jan 15, 2021

there is still a bug here, reopening the issue so that we can track & fix it.

@ajcvickers ajcvickers added this to the 6.0.0 milestone Jan 15, 2021
maumar added a commit that referenced this issue Jan 27, 2021
… new DTO for one to one relationship

Problem was that during query optimization, TryOptimizeMemberAccessOverConditional could change the type of the expression (from non nullable to nullable), which then would throw when trying to re-construct the expression tree upstream.
Fix is to change the type to the original as part of TryOptimizeMemberAccessOverConditional.

Fixes #23877
@maumar maumar added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jan 27, 2021
maumar added a commit that referenced this issue Jan 28, 2021
… new DTO for one to one relationship

Problem was that during query optimization, TryOptimizeMemberAccessOverConditional could change the type of the expression (from non nullable to nullable), which then would throw when trying to re-construct the expression tree upstream.
Fix is to change the type to the original as part of TryOptimizeMemberAccessOverConditional.

Fixes #23877
maumar added a commit that referenced this issue Jan 28, 2021
… new DTO for one to one relationship

Problem was that during query optimization, TryOptimizeMemberAccessOverConditional could change the type of the expression (from non nullable to nullable), which then would throw when trying to re-construct the expression tree upstream.
Fix is to change the type to the original as part of TryOptimizeMemberAccessOverConditional.

Fixes #23877
@Erythnul
Copy link

Glad to see that this is being fixed. Could the fix be considered for cherry picking into a servicing release?
This is a serious blocker for our planned upgrade path.
Thanks in advance!

maumar added a commit that referenced this issue Feb 12, 2021
… new DTO for one to one relationship

Problem was that during query optimization, TryOptimizeMemberAccessOverConditional could change the type of the expression (from non nullable to nullable), which then would throw when trying to re-construct the expression tree upstream.
Fix is to compensate for type changes across the visitor and only reconstructing original type at the top level (projection, lambda, method call arguments).

Fixes #23877
maumar added a commit that referenced this issue Feb 17, 2021
… new DTO for one to one relationship

Problem was that during query optimization, TryOptimizeMemberAccessOverConditional could change the type of the expression (from non nullable to nullable), which then would throw when trying to re-construct the expression tree upstream.
Fix is to compensate for type changes across the visitor and only reconstructing original type at the top level (projection, lambda, method call arguments, etc).

Fixes #23877
maumar added a commit that referenced this issue Feb 23, 2021
… new DTO for one to one relationship

Problem was that during query optimization, TryOptimizeMemberAccessOverConditional could change the type of the expression (from non nullable to nullable), which then would throw when trying to re-construct the expression tree upstream.
Fix is to compensate for type changes across the visitor and only reconstructing original type at the top level (projection, lambda, method call arguments, etc).

Fixes #23877
@maumar maumar closed this as completed in b247ded Feb 25, 2021
@maumar
Copy link
Contributor

maumar commented Feb 25, 2021

@Erythnul sorry for the late response - the bugfix is quite risky, as it involves modifying type of the expression tree and has potential to break existing queries if we missed a case. Also, there is a relatively straightforward workaround - adding conversion to nullable. So sadly this fix won't be ported into the patch release.

@ajcvickers ajcvickers modified the milestones: 6.0.0, 6.0.0-preview3 Mar 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
5 participants