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

Query: Exception when filtering on nullable boolean value (through navigation property) #5454

Closed
AustinFelipe opened this issue May 20, 2016 · 10 comments
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@AustinFelipe
Copy link

AustinFelipe commented May 20, 2016

I'm trying to figure out why the following command doesn't work:

var availability = await context.UserAvailabilities
    .Where(t => t.User.Id == user.Id && t.User.IsProfessional)
    .ToListAsync();

I'm getting this error (Sorry about the size...):

An unhandled exception has occurred while executing the request System.ArgumentException: Expression of type 'System.Func2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[UPlatform.Models.UserAvailability,System.Collections.Generic.IAsyncEnumerable1[UPlatform.Models.ApplicationUser]],UPlatform.Models.ApplicationUser],System.Nullable1[System.Boolean]]' cannot be used for parameter of type 'System.Func2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[UPlatform.Models.UserAvailability,System.Collections.Generic.IAsyncEnumerable1[UPlatform.Models.ApplicationUser]],UPlatform.Models.ApplicationUser],System.Boolean]' of method 'System.Collections.Generic.IAsyncEnumerable1[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[UPlatform.Models.UserAvailability,System.Collections.Generic.IAsyncEnumerable1[UPlatform.Models.ApplicationUser]],UPlatform.Models.ApplicationUser]] _Where[TransparentIdentifier2](System.Collections.Generic.IAsyncEnumerable1[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[UPlatform.Models.UserAvailability,System.Collections.Generic.IAsyncEnumerable1[UPlatform.Models.ApplicationUser]],UPlatform.Models.ApplicationUser]], System.Func2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier2[UPlatform.Models.UserAvailability,System.Collections.Generic.IAsyncEnumerable1[UPlatform.Models.ApplicationUser]],UPlatform.Models.ApplicationUser],System.Boolean])'

I thought it was because the table is empty and there is some problem when I'm trying to use the t.User property, but isn't. Wherever I try to access the User property reference, I get this error.

This strange behaviour starts when I updated to 1.0.0-rc2 versions.

If I remove the Where clausure, it works:

var availability = await context.UserAvailabilities.ToListAsync();

I already tried to replace ToListAsync() by ToList(), but I'm getting the same error.

Any help will be welcome. 👍

Thanks

@AustinFelipe AustinFelipe changed the title Error when using where in an empty table Error when using Where with reference May 21, 2016
@rowanmiller rowanmiller added this to the 1.0.0 milestone May 23, 2016
@maumar
Copy link
Contributor

maumar commented May 23, 2016

@AustinFelipe could you provide some more info about the entities and DbContext you are using (specifically contents of class User, UserAvailability and OnModelCreating method of the DbContext)

@AustinFelipe
Copy link
Author

@maumar They are:

public class ApplicationUser : IdentityUser
    {
        public string Name { get; set; }

        public bool IsProfessional { get; set; }

        public string PictureUrl { get; set; }

        public Genre Genre {
            get
            {
                return (Genre)GenreIdentifier;
            }
            set
            {
                GenreIdentifier = (int)value;
            }
        }

        public int GenreIdentifier { get; set; }

        /* Profile */
        public string AboutMe { get; set; }

        public ICollection<UserSpecialization> UserSpecs { get; set; }

        public ICollection<UserResumeEntry> Resume { get; set; }

        public ICollection<UserComment> Comments { get; set; }

        public ICollection<UserTimetable> Timetable { get; set; }

        public ICollection<UserAvailability> Availability { get; set; }
   }
public class UserAvailability
    {
        public int Id { get; set; }

        public int Day { get; set; }

        public string Hours { get; set; }

        public ApplicationUser User { get; set; }
    }
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public DbSet<Specialization> Specializations { get; set; }
        public DbSet<Package> Packages { get; set; }
        public DbSet<ChatMessage> ChatMessages { get; set; }
        public DbSet<UserComment> UserComments { get; set; }
        public DbSet<UserResumeEntry> UserResumes { get; set; }
        public DbSet<UserTimetable> UserTimetables { get; set; }
        public DbSet<UserAvailability> UserAvailabilities { get; set; }
        public DbSet<UserVacation> UserVacations { get; set; }
        public DbSet<Transaction> Transactions { get; set; }
        public DbSet<TransactionItem> TransactionItems { get; set; }

        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<UserSpecialization>()
                .HasKey(t => new { t.ApplicationUserId, t.SpecializationId });

            builder.Entity<ApplicationUser>()
                .HasMany(t => t.Comments)
                .WithOne(t => t.User);

            builder.Entity<ApplicationUser>()
                .HasMany(t => t.Timetable)
                .WithOne(t => t.User);

            builder.Entity<ApplicationUser>()
                .Ignore(t => t.Genre);

            builder.Entity<UserComment>()
                .HasOne<ApplicationUser>(t => t.Client);

            builder.Entity<UserTimetable>()
                .HasOne<ApplicationUser>(t => t.Client);

            builder.Entity<Transaction>()
                .HasOne<ApplicationUser>(t => t.Buyer);

            builder.Entity<Transaction>()
                .HasOne<ApplicationUser>(t => t.Seller);

            builder.Entity<UserTimetable>()
                .HasOne<Transaction>(t => t.Transaction);
        }

Let me know if you need something

@maumar
Copy link
Contributor

maumar commented May 25, 2016

@AustinFelipe the workaround is as follows:

var availability = await context.UserAvailabilities
    .Where(t => t.User.Id == user.Id && t.User.IsProfessional == true)
    .ToList();

if that doesn't work you can also try:

var availability = await context.UserAvailabilities
    .Where(t => t.User.Id == user.Id && ((bool?)t.User.IsProfessional) == true)
    .ToList();

The reason is that, since User is optional navigation and the result can be null, we are trying to compensate for that by casting non-nullable value types to their nullable version (so that in case User is null, we can return a null for the IsProfessional as well). In some cases we can be smart enough so that the process in transparent to the end user (and hopefully case will be transparent also), but sometimes users need to compensate for it manually. Good example would be projecting optional value into anonymous type:

var availability = await context.UserAvailabilities
    .Select(t => new { foo = t.User.IsProfessional })
    .ToList();

will throw and we can't do anything about it, so it needs to be changed to:

var availability = await context.UserAvailabilities
    .Select(t => new { foo = (bool?)t.User.IsProfessional })
    .ToList();

@divega
Copy link
Contributor

divega commented May 25, 2016

I think this is important still because it happens for a simple query and although the workaround (adding == true) looks simple enough once you know it, it is hard to imagine customers arriving to it.

From another perspective it looks lower priority than other RTM bugs.

@rowanmiller I have asked @maumar to still try to get a fix for this in but #3807 seems higher priority. Thoughts?

@maumar
Copy link
Contributor

maumar commented May 26, 2016

Actually, this seems to be a duplicate of #4753 (scheduled for 1.0.1 currently)

@AustinFelipe
Copy link
Author

AustinFelipe commented May 26, 2016

@maumar Yeah, I changed it and it's working now.

var availability = await context.UserAvailabilities
    .Where(t => t.User.Id == user.Id && t.User.IsProfessional == true)
    .ToListAsync();

Btw, I agree with @divega, it's hard to figure out, isn't it?

But, thanks man, it's working now 👍

@rowanmiller
Copy link
Contributor

@rowanmiller I have asked @maumar to still try to get a fix for this in but #3807 seems higher priority. Thoughts?

Agreed

@rowanmiller rowanmiller modified the milestones: 1.0.1, 1.0.0 May 31, 2016
maumar added a commit that referenced this issue Jun 16, 2016
Problem was for happening for queries with optional navigations like so:

context.Orders.Where(o => o.Customer.IsVip)

In this case Customer can be nullable, so IsVip can also be nullable. We compensate for this by introducing the following:

context.Orders.Where(o => o.Customer != null ? (bool?)o.Customer.IsVip : (bool?)null)

However, we didn't convert it back to the original type (users had to introduce those casts themselves). Without the cast, those queries were throwing compile-time exceptions that were not very informative.

Fix is to cast back to the original type requested by user:

context.Orders.Where(o => (bool)(o.Customer != null ? (bool?)o.Customer.IsVip : (bool?)null))

This still may cause runtime errors if o.Customer is actually null, but those are much more understandable now.

Also fixed compilation errors for complex Skip/Take arguments. Those are evaluated on the client for the time being.
maumar added a commit that referenced this issue Jun 17, 2016
Problem was for happening for queries with optional navigations like so:

context.Orders.Where(o => o.Customer.IsVip)

In this case Customer can be nullable, so IsVip can also be nullable. We compensate for this by introducing the following:

context.Orders.Where(o => o.Customer != null ? (bool?)o.Customer.IsVip : (bool?)null)

However, we didn't convert it back to the original type (users had to introduce those casts themselves). Without the cast, those queries were throwing compile-time exceptions that were not very informative.

Fix is to cast back to the original type requested by user:

context.Orders.Where(o => (bool)(o.Customer != null ? (bool?)o.Customer.IsVip : (bool?)null))

This still may cause runtime errors if o.Customer is actually null, but those are much more understandable now.

Also fixed compilation errors for complex Skip/Take arguments. Those are evaluated on the client for the time being.

CR: Andrew, Smit
@maumar
Copy link
Contributor

maumar commented Jun 17, 2016

fixed in 7fe8981

@gdoron
Copy link

gdoron commented Jun 17, 2016

@maumar which means it's fixed for 1.0.0, right?
How the versions and milestones work now?

@maumar
Copy link
Contributor

maumar commented Jun 17, 2016

@gdoron unfortunately by the time I got around to making the fix, RTM was pretty much locked for development, apart from security issues and ship stoppers. So this fix will actually be available in post RTM release. You can see the list of commits for RTM here:
https://github.com/aspnet/EntityFramework/commits/release

dev branch is not for post RTM development

@rowanmiller rowanmiller modified the milestones: 1.0.1, 1.1.0 Jul 1, 2016
AndriySvyryd pushed a commit that referenced this issue Jul 2, 2016
Problem was for happening for queries with optional navigations like so:

context.Orders.Where(o => o.Customer.IsVip)

In this case Customer can be nullable, so IsVip can also be nullable. We compensate for this by introducing the following:

context.Orders.Where(o => o.Customer != null ? (bool?)o.Customer.IsVip : (bool?)null)

However, we didn't convert it back to the original type (users had to introduce those casts themselves). Without the cast, those queries were throwing compile-time exceptions that were not very informative.

Fix is to cast back to the original type requested by user:

context.Orders.Where(o => (bool)(o.Customer != null ? (bool?)o.Customer.IsVip : (bool?)null))

This still may cause runtime errors if o.Customer is actually null, but those are much more understandable now.

Also fixed compilation errors for complex Skip/Take arguments. Those are evaluated on the client for the time being.

CR: Andrew, Smit
@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 Jul 11, 2016
@rowanmiller rowanmiller changed the title Error when using Where with reference Query: Exception when filtering on nullable boolean value Jul 21, 2016
@rowanmiller rowanmiller changed the title Query: Exception when filtering on nullable boolean value Query: Exception when filtering on nullable boolean value (thru nav prop) Jul 21, 2016
@divega divega changed the title Query: Exception when filtering on nullable boolean value (thru nav prop) Query: Exception when filtering on nullable boolean value (through navigation property) Sep 13, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

6 participants